Skip to content

Commit

Permalink
perf: various optimizations for Laravel/Symfony (#6954)
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka authored Feb 7, 2025
1 parent dad1760 commit 49da8ec
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 36 deletions.
1 change: 1 addition & 0 deletions src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
49 changes: 21 additions & 28 deletions src/Laravel/Eloquent/Metadata/ModelMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -26,6 +25,16 @@
*/
final class ModelMetadata
{
/**
* @var array<class-string, Collection<string, mixed>>
*/
private $attributesLocalCache = [];

/**
* @var array<class-string, Collection<int, mixed>>
*/
private $relationsLocalCache = [];

/**
* The methods that can be called in a model to indicate a relation.
*
Expand All @@ -45,31 +54,25 @@ 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.
*
* @return Collection<string, mixed>
*/
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();
$columns = $schema->getColumns($table);
$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']
Expand Down Expand Up @@ -112,7 +115,7 @@ private function isColumnPrimaryKey(array $indexes, string $column): bool
*
* @return Collection<int, mixed>
*/
public function getVirtualAttributes(Model $model, array $columns): Collection
private function getVirtualAttributes(Model $model, array $columns): Collection
{
$class = new \ReflectionClass($model);

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -207,20 +214,6 @@ public function getRelations(Model $model): Collection
->values();
}

/**
* Gets the Events that the model dispatches.
*
* @return Collection<int, mixed>
*/
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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 17 additions & 3 deletions src/Metadata/Resource/Factory/LinkFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
*/
final class LinkFactory implements LinkFactoryInterface, PropertyLinkFactoryInterface
{
/**
* @var array<class-string, string[]>
*/
private $localIdentifiersPerResourceClassCache = [];

public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceClassResolverInterface $resourceClassResolver)
{
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

/**
Expand Down
21 changes: 19 additions & 2 deletions src/Metadata/Util/ReflectionClassRecursiveIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,26 @@
*/
final class ReflectionClassRecursiveIterator
{
/**
* @var array<string, array<class-string, \ReflectionClass>>
*/
private static array $localCache;

private function __construct()
{
}

public static function getReflectionClassesFromDirectories(array $directories): \Iterator
/**
* @return array<class-string, \ReflectionClass>
*/
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(
Expand Down Expand Up @@ -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;
}
}

0 comments on commit 49da8ec

Please sign in to comment.