Skip to content

Commit

Permalink
Reflection classes extend native, attributes fully supported, more co…
Browse files Browse the repository at this point in the history
…mpatibility tests, memory management via session
  • Loading branch information
vudaltsov committed Feb 8, 2024
1 parent 85770f6 commit f4a3837
Show file tree
Hide file tree
Showing 70 changed files with 3,401 additions and 2,555 deletions.
22 changes: 22 additions & 0 deletions .RecursiveTypeReplacer.php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PHPyh\CodingStandard\PhpCsFixerCodingStandard;

$finder = Finder::create()->append([
__FILE__,
__DIR__ . '/src/TypeResolver/RecursiveTypeReplacer.php',
]);

$config = (new Config())
->setFinder($finder)
->setCacheFile(__DIR__ . '/var/' . basename(__FILE__) . '.cache');

(new PhpCsFixerCodingStandard())->applyTo($config, [
'final_public_method_for_abstract_class' => false,
]);

return $config;
29 changes: 29 additions & 0 deletions .Reflection.php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PHPyh\CodingStandard\PhpCsFixerCodingStandard;

$finder = Finder::create()->append([
__FILE__,
__DIR__ . '/src/AttributeReflection.php',
__DIR__ . '/src/ClassReflection.php',
__DIR__ . '/src/MethodReflection.php',
__DIR__ . '/src/ParameterReflection.php',
__DIR__ . '/src/PropertyReflection.php',
]);

$config = (new Config())
->setFinder($finder)
->setCacheFile(__DIR__ . '/var/' . basename(__FILE__) . '.cache');

(new PhpCsFixerCodingStandard())->applyTo($config);

$rules = $config->getRules();
$rules['ordered_class_elements']['sort_algorithm'] = 'alpha';
$rules['no_unset_on_property'] = false;
$rules['phpdoc_no_alias_tag'] = false;

return $config->setRules($rules);
22 changes: 22 additions & 0 deletions .TypeInheritanceResolver.php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PHPyh\CodingStandard\PhpCsFixerCodingStandard;

$finder = Finder::create()->append([
__FILE__,
__DIR__ . '/src/Inheritance/TypeInheritanceResolver.php',
]);

$config = (new Config())
->setFinder($finder)
->setCacheFile(__DIR__ . '/var/' . basename(__FILE__) . '.cache');

(new PhpCsFixerCodingStandard())->applyTo($config, [
'strict_comparison' => false,
]);

return $config;
33 changes: 19 additions & 14 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,30 @@
use PHPyh\CodingStandard\PhpCsFixerCodingStandard;

$finder = Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
->in(__DIR__ . '/src')
->notName([
'AttributeReflection.php',
'ClassReflection.php',
'MethodReflection.php',
'ParameterReflection.php',
'PropertyReflection.php',
'RecursiveTypeReplacer.php',
'TypeInheritanceResolver.php',
])
->append([
__FILE__,
])
->exclude([
'unit/NameContext/functional',
'unit/ReflectorCompatibility',
]);
->append([__FILE__])
->append(
Finder::create()
->in(__DIR__ . '/tests')
->exclude([
'unit/NameContext/functional',
'unit/ReflectorCompatibility',
]),
);

$config = (new Config())
->setFinder($finder)
->setCacheFile(__DIR__ . '/var/.php-cs-fixer.cache');

(new PhpCsFixerCodingStandard())->applyTo($config, [
/** @see TypeInheritanceResolver::equal() */
'strict_comparison' => false,
]);
(new PhpCsFixerCodingStandard())->applyTo($config);

return $config;
14 changes: 8 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,10 @@
"phpunit/phpunit": "^10.5.8",
"phpyh/coding-standard": "^2.6.0",
"psalm/plugin-phpunit": "^0.18.4",
"symfony/filesystem": "^6.4",
"symfony/finder": "^6.4",
"symfony/var-dumper": "^6.4.2",
"typhoon/exporter": "^0.2@dev",
"symfony/var-dumper": "^6.4.2 || ^7",
"typhoon/opcache": "^0.2@dev",
"typhoon/psalm-plugin": "^0.1.0",
"vimeo/psalm": "^5.21"
"vimeo/psalm": "^5@dev"
},
"autoload": {
"psr-4": {
Expand All @@ -55,7 +52,12 @@
"sort-packages": true
},
"scripts": {
"fixcs": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix -v",
"fixcs": [
"vendor/bin/php-cs-fixer fix -v",
"vendor/bin/php-cs-fixer fix --config=.RecursiveTypeReplacer.php-cs-fixer.dist.php -v",
"vendor/bin/php-cs-fixer fix --config=.Reflection.php-cs-fixer.dist.php -v",
"vendor/bin/php-cs-fixer fix --config=.TypeInheritanceResolver.php-cs-fixer.dist.php -v"
],
"infection": "infection --threads=max --only-covered --show-mutations",
"pre-command-run": "mkdir -p var",
"psalm": "psalm --show-info=true --no-diff",
Expand Down
360 changes: 172 additions & 188 deletions docs/compatibility.md

Large diffs are not rendered by default.

15 changes: 2 additions & 13 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
findUnusedVariablesAndParams="true"
memoizeMethodCallResults="true"
reportMixedIssues="true"
sealAllProperties="true"
sealAllMethods="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
Expand All @@ -38,6 +39,7 @@
</projectFiles>

<ignoreExceptions>
<classAndDescendants name="ReflectionException"/>
<classAndDescendants name="LogicException"/>
<classAndDescendants name="RuntimeException"/>
<classAndDescendants name="Psr\SimpleCache\InvalidArgumentException"/>
Expand All @@ -49,19 +51,6 @@
<directory name="tests"/>
</errorLevel>
</MissingThrowsDocblock>
<MixedAssignment errorLevel="suppress"/>
<PossiblyUnusedMethod>
<errorLevel type="suppress">
<directory name="tests"/>
</errorLevel>
</PossiblyUnusedMethod>
<RiskyTruthyFalsyComparison errorLevel="suppress"/>
<UnusedMethodCall>
<errorLevel type="suppress">
<directory name="tests"/>
</errorLevel>
</UnusedMethodCall>
<UnresolvableInclude errorLevel="suppress"/>
</issueHandlers>

<forbiddenFunctions>
Expand Down
82 changes: 24 additions & 58 deletions src/AttributeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,99 +4,65 @@

namespace Typhoon\Reflection;

use Typhoon\Reflection\Metadata\AttributeMetadata;

/**
* @api
* @template TAttribute of object
* @extends \ReflectionAttribute<TAttribute>
* @psalm-suppress MissingImmutableAnnotation
*/
final class AttributeReflection
final class AttributeReflection extends \ReflectionAttribute
{
public const TARGET_FUNCTION = \Attribute::TARGET_FUNCTION;
public const TARGET_CLASS = \Attribute::TARGET_CLASS;
public const TARGET_CLASS_CONSTANT = \Attribute::TARGET_CLASS_CONSTANT;
public const TARGET_PROPERTY = \Attribute::TARGET_PROPERTY;
public const TARGET_METHOD = \Attribute::TARGET_METHOD;
public const TARGET_PARAMETER = \Attribute::TARGET_PARAMETER;

/**
* @internal
* @psalm-internal Typhoon\Reflection
* @param class-string<TAttribute> $name
* @param int<0, max> $position
* @param self::TARGET_* $target
* @param non-empty-list $nativeOwnerArguments
* @param ?\ReflectionAttribute<TAttribute> $nativeReflection
* @param \Closure(): list<\ReflectionAttribute> $nativeAttributes
*/
public function __construct(
private readonly string $name,
private readonly int $position,
private readonly int $target,
private readonly bool $repeated,
private readonly array $nativeOwnerArguments,
private ?\ReflectionAttribute $nativeReflection = null,
private readonly AttributeMetadata $metadata,
private readonly \Closure $nativeAttributes,
) {}

/**
* @return class-string<TAttribute>
*/
public function getName(): string
public function __toString(): string
{
return $this->name;
return $this->native()->__toString();
}

/**
* @return self::TARGET_*
*/
public function getTarget(): int
public function getArguments(): array
{
return $this->target;
return $this->native()->getArguments();
}

public function isRepeated(): bool
public function getName(): string
{
return $this->repeated;
return $this->metadata->name;
}

public function getArguments(): array
public function getTarget(): int
{
return $this->metadata->target;
}

public function isRepeated(): bool
{
return $this->getNativeReflection()->getArguments();
return $this->metadata->repeated;
}

/**
* @return TAttribute
*/
public function newInstance(): object
{
return $this->getNativeReflection()->newInstance();
}

public function __serialize(): array
{
return array_diff_key(get_object_vars($this), [
'nativeReflection' => null,
]);
return $this->native()->newInstance();
}

/**
* @return \ReflectionAttribute<TAttribute>
*/
public function getNativeReflection(): \ReflectionAttribute
private function native(): \ReflectionAttribute
{
if ($this->nativeReflection !== null) {
return $this->nativeReflection;
}

/** @psalm-suppress MixedArgument */
$owner = match ($this->target) {
self::TARGET_FUNCTION => new \ReflectionFunction(...$this->nativeOwnerArguments),
self::TARGET_CLASS => new \ReflectionClass(...$this->nativeOwnerArguments),
self::TARGET_CLASS_CONSTANT => new \ReflectionClassConstant(...$this->nativeOwnerArguments),
self::TARGET_PROPERTY => new \ReflectionProperty(...$this->nativeOwnerArguments),
self::TARGET_METHOD => new \ReflectionMethod(...$this->nativeOwnerArguments),
self::TARGET_PARAMETER => new \ReflectionParameter(...$this->nativeOwnerArguments),
};
/** @var \ReflectionAttribute<TAttribute> */
$attribute = $owner->getAttributes()[$this->position];

return $this->nativeReflection = $attribute;
return ($this->nativeAttributes)()[$this->metadata->position] ?? throw new ReflectionException();
}
}
69 changes: 69 additions & 0 deletions src/Attributes/AttributeReflections.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Attributes;

use Typhoon\Reflection\AttributeReflection;
use Typhoon\Reflection\ClassReflection\ClassReflector;
use Typhoon\Reflection\Metadata\AttributeMetadata;

/**
* @internal
* @psalm-internal Typhoon\Reflection
* @template-covariant T of object
* @psalm-suppress PossiblyUnusedProperty
*/
final class AttributeReflections
{
/**
* @var list<AttributeReflection>
*/
private readonly array $attributes;

/**
* @param list<AttributeMetadata> $attributes
* @param \Closure(): list<\ReflectionAttribute> $nativeAttributes
*/
public function __construct(
private readonly ClassReflector $classReflector,
array $attributes,
\Closure $nativeAttributes,
) {
$this->attributes = array_map(
static fn(AttributeMetadata $attribute): \ReflectionAttribute => new AttributeReflection($attribute, $nativeAttributes),
$attributes,
);
}

/**
* @template TClass as object
* @param class-string<TClass>|null $name
* @return ($name is null ? list<AttributeReflection<object>> : list<AttributeReflection<TClass>>)
*/
public function get(?string $name, int $flags): array
{
if ($this->attributes === []) {
return [];
}

if ($name === null) {
return $this->attributes;
}

if ($flags & \ReflectionAttribute::IS_INSTANCEOF) {
/** @var list<AttributeReflection<TClass>> */
return array_filter(
$this->attributes,
fn(AttributeReflection $attribute): bool => $attribute->getName() === $name
|| $this->classReflector->reflectClass($attribute->getName())->isSubclassOf($name),
);
}

/** @var list<AttributeReflection<TClass>> */
return array_filter(
$this->attributes,
static fn(AttributeReflection $attribute): bool => $attribute->getName() === $name,
);
}
}
2 changes: 1 addition & 1 deletion src/ClassLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ interface ClassLocator
/**
* @param non-empty-string $name
*/
public function locateClass(string $name): null|\ReflectionClass|Resource;
public function locateClass(string $name): null|\ReflectionClass|FileResource;
}
Loading

0 comments on commit f4a3837

Please sign in to comment.