diff --git a/src/Laravel/ApiPlatformProvider.php b/src/Laravel/ApiPlatformProvider.php index b1612e0542c..b47a731431d 100644 --- a/src/Laravel/ApiPlatformProvider.php +++ b/src/Laravel/ApiPlatformProvider.php @@ -247,6 +247,7 @@ public function register(): void ); }); + $this->app->singleton(ModelMetadata::class); $this->app->bind(LoaderInterface::class, AttributeLoader::class); $this->app->bind(ClassMetadataFactoryInterface::class, ClassMetadataFactory::class); $this->app->singleton(ClassMetadataFactory::class, function (Application $app) { diff --git a/src/Laravel/Eloquent/Metadata/ModelMetadata.php b/src/Laravel/Eloquent/Metadata/ModelMetadata.php index 77254e1681d..e6c5b90ae78 100644 --- a/src/Laravel/Eloquent/Metadata/ModelMetadata.php +++ b/src/Laravel/Eloquent/Metadata/ModelMetadata.php @@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Gate; use Illuminate\Support\Str; /** @@ -26,6 +25,16 @@ */ final class ModelMetadata { + /** + * @var array> + */ + private $attributesLocalCache = []; + + /** + * @var array> + */ + private $relationsLocalCache = []; + /** * The methods that can be called in a model to indicate a relation. * @@ -45,16 +54,6 @@ final class ModelMetadata 'morphedByMany', ]; - /** - * Gets the first policy associated with this model. - */ - public function getPolicy(Model $model): ?string - { - $policy = Gate::getPolicyFor($model::class); - - return $policy ? $policy::class : null; - } - /** * Gets the column attributes for the given model. * @@ -62,6 +61,10 @@ public function getPolicy(Model $model): ?string */ public function getAttributes(Model $model): Collection { + if (isset($this->attributesLocalCache[$model::class])) { + return $this->attributesLocalCache[$model::class]; + } + $connection = $model->getConnection(); $schema = $connection->getSchemaBuilder(); $table = $model->getTable(); @@ -69,7 +72,7 @@ public function getAttributes(Model $model): Collection $indexes = $schema->getIndexes($table); $relations = $this->getRelations($model); - return collect($columns) + return $this->attributesLocalCache[$model::class] = collect($columns) ->reject( fn ($column) => $relations->contains( fn ($relation) => $relation['foreign_key'] === $column['name'] @@ -112,7 +115,7 @@ private function isColumnPrimaryKey(array $indexes, string $column): bool * * @return Collection */ - public function getVirtualAttributes(Model $model, array $columns): Collection + private function getVirtualAttributes(Model $model, array $columns): Collection { $class = new \ReflectionClass($model); @@ -155,7 +158,11 @@ public function getVirtualAttributes(Model $model, array $columns): Collection */ public function getRelations(Model $model): Collection { - return collect(get_class_methods($model)) + if (isset($this->relationsLocalCache[$model::class])) { + return $this->relationsLocalCache[$model::class]; + } + + return $this->relationsLocalCache[$model::class] = collect(get_class_methods($model)) ->map(fn ($method) => new \ReflectionMethod($model, $method)) ->reject( fn (\ReflectionMethod $method) => $method->isStatic() @@ -207,20 +214,6 @@ public function getRelations(Model $model): Collection ->values(); } - /** - * Gets the Events that the model dispatches. - * - * @return Collection - */ - public function getEvents(Model $model): Collection - { - return collect($model->dispatchesEvents()) - ->map(fn (string $class, string $event) => [ - 'event' => $event, - 'class' => $class, - ])->values(); - } - /** * Gets the cast type for the given column. */ diff --git a/src/Metadata/Resource/Factory/ConcernsResourceNameCollectionFactory.php b/src/Metadata/Resource/Factory/ConcernsResourceNameCollectionFactory.php index 963e10e4bc3..e935be70fd2 100644 --- a/src/Metadata/Resource/Factory/ConcernsResourceNameCollectionFactory.php +++ b/src/Metadata/Resource/Factory/ConcernsResourceNameCollectionFactory.php @@ -47,10 +47,14 @@ public function create(): ResourceNameCollection } foreach (ReflectionClassRecursiveIterator::getReflectionClassesFromDirectories($this->paths) as $className => $reflectionClass) { + if (!$reflectionClass->hasMethod('apiResource')) { + continue; + } + + $m = $reflectionClass->getMethod('apiResource'); + if ( - $reflectionClass->hasMethod('apiResource') - && ($m = $reflectionClass->getMethod('apiResource')) - && $m->isPublic() + $m->isPublic() && $m->isStatic() ) { $classes[$className] = true; diff --git a/src/Metadata/Resource/Factory/LinkFactory.php b/src/Metadata/Resource/Factory/LinkFactory.php index 248bf6d5986..5c020a26976 100644 --- a/src/Metadata/Resource/Factory/LinkFactory.php +++ b/src/Metadata/Resource/Factory/LinkFactory.php @@ -26,6 +26,11 @@ */ final class LinkFactory implements LinkFactoryInterface, PropertyLinkFactoryInterface { + /** + * @var array + */ + private $localIdentifiersPerResourceClassCache = []; + public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceClassResolverInterface $resourceClassResolver) { } @@ -138,8 +143,17 @@ public function completeLink(Link $link): Link return $link; } + /** + * @param class-string $resourceClass + * + * @return string[] + */ private function getIdentifiersFromResourceClass(string $resourceClass): array { + if (isset($this->localIdentifiersPerResourceClassCache[$resourceClass])) { + return $this->localIdentifiersPerResourceClassCache[$resourceClass]; + } + $hasIdProperty = false; $identifiers = []; foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) { @@ -155,14 +169,14 @@ private function getIdentifiersFromResourceClass(string $resourceClass): array } if ($hasIdProperty && !$identifiers) { - return ['id']; + return $this->localIdentifiersPerResourceClassCache[$resourceClass] = ['id']; } if (!$hasIdProperty && !$identifiers && enum_exists($resourceClass)) { - return ['value']; + return $this->localIdentifiersPerResourceClassCache[$resourceClass] = ['value']; } - return $identifiers; + return $this->localIdentifiersPerResourceClassCache[$resourceClass] = $identifiers; } /** diff --git a/src/Metadata/Util/ReflectionClassRecursiveIterator.php b/src/Metadata/Util/ReflectionClassRecursiveIterator.php index 7b22077cbb7..76d99be2090 100644 --- a/src/Metadata/Util/ReflectionClassRecursiveIterator.php +++ b/src/Metadata/Util/ReflectionClassRecursiveIterator.php @@ -22,12 +22,26 @@ */ final class ReflectionClassRecursiveIterator { + /** + * @var array> + */ + private static array $localCache; + private function __construct() { } - public static function getReflectionClassesFromDirectories(array $directories): \Iterator + /** + * @return array + */ + public static function getReflectionClassesFromDirectories(array $directories): array { + $id = hash('xxh3', implode('', $directories)); + if (isset(self::$localCache[$id])) { + return self::$localCache[$id]; + } + + $includedFiles = []; foreach ($directories as $path) { $iterator = new \RegexIterator( new \RecursiveIteratorIterator( @@ -61,12 +75,15 @@ public static function getReflectionClassesFromDirectories(array $directories): $sortedInterfaces = get_declared_interfaces(); sort($sortedInterfaces); $declared = [...$sortedClasses, ...$sortedInterfaces]; + $ret = []; foreach ($declared as $className) { $reflectionClass = new \ReflectionClass($className); $sourceFile = $reflectionClass->getFileName(); if (isset($includedFiles[$sourceFile])) { - yield $className => $reflectionClass; + $ret[$className] = $reflectionClass; } } + + return self::$localCache[$id] = $ret; } }