Created
February 29, 2024 16:08
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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