-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Add a new "PHP fluent format" for configuring the container #22407
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
Comments
This is why I was unable to contribute. I was also unable to find out how to test the changes in the plugin after making adjustments. There's hardly any documentation and there was some old documentation to be found in German only. At first I was skeptical, but from a DX perspective, this will be really nice to have and find typos before compiling the container. I think this would be a very nice addition to Symfony, regardless if it would be in the core or not. |
Note that the container is compiled and dumped to PHP, which is why expressions were created. The main problem all other projects struggle with is performance, which Symfony solved by compiling down to a single optimized container class. The usage of namespaced functions looks really nice and clean, I always use XML because of the mentioned problems of YAML but XML is no silver bullet either. I only use the PHP registering version when creating a registration factory. One concern I have (which is just a thought), is XML/YAML used be anyone because they can't use PHP because of security reasons (analyzing rather then executing)? |
The reasons I use yaml:
|
It should, else this will be a HUGE bc break preventing LOTS of projects to migrate 😆 |
👍 it may even replace all current loaders. (not YAML for extension configs though). Agree with @iltar, YAML has its charm.. but it's also becoming more and more complex compared to how it started. Should we really continue with that approach? About the format.. what about avoiding using array keys at all; return [
param('key', 'value'),
service(Mailer::class)->autowire(),
extension('framework', ['key' => 'value']),
]; I think using arrays for extension config is just fine, this is what |
For the bundle semantic config, keeping the array is fine, as we just need to build an array anyway (this is really a datastructure in this case). |
When Fabien presented Symfony Flex in Berlin at SymfonyCon, it used php configuration, but he also specified that he wasn't sure if he wanted to keep this. Hence I'm not sure whether or not yaml will stay consistent for other configurations. |
Just to add my 2cents:
At first sight, I tended to think it was a pretty hard thing to keep in terms of readability, because one could be totally messing up the whole config just because everything is at the root (and then, no indentation, only a list of stuff to register). But in the end, I find this very useful in technical terms: just analyze each entry of the array. Definition=>service, resource=>imported file, scalar/array => parameter, etc., so it's easier for the container to get everything just by adding So 👍 for this :)
Not sure it's a good option, because it complexifies a lot the implementation of this feature. "framework" would need to be an available function returning an object that would check ALL methods to exist in the configuration definition, etc. I'm 👎 on this part
THIS is the place where your proposal makes sense to me: keep current format AND add helpers, I would be totally on it! Simply import functions and do the stuff. Best part of this RFC to me 👍 For the namespace Symfony\DependencyInjection;
{
final class ConfigHelpers {}
}
function parameter();
function service();
function extension(); Maybe it sounds like a dirty hack, but actually this could work and make things a bit easier. We could even imagine to register these functions in the ConfigHelpers class itself as static calls, and register global functions afterwards, so we could even use |
or just put |
Flex allows to use any format supported by Symfony. And actually, the default format for bundle semantic config is still YAML (and will probably stay this way), and the file containing service definitions is also in YAML currently. |
If we do this, the functions be declared only in the use function Symfony\DependencyInjection\parameter;
use function Symfony\DependencyInjection\service;
use function Symfony\DependencyInjection\extension; because the configuration file is using the global namespace? |
@Pierstoval My comment is precisely telling you that your configuration file does not need to be in the global namespace. Symfony functions MUST be namespaced if we add them. This is not an option to put them in the global namespace (otherwise, we must name them |
Oh yeah I didn't see things like that, I didn't notice the fact that you were talking about the |
autoloader has nothing to do with it. |
About the functions importing: http://php.net/manual/en/language.namespaces.importing.php#language.namespaces.importing.group (PHP 7 🤘)
|
I rather void using that notation ever. I don't mind the imports, my IDE will manage them for me. |
@iltar you are free to use any of the alternatives:
These have no impact on the feature, as the feature would not parse the source of the code anyway, but ask PHP to execute the code (and then, PHP supports all 3 alternatives) |
Does this idea have the same downsides as listed for XML? I think so.
Anyways this could be a nice alternative. I won't use it but I am sure people that are new to PHP, have an IDE and dislike yml will like it. As a rookie I always thought this is already possible using the |
Very interesting idea. Do you have a basic proof of concept or any initial implementation or any code, really, you can share? |
@mnapoli posted a link: https://github.com/mnapoli/fluent-symfony |
It looks good, but how could we support the <?php
defaults()
->private();
return [
//...
]; And in the loader, before loading set files, it sets the real defaults (public by default, etc) |
@Taluu IMO, they should be entries in the array as well, like others (but the internal representation would use different value objects, allowing to distinguish them). The current implementation of these functions are just builders for value objects, which are then processed by the loader. So anything which is not part of the returned array is not available for the loader. |
Hi there, I am so happy to see that you've opened that issue. Hoping it will be widely accepted by the Symfony community. |
Isn't it what Pimple does in the Silex framework, an all code DI for all your services? The framework has been developped primarly by Fabien and so does Pimple. From my experience it worked quite well in the few projects I tried it. |
see #23834 |
…iner (nicolas-grekas) This PR was merged into the 3.4 branch. Discussion ---------- [DI] Add "PHP fluent format" for configuring the container | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #22407 | License | MIT | Doc PR | - This PR allows one to write DI configuration using just PHP, with full IDE auto-completion. Example: ```php namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Foo; return function (ContainerConfigurator $c) { $c->import('basic.php'); $s = $c->services()->defaults() ->public() ->private() ->autoconfigure() ->autowire() ->tag('t', array('a' => 'b')) ->bind(Foo::class, ref('bar')) ->private(); $s->set(Foo::class)->args([ref('bar')])->public(); $s->set('bar', Foo::class)->call('setFoo')->autoconfigure(false); }; ``` Commits ------- 814cc31 [DI] Add "PHP fluent format" for configuring the container
How about adding a new configuration format for the dependency injection container?
TL/DR
Short version: YAML and XML suck in their own ways. We are PHP developers, we know how to write PHP code and we have all the tools we need around PHP.
Let's write configuration in PHP. That doesn't have to be verbose or boring, on the contrary it's possible to improve the developer experience.
A possible solution is detailed at the end. The main idea is to take the best from YAML, XML and PHP and imagine a new and better configuration format.
Intro
The current formats:
To be clear, I'm suggesting adding a 4th format.
3 options is already a lot, adding a 4th is maybe not the best. But I think we can write a new format that is better than these 3 formats altogether. I believe that new format could replace all other 3 eventually.
At first the new format could be tagged as experimental.
Why?
The downsides of the existing formats.
YAML
YAML is easy to read, but:
@service_name
for service references,%param_name%
for parameters%env(...)%
for environment variables@?app.mailer
for optional injection"@=service('mailer_configuration').getMailerMethod()"
decorate
ordecorates
?autowiring_types
do? -> there is no phpdoc for that, you have to go find it in the online documentation)symfony/yaml
. This is also why the XML format is encouraged for open source bundles, leading to 2 different formats being recommended.XML
XML has the benefit of being more strict and benefit from validation/IDE helpers, but:
PHP
By "PHP" I mean the PHP API of the
Container
class, for example as a reminder:This format brings stricter validation and IDE support (autocompletion of the
$container
methods for example, but also static analysis of PHP classes mentioned in the config), but:Take for example this piece of config:
These are the tokens we see:
$container
: this word is noise, we are in the config so we know we are configuring the containerregister
: noise too, the goal of the config is to register stuff, no need to state the obvious'mailer'
, the service name, is not very visibleaddArgument
,addMethodCall
: when configuring a service we don't really want to "add" arguments or method calls, we want to define them, the wordadd
is noise.new Reference(...)
: this is actually a good thing: an explicit PHP object for representing a reference, instead of using a string convention like@logger
in YAML. It's a bit verbose thoughHere is the equivalent config in YAML:
YAML has a lot of problems (no need to list them again), but it gets some things right:
addArgument
andaddMethodCall
are now declarative sections:arguments
,calls
-> just as explicit but also much shorter and clearer to readThe format
Learning from the pros and cons of all those formats, here is how I would sum it up:
::class
allowing to use imported class names)::class
to reference class names (means refactoring in most IDEs)What I mean by that is that this kind of format, for example, is NOT a good solution:
This is a clone of YAML in PHP and it suffers from the same downsides.
arguments
could be mistyped,@logger
is still a magical string, etc.Proof of concept
With the help of others, I've played with a possible solution in https://github.com/mnapoli/fluent-symfony
The main idea behind that is to build definitions through a fluent API, and declare them at the same time in an array.
You will also notice the
create()
function, which is kind of unusual. This is a helper function that returns an object. Its role is to avoid this complexity:create()
is simpler, shorter, and much more explicit. It says an instance ofMailer
will be created for themailer
service: simple.Here is another example with
get()
, which replaces the magic@
to inject another service:Functions are usually viewed as "bad" because they are global: those are helper functions, they do not need to conform to OOP best practices. The only problem with those functions is to namespace them correctly to avoid conflicts with other libraries. Thanks to PHP 5.6 it's not an issue as they can be imported like classes:
Illustrations
A picture is worth a thousand words:
auto-completion on classes or constants:
auto-completion when writing configuration:
real time validation in IDEs:
constant support:
More examples
I will not list all syntax options, you can read all of them here: https://github.com/mnapoli/fluent-symfony#syntax
Below is a compilation of most common use cases, hopefully they illustrate how nice such a format could be in terms of DX:
We could even go further for extensions and define helpers that provide an object-oriented API to configure them. For example with the
framework
extension:But then comes nested entries which may require more thinking :)
Everything at the root
You may notice that services, parameters and extensions are all mixed up at the root of the array. I did it just because it was possible. The function helpers make it possible to mix everything, and I found it made everything simpler.
It's of course just one of the many possibilities.
How to add a 4th format without really adding a 4th format?
A possibility is to build on top of the existing PHP format. That would avoid having to write new loaders (and this kind of hack to support existing PHP config files).
In the existing format, a configuration file expects a
$container
variable to be available, for example:We could keep that and write a helper that would "apply" an array config to the container:
Just an idea for the implementation.
Disclaimer
I would not be honest if I did not mention that most of these suggestions are based on PHP-DI :) In version 3 it supported configuration formats very similar to Symfony (YAML, XML and PHP). Starting from version 4 I dropped all these formats and instead switched to a PHP-oriented format. Since then I'm entirely convinced that that kind of format is much better in terms of developer experience. I would love to see that kind of format land in Symfony.
Conclusion
What do you think?
I want to make clear that all of these are suggestions and that all aspects are up for discussion. In other words, if you disagree with very specific syntax choices, that's fine. I'm mostly interested for now in whether or not such a big change is doable.
The text was updated successfully, but these errors were encountered: