-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Fixing missing "exclude" functionality from PSR4 loader #22680
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
dbd881d
to
54b0653
Compare
You don't have to use a single psr4 block. You can have several, one per sub namespace, no? This would still be less config lines than 3.2-style config, no? Thus, not sure about the "bug fix" part. Registering everything as a service by default looks strange. |
@nicolas-grekas if you create lots of different subnamespaces to organize your code, most of these subnamespaces will be about services (as you will keep the entities grouped somewhere to avoid making your Doctrine ORM config totally crazy). Adding 1 PSR-4 rule per subnamespace still requires listing them all one-by-one (and remembering to add the new subnamespace in the list when adding one) |
Looking at symfony/symfony-standard#1070, I think like it. I'm mostly challenging the bug fix nature of the PR. Maybe I shouldn't :) |
Should the pattern be matched against the path or the class? That's not obvious to me. |
The
I chose bug in part because I think this is important for 3.3 :). Well, specifically, I think symfony/symfony-standard#1070 is important for 3.3, and this is needed. But also, if you have 40 directories, listing them all one-by-one just to avoid |
@@ -82,11 +84,30 @@ public function testRegisterClasses() | |||
$container->setParameter('sub_dir', 'Sub'); | |||
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); | |||
|
|||
$loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*'); | |||
$loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*', ''); |
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 needed, right?
new Definition(), | ||
'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\', | ||
'Prototype/*', | ||
// load everything, except for OtherDir/AnotherSub & Foo.ph |
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.
.php
and I would remove the for
608b317
to
b08065e
Compare
Nice feature ... but the proposed syntax looks confusing:
The If the option name is already "exclude", why can't we use positive glob expressions for it? |
@javiereguiluz The exclude option is a regex, not a glob so Maybe the proposition should be updated to use a glob for the exclude part to, it's strange to use a glob for the resource key and a regex for the exclude key. |
@jvasseur thanks! So I was confused ... but for the wrong reasons 😵 I agree that this should be a glob, just like the resource key. |
You're lacking Do you have another idea? |
@GuilhemN glob syntax has an equivalent to symfony/symfony-standard#1070 would become |
Right, I didn't think about this possibility! |
@weaverryan For consistency, I'm also for using globs instead of regexes. |
2016fe3
to
4f43922
Compare
Updated to use glob! Great idea (seems obvious in hindsight). PR example updated to show its usage. Tests are passing - the one failure is also failing on master. I used the full path for the resource: '../../src/AppBundle/*'
# current
exclude: '../../src/AppBundle/{AppBundle.php,Entity}'
# but we could possibly shorten if we want
exclude: {AppBundle.php,Entity}` What we have now is a bit longer... but it also looks really clear. |
100% agree with the long version. We should throw an exception when "exclude" does not share the same prefix than with "resource" |
4f43922
to
df13524
Compare
Ok, long version it is then! I added a 2nd commit that throws an exception if the Anything else? Also, this PR will fix a "bug" / "bad error" caused by autowiring in some cases (symfony/symfony-standard#1070 (comment)). |
} | ||
|
||
// realpath to make sure Windows slashes are consistent | ||
$excludePaths[realpath($path)] = 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.
realpath()
with return false
if the $path does not exist. You should take care of that. We try to avoid using realpath()
in Symfony (another issue is when you are using a phar -- not sure we need to support that case anyway).
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 realpath also won't work inside a phar
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.
realpath($path) ?: $path
} | ||
} | ||
|
||
if (isset($excludePaths[realpath($path)])) { |
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.
Same here of course
df13524
to
b420790
Compare
I've removed The So, things should be good now! |
$excludePrefix = $resource->getPrefix(); | ||
} | ||
|
||
// realpath to make sure Windows slashes are consistent |
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.
Comment needs a fix
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.
👍
…lob doesn't support this
b420790
to
7d07f19
Compare
Thank you @weaverryan. |
…loader (weaverryan) This PR was merged into the 3.3-dev branch. Discussion ---------- [DI] Fixing missing "exclude" functionality from PSR4 loader | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | none | License | MIT | Doc PR | TODO When the PSR4 loader was added in #21289, @nicolas-grekas said this: > given that glob() is powerful enough to include/exclude dirs, I removed the Test special exclusion (source: #21289 (comment)) But, I don't believe that's true! [Glob is all about inclusion, not exclusion](https://en.wikipedia.org/wiki/Glob_(programming)#Syntax) - the maximum you can exclude is a single character. Thus, I've marked this as a bug. My motivation came from upgrading KnpU to the new 3.3 DI stuff. We have many directories (40) in `src/`, and listing them all one-by-one in `resource:` is crazy - the `resource` line would be ~350 characters long and quite unreadable. What I really want to do is include *everything* and then exclude few directories. I tried to do this with `glob`, but it's not possible. This PR allows for something like this: ```yml services: _defaults: public: false # ... AppBundle\: resource: '../../src/AppBundle/*' exclude: '../../src/AppBundle/{AppBundle.php,Entity}' ``` This works *beautifully* in practice. And even if I forget to exclude a directory - since the services are private - **they're ultimately removed from the container anyways**. In fact, the *only* reason I need to exclude *anything* is because of the new "service argument resolver", which causes entities to not be properly removed from the compiled container. Thanks! Commits ------- 7d07f19 Allowing prototype/PSR4 service loading ability to exclude, because glob doesn't support this
…ices (weaverryan) This PR was merged into the 3.3-dev branch. Discussion ---------- Making *all* classes in src/AppBundle available as services Hi guys! tl;dr; This makes *all* services from `src/AppBundle/` available as services by default. > This PR depends on symfony/symfony#22680 and symfony/symfony#22665 If this seems crazy to you, it's actually not: it's almost exactly how the system already works :). By registering *all* of `src/AppBundle`, we will almost undoubtedly register some classes that should *not* be services. But, because the services are private, unless you actually reference them somewhere, they're simply removed. In other words, **we're not *really* importing all services from `src/AppBundle`, we're making all classes in `src/AppBundle` *available* to be used as services**. Today, thanks to autowiring, if you type-hint a class that is *not* registered as a service, autowiring registers it for you. That means that **the total number of services in the compiled container today versus after this change will be the same**. * **Today** if you don't explicitly register a service as public and don't type-hint/reference it anywhere, it's never added. * **After** if you don't explicitly register a service as public and don't type-hint/reference it somewhere, it's removed from the final container So, the end result is the same as now. And there are a number of advantages: **PROS** * A) **Better developer experience: when a dev creates a new directory**, they don't need to remember to go into `services.yml` and add it. The original directories we added to the SE were a random "guess"... which should feel weird. I think it's because that approach is flawed in general. * B) **Less WTF and better DX with `autoconfigure`**: now, if I create a new class that extends `Twig_Extension` but forget to import my `Twig` directory, then my extension won't work. After this, we'll find it for sure. * C) **Clearer for the documentation**: before this change, in many places, we need to say "go to services.yml and add this new directory to your import... unless you already have it... and don't make your line look exactly like our's - you're probably also importing other directories" * D) **We could deprecating the "autowire auto-registration" code in 3.4** (i.e. when autowiring automatically registers a private services if no instance exists in the container). That means less magic: this is actually *more* explicit. This could be awesome - we could remove some "hacky" code (e.g. symfony/symfony#22306). And, if you type-hinted an `Entity` (for example) in autowiring, instead of the container creating a service for you, it can give you a clear error In theory, the CON is that this will slow down the container rebuilding as more services are added to `ContainerBuilder` (before being removed later). On one project, I compared the build time without importing everything (138 services added from `src/`) versus importing everything (200 services dded from `src/`). The build time difference was negligible - maybe 10ms difference (both were around 950ms). Btw, the `exclude` key added in symfony/symfony#22680 is almost not even needed, since unused services are simply removed. But, currently, the `Entity` directory is an exception due to the new "argument resolver" trying to see if it can wire those as services. That could be fixed in the future. But `exclude` is kind of nice, if you want to explicitly safe-guard someone from accidentally type-hinting something from that directory. Again, that's *better* than now... where we will *always* auto-register an Entity if you type-hint it. Cheers! Commits ------- 47d3561 Making *all* services in src/AppBundle available as services
When the PSR4 loader was added in #21289, @nicolas-grekas said this:
But, I don't believe that's true! Glob is all about inclusion, not exclusion - the maximum you can exclude is a single character. Thus, I've marked this as a bug.
My motivation came from upgrading KnpU to the new 3.3 DI stuff. We have many directories (40) in
src/
, and listing them all one-by-one inresource:
is crazy - theresource
line would be ~350 characters long and quite unreadable. What I really want to do is include everything and then exclude few directories. I tried to do this withglob
, but it's not possible.This PR allows for something like this:
This works beautifully in practice. And even if I forget to exclude a directory - since the services are private - they're ultimately removed from the container anyways. In fact, the only reason I need to exclude anything is because of the new "service argument resolver", which causes entities to not be properly removed from the compiled container.
Thanks!