diff --git a/src/Drop.php b/src/Drop.php index 6c9da69..2c7d94d 100644 --- a/src/Drop.php +++ b/src/Drop.php @@ -4,27 +4,15 @@ use Keepsuit\Liquid\Concerns\ContextAware; use Keepsuit\Liquid\Contracts\IsContextAware; -use Keepsuit\Liquid\Drops\Cache; -use Keepsuit\Liquid\Drops\Hidden; use Keepsuit\Liquid\Exceptions\UndefinedDropMethodException; +use Keepsuit\Liquid\Support\DropMetadata; use Keepsuit\Liquid\Support\Str; -use ReflectionClass; -use ReflectionMethod; -use Traversable; class Drop implements IsContextAware { use ContextAware; - /** - * @var string[] - */ - private ?array $invokableMethods = null; - - /** - * @var string[] - */ - private ?array $cacheableMethods = null; + private ?DropMetadata $metadata = null; private array $cache = []; @@ -40,8 +28,8 @@ public function __toString(): string public function __get(string $name): mixed { - $invokableMethods = $this->getInvokableMethods(); - $cacheableMethods = $this->getCacheableMethods(); + $invokableMethods = $this->getMetadata()->invokableMethods; + $cacheableMethods = $this->getMetadata()->cacheableMethods; $possibleNames = [ $name, @@ -85,54 +73,8 @@ public function __get(string $name): mixed return null; } - protected function getInvokableMethods(): array + protected function getMetadata(): DropMetadata { - if ($this->invokableMethods === null) { - $this->init(); - } - - return $this->invokableMethods; - } - - protected function getCacheableMethods(): array - { - if ($this->cacheableMethods === null) { - $this->init(); - } - - return $this->cacheableMethods; - } - - /** - * @phpstan-assert !null $this->invokableMethods - * @phpstan-assert !null $this->cacheableMethods - */ - private function init(): void - { - $blacklist = array_map( - fn (ReflectionMethod $method) => $method->getName(), - (new ReflectionClass(Drop::class))->getMethods(ReflectionMethod::IS_PUBLIC) - ); - - if ($this instanceof Traversable) { - $blacklist = [...$blacklist, 'current', 'next', 'key', 'valid', 'rewind']; - } - - $publicMethods = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC); - - $visibleMethodNames = array_map( - fn (ReflectionMethod $method) => $method->getAttributes(Hidden::class) !== [] ? null : $method->getName(), - $publicMethods - ); - - $this->invokableMethods = array_values(array_filter( - array_diff($visibleMethodNames, $blacklist), - fn (?string $name) => $name !== null && ! str_starts_with($name, '__') - )); - - $this->cacheableMethods = array_values(array_filter(array_map( - fn (ReflectionMethod $method) => $method->getAttributes(Cache::class) !== [] ? $method->getName() : null, - $publicMethods - ))); + return $this->metadata ??= DropMetadata::init($this); } } diff --git a/src/Support/DropMetadata.php b/src/Support/DropMetadata.php new file mode 100644 index 0000000..2c59827 --- /dev/null +++ b/src/Support/DropMetadata.php @@ -0,0 +1,62 @@ + + */ + protected static array $cache = []; + + public static function init(Drop $drop): DropMetadata + { + if (isset(self::$cache[get_class($drop)])) { + return self::$cache[get_class($drop)]; + } + + $blacklist = array_map( + fn (ReflectionMethod $method) => $method->getName(), + (new ReflectionClass(Drop::class))->getMethods(ReflectionMethod::IS_PUBLIC) + ); + + if ($drop instanceof Traversable) { + $blacklist = [...$blacklist, 'current', 'next', 'key', 'valid', 'rewind']; + } + + $publicMethods = (new ReflectionClass($drop))->getMethods(ReflectionMethod::IS_PUBLIC); + + $visibleMethodNames = array_map( + fn (ReflectionMethod $method) => $method->getAttributes(Hidden::class) !== [] ? null : $method->getName(), + $publicMethods + ); + + $invokableMethods = array_values(array_filter( + array_diff($visibleMethodNames, $blacklist), + fn (?string $name) => $name !== null && ! str_starts_with($name, '__') + )); + + $cacheableMethods = array_values(array_filter(array_map( + fn (ReflectionMethod $method) => $method->getAttributes(Cache::class) !== [] ? $method->getName() : null, + $publicMethods + ))); + + return self::$cache[get_class($drop)] = new DropMetadata( + invokableMethods: $invokableMethods, + cacheableMethods: $cacheableMethods + ); + } + + public function __construct( + public readonly array $invokableMethods, + public readonly array $cacheableMethods + ) { + } +} diff --git a/tests/Integration/DropTest.php b/tests/Integration/DropTest.php index 9be77ea..059c4b5 100644 --- a/tests/Integration/DropTest.php +++ b/tests/Integration/DropTest.php @@ -1,6 +1,7 @@ new EnumerableDrop()]))->toBe(EnumerableDrop::class); }); -test('invokable methods', function () { - expect(invade(new ProductDrop())->getInvokableMethods())->toBe(['texts', 'catchall', 'context']); - expect(invade(new EnumerableDrop())->getInvokableMethods())->toBe(['size', 'first', 'count', 'min', 'max']); +test('drop metadata', function () { + expect(invade(new ProductDrop())->getMetadata()) + ->invokableMethods->toBe(['texts', 'catchall', 'context']) + ->cacheableMethods->toBe([]); + + expect(invade(new EnumerableDrop())->getMetadata()) + ->invokableMethods->toBe(['size', 'first', 'count', 'min', 'max']) + ->cacheableMethods->toBe([]); + + expect(invade(new CachableDrop())->getMetadata()) + ->invokableMethods->toBe(['notCached', 'cached']) + ->cacheableMethods->toBe(['cached']); }); it('can cache drop method calls', function () { - $drop = new \Keepsuit\Liquid\Tests\Stubs\CachableDrop(); + $drop = new CachableDrop(); expect($drop) ->notCached->toBe(0)