Skip to content

Instantly share code, notes, and snippets.

@tschallacka
Created February 29, 2024 16:08
Show Gist options
  • Save tschallacka/d981b7c63fcd1ddb8399e52d15390b76 to your computer and use it in GitHub Desktop.
Save tschallacka/d981b7c63fcd1ddb8399e52d15390b76 to your computer and use it in GitHub Desktop.
Using attributes to get "AOP" execution of attributes in php. The only caveat is that you need to extend a baseclass, and need to use private methods or methods starting with a _ so __call can work.
<?php
interface RunnableAttribute
{
public function run($object, ReflectionMethod $method, $args);
}
#[Attribute]
class CacheOutput implements RunnableAttribute
{
public function __construct(public int $duration = 60) { }
public function run($object, ReflectionMethod $method, $args)
{
$cacheKey = md5($method->getName().serialize($args));
static $cache = [];
if (isset($cache[$cacheKey])) {
if ($cache[$cacheKey]->hasExpired()) {
unset($cache[$cacheKey]);
} else {
return $cache[$cacheKey]->value . ' (from cache)';
}
}
$value = $method->invoke($object, ...$args);
$cache[$cacheKey] = new CacheItem(new DateTime("now +{$this->duration} seconds"), $value);
return $value;
}
}
class CacheItem
{
public DateTime $expires;
public mixed $value;
public function __construct(DateTime $expires, mixed $value)
{
$this->expires = $expires;
$this->value = $value;
}
public function hasExpired()
{
return $this->expires < new DateTime();
}
}
class Base
{
public function __call(string $name, array $arguments)
{
$reflection = $this->getMethod($name);
if (!is_null($reflection)) {
$attributes = $reflection->getAttributes();
foreach ($attributes as $attribute) {
if (is_subclass_of($attribute->getName(), RunnableAttribute::class)) {
/** @var RunnableAttribute $attribute */
$attribute = $attribute->newInstance();
return $attribute->run($this, $reflection, $arguments);
}
}
}
return call_user_func_array([$this, $name], $arguments);
}
/**
* Gets the reflection method for the given method name.
* @param $name
* @return ReflectionMethod|null
* @throws ReflectionException
*/
private function getMethod($name): ?ReflectionMethod
{
$reflection = new \ReflectionClass($this);
if ($reflection->hasMethod($name)) {
return $reflection->getMethod($name);
} else {
if ($reflection->hasMethod("_$name")) {
return $reflection->getMethod("_$name");
}
}
return null;
}
}
/** @method string bar() */
class Foo extends Base
{
#[CacheOutput(duration: 10)]
public function _bar()
{
return 'baz';
}
}
$foo = new Foo();
echo 'output: '.$foo->bar().PHP_EOL; // baz
echo 'output: '.$foo->bar().PHP_EOL; // baz
echo 'output: '.$foo->bar().PHP_EOL; // baz
echo 'sleeping 11 seconds' . PHP_EOL;
sleep(11);
echo 'output: '.$foo->bar().PHP_EOL; // baz
echo 'output: '.$foo->bar().PHP_EOL; // baz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment