-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI][Config] Add & use ReflectionClassResource #21419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
79cf689
to
769b2c5
Compare
|
||
unlink($tmp); | ||
$this->assertFalse($res->isFresh($now), '->isFresh() returns false if the resource does not exist'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a way to test the handling of the hash ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I split the logic to allow for testing signature mutations - tests added
@@ -459,6 +424,9 @@ private function addServiceToAmbiguousType($id, $type) | |||
$this->ambiguousServiceTypes[$type][] = $id; | |||
} | |||
|
|||
/** | |||
* @deprecated since version 3.3, to be removed in 4.0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deprecating a private method does not make sense. Either we don't use it anymore, and we should remove it already, or we use it in deprecated method and it will become dead code when removing the other method (which is catched by IDEs and any static analysis tool, for instance SensioLabsInsight)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just a friendly reminder for the one who will cleanup the code preparing 4.0
* | ||
* @param string $class | ||
* | ||
* @return \ReflectionClass|false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use \ReflectionClass|null
instead ? It would be cleaner (PHP 7.1 has nullable types, not falsable types)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would only add boilerplate (isset(false)
is used in the code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if this was a private API, I would be OK with this argument about simplifying the internal implementation.
But this is a public API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicolas-grekas I think we need to try to not use mixed return args in our public APIs (anymore). With \ReflectionClass|null
, we could add a native PHP return type in 7.1, but \ReflectionClass|false
, we cannot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -20,7 +20,7 @@ | |||
}, | |||
"require-dev": { | |||
"symfony/yaml": "~3.2", | |||
"symfony/config": "~2.8|~3.0", | |||
"symfony/config": "~3.3", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we add a conflict rule to avoid issues when getting a lower version for people using standalone components ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, added
*/ | ||
public static function createResourceForClass(\ReflectionClass $reflectionClass) | ||
{ | ||
@trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', E_USER_DEPRECATED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it's a good idea or not. The current implementation will more efficient because some changes doesn't impact autowiring (like properties changes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In practice, I don't think there are that more situations where the new code would invalidate where the existing wouldn't. And I see the benefit for having only one place where this tracking logic is implemented. So I think it's in fact a desirable thing yes. Allows better aggregation of resources across passes also - and maybe faster checking (with simpler/optimized logic.)
👍 |
dfcd57d
to
d287508
Compare
061a58a
to
86836f9
Compare
Hash computation, file and class existence is now done lazily - ie after the resources are deduplicated. And to prevent checking the hash again when it was found valid in a previous run, files are "touched" when needed. |
7a057f6
to
6342a34
Compare
Rebased |
b267a7f
to
e251e19
Compare
if (is_subclass_of($class, 'Symfony\Component\Translation\TranslatorInterface') && is_subclass_of($class, 'Symfony\Component\Translation\TranslatorBagInterface')) { | ||
if (!$r = $container->getReflectionClass($class)) { | ||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias)); | ||
} elseif ($r->isSubclassOf(TranslatorInterface::class) && $r->isSubclassOf(TranslatorBagInterface::class)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just if
here, as the previous case is throwing
*/ | ||
public function __construct($resource) | ||
public function __construct($resource, $exists = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the name is confusing here. It is not a value telling whether the class exists (which is what I thought when seeing the param name, compared to the level), but a checking level
{ | ||
$this->resource = $resource; | ||
$this->exists = class_exists($resource); | ||
if (null !== $exists) { | ||
$this->exists = (int) $exists; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rename the property, as this is not storing whether the class exists (unlike previously)
yield $class->getDocComment().$class->getModifiers(); | ||
|
||
if ($class->isTrait()) { | ||
yield print_r(class_uses($class->name), true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be always included ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need: traits are not directly part of the signature - the methods they provide will be seen as any other methods, thus tracked
|
||
public function testExistsKo() | ||
{ | ||
spl_autoload_register(function ($class) use (&$loadedClass) { $loadedClass = $class; }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should unregister it at the end of the test (even when it fails)
@@ -30,13 +32,17 @@ public function process(ContainerBuilder $container) | |||
$definition = $container->getDefinition($id); | |||
|
|||
if ($definition->isAbstract()) { | |||
throw new \InvalidArgumentException(sprintf('The service "%s" tagged "console.command" must not be abstract.', $id)); | |||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why changing this ?
// ensure the next checks won't require a hash computation again, | ||
// use a local mtime cache so that the current process still sees | ||
// the original mtime and validates the hash for child classes | ||
@touch($file, $timestamp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might break other systems relying on the real mtime of the file because they are concerned about more things than the ones involved in the Symfony hash
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, removed
@@ -251,7 +253,32 @@ public function setResources(array $resources) | |||
public function addObjectResource($object) | |||
{ | |||
if ($this->trackResources) { | |||
$this->addClassResource(new \ReflectionClass($object)); | |||
if (is_object($object)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it also accepts class names, the phpdoc must be updated
*/ | ||
public function addClassResource(\ReflectionClass $class) | ||
{ | ||
if (!$this->trackResources) { | ||
return $this; | ||
@trigger_error('The '.__METHOD__.'() mehtod is deprecated since version 3.3 and will be removed in 4.0. Use the addObjectResource() or the getReflectionClass() method instead.', E_USER_DEPRECATED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
} | ||
|
||
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); | ||
if (!$r = $container->getReflectionClass($class)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually need to invalidate the cache when the implementation of the class changes here, as it can change the list of events (btw, it means we are missing a ClassResource today)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, fixed
3e78969
to
0db525a
Compare
@stof comments addressed |
0db525a
to
3e78e70
Compare
if ($this->trackResources) { | ||
if (!$classReflector) { | ||
$this->addResource($resource ?: new ClassExistenceResource($class, ClassExistenceResource::EXISTS_KO)); | ||
} elseif (!$classReflector->isInternal()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is there this check. Should we replace it with a ClassExistenceResource at least for internal classes if there is no way to check for details ?
and if the hash computation does not work for internal classes, what happens for classes extending an internal one ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can track details for internal classes, but this makes no sense: if you switch your version/extension of PHP, the container is not invalidated. Tracking here would mean checking the signature every single time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #21505 for internal classes
foreach ($this->classResources as $r) { | ||
$this->container->addClassResource($r); | ||
} | ||
$this->classResources = array(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we actually have this duplicate code in older branches, or was it all added in 3.3 only ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3.3 only
3e78e70
to
a8ce243
Compare
a8ce243
to
37e4493
Compare
* | ||
* @final | ||
*/ | ||
public function fileExists($path, $trackContents = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved as is above with other resource-related methods
Thank you @nicolas-grekas. |
…s-grekas) This PR was merged into the 3.3-dev branch. Discussion ---------- [DI][Config] Add & use ReflectionClassResource | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #21079 | License | MIT | Doc PR | - With new changes comming to 3.3, we need a more generic reflection tracking logic than the one already managed by the autowiring subsystem. This PR adds a new ReflectionClassResource in the Config component, and a new ContainerBuilder::getReflectionClass() method in the DI one (for fetching+tracking reflection-related info). ReflectionClassResource tracks changes to any public or protected properties/method. PR updated and ready, best viewed [ignoring whitespaces](https://github.com/symfony/symfony/pull/21419/files?w=1). changelog: * added `ReflectionClassResource` class * added second `$exists` constructor argument to `ClassExistenceResource` - with a special mode that prevents fatal errors from happening when some parent class is broken (logic generalized from AutowiringPass) * made `ClassExistenceResource` also work with interfaces and traits * added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info * deprecated `ContainerBuilder::getClassResource()`, use `ContainerBuilder::getReflectionClass()` or `ContainerBuilder::addObjectResource()` instead Commits ------- 37e4493 [DI][Config] Add & use ReflectionClassResource
@@ -22,7 +22,7 @@ | |||
"symfony/dependency-injection": "~3.3", | |||
"symfony/config": "~3.3", | |||
"symfony/event-dispatcher": "~3.3", | |||
"symfony/http-foundation": "~3.1", | |||
"symfony/http-foundation": "~3.3", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason for this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Transitivity and composer bug, this doesn't change the result, it only helps composer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, I was just wondering as the code changes didn't indicate any need for this change
This PR was merged into the 1.0-dev branch. Discussion ---------- | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT `ContainerBuilder::addClassResource()` is deprecated. symfony/symfony#21419 Commits ------- de72f26 Fix Symfony 4.0 compatibility
…sn't exist (chalasr) This PR was merged into the 3.3-dev branch. Discussion ---------- [DI] Don't try to instantiate reflection class if it doesn't exist | Q | A | ------------- | --- | Branch? | master | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | n/a Introduced in #21419 so master only. It breaks on bundles that ~~do not use the convention for naming their `Configuration`~~ do not have configuration, e.g. SecurityBundle's FirewallEntryPointExtension for which tests are actually broken (see travis). Commits ------- f9b917a [DI] Don't instantiate unexisting reflection class
With new changes comming to 3.3, we need a more generic reflection tracking logic than the one already managed by the autowiring subsystem.
This PR adds a new ReflectionClassResource in the Config component, and a new ContainerBuilder::getReflectionClass() method in the DI one (for fetching+tracking reflection-related info).
ReflectionClassResource tracks changes to any public or protected properties/method.
PR updated and ready, best viewed ignoring whitespaces.
changelog:
ReflectionClassResource
class$exists
constructor argument toClassExistenceResource
- with a special mode that prevents fatal errors from happening when some parent class is broken (logic generalized from AutowiringPass)ClassExistenceResource
also work with interfaces and traitsContainerBuilder::getReflectionClass()
for retrieving and tracking reflection class infoContainerBuilder::getClassResource()
, useContainerBuilder::getReflectionClass()
orContainerBuilder::addObjectResource()
instead