Skip to content

Commit

Permalink
misc: exclude unneeded attributes in class/function definitions
Browse files Browse the repository at this point in the history
This change aims to reduce the memory usage, as well as the compiled
cache size for classes that heavily rely on attributes that are not used
by this library, for instance `OpenAPI` attributes.
  • Loading branch information
romm committed Apr 12, 2024
1 parent 6fad94a commit 5a75ad8
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 141 deletions.
12 changes: 9 additions & 3 deletions src/Definition/Repository/AttributesRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@
namespace CuyZ\Valinor\Definition\Repository;

use CuyZ\Valinor\Definition\AttributeDefinition;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use Reflector;

/** @internal */
interface AttributesRepository
{
/**
* @param ReflectionAttribute<object> $reflection
* @param ReflectionClass<object>|ReflectionProperty|ReflectionMethod|ReflectionFunction|ReflectionParameter $reflection
* @return list<AttributeDefinition>
*/
public function for(ReflectionAttribute $reflection): AttributeDefinition;
public function for(Reflector $reflection): array;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,68 @@
use CuyZ\Valinor\Definition\AttributeDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Normalizer\AsTransformer;
use CuyZ\Valinor\Type\Types\NativeClassType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use Error;
use ReflectionAttribute;
use Reflector;

use function array_map;
use function array_values;

/** @internal */
final class ReflectionAttributesRepository implements AttributesRepository
{
public function __construct(private ClassDefinitionRepository $classDefinitionRepository) {}
public function __construct(
private ClassDefinitionRepository $classDefinitionRepository,
/** @var list<class-string> */
private array $allowedAttributes,
) {}

public function for(ReflectionAttribute $reflection): AttributeDefinition
public function for(Reflector $reflection): array
{
$class = $this->classDefinitionRepository->for(new NativeClassType($reflection->getName()));
$attributes = array_filter(
$reflection->getAttributes(),
function (ReflectionAttribute $attribute) {
foreach ($this->allowedAttributes as $allowedAttribute) {
if (is_a($attribute->getName(), $allowedAttribute, true)) {
return $this->attributeCanBeInstantiated($attribute);
}
}

$parentAttributes = Reflection::class($attribute->getName())->getAttributes(AsTransformer::class);

return new AttributeDefinition(
$class,
$reflection->getArguments(),
return $parentAttributes !== [];
},
);

return array_values(array_map(
fn (ReflectionAttribute $attribute) => new AttributeDefinition(
$this->classDefinitionRepository->for(new NativeClassType($attribute->getName())),
$attribute->getArguments(),
),
$attributes,
));
}

/**
* @param ReflectionAttribute<object> $attribute
*/
private function attributeCanBeInstantiated(ReflectionAttribute $attribute): bool
{
try {
$attribute->newInstance();

return true;
} catch (Error) {
// Race condition when the attribute is affected to a property/parameter
// that was PROMOTED, in this case the attribute will be applied to both
// ParameterReflection AND PropertyReflection, BUT the target arg inside the attribute
// class is configured to support only ONE of them (parameter OR property)
// https://wiki.php.net/rfc/constructor_promotion#attributes for more details.
// Ignore attribute if the instantiation failed.
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace CuyZ\Valinor\Definition\Repository\Reflection;

use CuyZ\Valinor\Definition\AttributeDefinition;
use CuyZ\Valinor\Definition\Attributes;
use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\Exception\ClassTypeAliasesDuplication;
Expand All @@ -22,7 +21,6 @@
use CuyZ\Valinor\Type\ObjectType;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
Expand All @@ -45,10 +43,15 @@ final class ReflectionClassDefinitionRepository implements ClassDefinitionReposi
/** @var array<string, ReflectionTypeResolver> */
private array $typeResolver = [];

public function __construct(TypeParserFactory $typeParserFactory)
{
/**
* @param list<class-string> $allowedAttributes
*/
public function __construct(
TypeParserFactory $typeParserFactory,
array $allowedAttributes,
) {
$this->typeParserFactory = $typeParserFactory;
$this->attributesRepository = new ReflectionAttributesRepository($this);
$this->attributesRepository = new ReflectionAttributesRepository($this, $allowedAttributes);
$this->propertyBuilder = new ReflectionPropertyDefinitionBuilder($this->attributesRepository);
$this->methodBuilder = new ReflectionMethodDefinitionBuilder($this->attributesRepository);
}
Expand All @@ -60,26 +63,14 @@ public function for(ObjectType $type): ClassDefinition
return new ClassDefinition(
$reflection->name,
$type,
new Attributes(...$this->attributes($reflection)),
new Attributes(...$this->attributesRepository->for($reflection)),
new Properties(...$this->properties($type)),
new Methods(...$this->methods($type)),
$reflection->isFinal(),
$reflection->isAbstract(),
);
}

/**
* @param ReflectionClass<object> $reflection
* @return list<AttributeDefinition>
*/
private function attributes(ReflectionClass $reflection): array
{
return array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Reflection::attributes($reflection),
);
}

/**
* @return list<PropertyDefinition>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace CuyZ\Valinor\Definition\Repository\Reflection;

use CuyZ\Valinor\Definition\AttributeDefinition;
use CuyZ\Valinor\Definition\Attributes;
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\Parameters;
Expand All @@ -15,7 +14,6 @@
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionFunction;
use ReflectionParameter;

Expand Down Expand Up @@ -70,7 +68,7 @@ public function for(callable $function): FunctionDefinition
return new FunctionDefinition(
$name,
$signature,
new Attributes(...$this->attributes($reflection)),
new Attributes(...$this->attributesRepository->for($reflection)),
$reflection->getFileName() ?: null,
$class?->name,
$reflection->getClosureThis() === null,
Expand All @@ -80,17 +78,6 @@ public function for(callable $function): FunctionDefinition
);
}

/**
* @return list<AttributeDefinition>
*/
private function attributes(ReflectionFunction $reflection): array
{
return array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Reflection::attributes($reflection),
);
}

/**
* @return non-empty-string
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\FunctionReturnTypeResolver;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ReflectionTypeResolver;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionMethod;
use ReflectionParameter;

Expand All @@ -37,11 +35,6 @@ public function for(ReflectionMethod $reflection, ReflectionTypeResolver $typeRe
$name = $reflection->name;
$signature = $reflection->getDeclaringClass()->name . '::' . $reflection->name . '()';

$attributes = array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Reflection::attributes($reflection)
);

$parameters = array_map(
fn (ReflectionParameter $parameter) => $this->parameterBuilder->for($parameter, $typeResolver),
$reflection->getParameters()
Expand All @@ -61,7 +54,7 @@ public function for(ReflectionMethod $reflection, ReflectionTypeResolver $typeRe
return new MethodDefinition(
$name,
$signature,
new Attributes(...$attributes),
new Attributes(...$this->attributesRepository->for($reflection)),
new Parameters(...$parameters),
$reflection->isStatic(),
$reflection->isPublic(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@

namespace CuyZ\Valinor\Definition\Repository\Reflection;

use CuyZ\Valinor\Definition\AttributeDefinition;
use CuyZ\Valinor\Definition\Attributes;
use CuyZ\Valinor\Definition\ParameterDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ParameterTypeResolver;
use CuyZ\Valinor\Definition\Repository\Reflection\TypeResolver\ReflectionTypeResolver;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionParameter;

use function array_map;

/** @internal */
final class ReflectionParameterDefinitionBuilder
{
Expand Down Expand Up @@ -58,18 +53,7 @@ public function for(ReflectionParameter $reflection, ReflectionTypeResolver $typ
$isOptional,
$isVariadic,
$defaultValue,
new Attributes(...$this->attributes($reflection)),
);
}

/**
* @return list<AttributeDefinition>
*/
private function attributes(ReflectionParameter $reflection): array
{
return array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Reflection::attributes($reflection)
new Attributes(...$this->attributesRepository->for($reflection)),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace CuyZ\Valinor\Definition\Repository\Reflection;

use CuyZ\Valinor\Definition\AttributeDefinition;
use CuyZ\Valinor\Definition\Attributes;
use CuyZ\Valinor\Definition\PropertyDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
Expand All @@ -13,12 +12,8 @@
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\NullType;
use CuyZ\Valinor\Type\Types\UnresolvableType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
use ReflectionAttribute;
use ReflectionProperty;

use function array_map;

/** @internal */
final class ReflectionPropertyDefinitionBuilder
{
Expand Down Expand Up @@ -53,7 +48,7 @@ public function for(ReflectionProperty $reflection, ReflectionTypeResolver $type
$hasDefaultValue,
$defaultValue,
$isPublic,
new Attributes(...$this->attributes($reflection)),
new Attributes(...$this->attributesRepository->for($reflection)),
);
}

Expand All @@ -66,15 +61,4 @@ private function hasDefaultValue(ReflectionProperty $reflection, Type $type): bo
return $reflection->getDeclaringClass()->getDefaultProperties()[$reflection->name] !== null
|| NullType::get()->matches($type);
}

/**
* @return list<AttributeDefinition>
*/
private function attributes(ReflectionProperty $reflection): array
{
return array_map(
fn (ReflectionAttribute $attribute) => $this->attributesRepository->for($attribute),
Reflection::attributes($reflection)
);
}
}
2 changes: 2 additions & 0 deletions src/Library/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ public function __construct(Settings $settings)
ClassDefinitionRepository::class => fn () => new CacheClassDefinitionRepository(
new ReflectionClassDefinitionRepository(
$this->get(TypeParserFactory::class),
$settings->allowedAttributes(),
),
$this->get(CacheInterface::class),
),
Expand All @@ -215,6 +216,7 @@ public function __construct(Settings $settings)
$this->get(TypeParserFactory::class),
new ReflectionAttributesRepository(
$this->get(ClassDefinitionRepository::class),
$settings->allowedAttributes(),
),
),
$this->get(CacheInterface::class)
Expand Down
18 changes: 18 additions & 0 deletions src/Library/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@

namespace CuyZ\Valinor\Library;

use CuyZ\Valinor\Mapper\Object\Constructor;
use CuyZ\Valinor\Mapper\Object\DynamicConstructor;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
use CuyZ\Valinor\Normalizer\AsTransformer;
use DateTimeImmutable;
use DateTimeInterface;
use Psr\SimpleCache\CacheInterface;
use Throwable;

use function array_keys;

/** @internal */
final class Settings
{
Expand Down Expand Up @@ -59,6 +64,19 @@ public function __construct()
$this->exceptionFilter = fn (Throwable $exception) => throw $exception;
}

/**
* @return non-empty-list<class-string>
*/
public function allowedAttributes(): array
{
return [
AsTransformer::class,
Constructor::class,
DynamicConstructor::class,
...array_keys($this->transformerAttributes),
];
}

/**
* @return list<callable>
*/
Expand Down
Loading

0 comments on commit 5a75ad8

Please sign in to comment.