Skip to content

Instantly share code, notes, and snippets.

@SerafimArts
Last active September 22, 2023 09:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SerafimArts/42a5de649de673fb569f6bfd96cc79ca to your computer and use it in GitHub Desktop.
Save SerafimArts/42a5de649de673fb569f6bfd96cc79ca to your computer and use it in GitHub Desktop.
PHP Decorators
<?php
declare(strict_types=1);
interface DecoratorInterface
{
public static function handle(\Reflector $context, self $instance): void;
}
<?php
require __DIR__ . '/vendor/autoload.php';
new StructureExample();
// TypeError@anonymous: The structure class does not allow method StructureExample::method() to be declared in example\StructureExample.php:20
// Stack trace:
// #0 example\PhpStruct.php(31): PhpStruct->error(Object(ReflectionMethod))
// #1 example\PhpStruct.php(65): PhpStruct->assert(Object(ReflectionClass))
// #2 example\interceptor.php(36): PhpStruct::handle(Object(ReflectionClass), Object(PhpStruct))
// #3 example\example.php(11): {closure}('StructureExample')
// #4 {main}
<?php
declare(strict_types=1);
use Composer\Autoload\ClassLoader;
$composer = (static function (): ClassLoader {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) ?? [];
while ($trace) {
$current = array_shift($trace);
if (str_starts_with($current['class'] ?? '', 'ComposerAutoloaderInit')) {
return $current['class']::getLoader();
}
}
throw new LogicException('File ' . __FILE__ . ' MUST be loaded though composer');
})();
spl_autoload_register(static function (string $class) use ($composer) {
$composer->loadClass($class);
if (\class_exists($class, false)) {
$reflection = new \ReflectionClass($class);
foreach ($reflection->getAttributes(DecoratorInterface::class, \ReflectionAttribute::IS_INSTANCEOF) as $context) {
($context->getName())::handle($reflection, $context->newInstance());
}
foreach ($reflection->getProperties() as $property) {
foreach ($property->getAttributes(DecoratorInterface::class, \ReflectionAttribute::IS_INSTANCEOF) as $context) {
($context->getName())::handle($property, $context->newInstance());
}
}
foreach ($reflection->getMethods() as $method) {
foreach ($method->getAttributes(DecoratorInterface::class, \ReflectionAttribute::IS_INSTANCEOF) as $context) {
($context->getName())::handle($method, $context->newInstance());
}
}
}
}, true, true);
<?php
declare(strict_types=1);
#[\Attribute]
class PhpStruct implements DecoratorInterface
{
private const ERROR_METHOD = 'The structure class does not allow method %s::%s() to be declared';
private function assert(\ReflectionClass $class): bool
{
foreach ($class->getMethods() as $method) {
throw $this->error($method);
}
return true;
}
private function error(\ReflectionMethod $method): \TypeError
{
$message = \sprintf(self::ERROR_METHOD, $method->getDeclaringClass()->getName(), $method->getName());
return new class($message, $method) extends \TypeError {
public function __construct(string $message, \ReflectionMethod $ctx)
{
parent::__construct($message);
$this->file = $ctx->getFileName();
$this->line = $ctx->getStartLine();
}
};
}
public static function handle(\Reflector $context, DecoratorInterface $instance): void
{
assert($instance instanceof self);
$instance->assert($context);
}
}
<?php
declare(strict_types=1);
#[PhpStruct]
class StructureExample
{
public string $field;
// Struct (data-class) can not contain methods
public function method()
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment