symfony2 factory-service in yaml - symfony

Looking at the code of the Sylius Bundle for Symfony I noticed the Resource Bundle has an interesting way of defining resource controllers as services.
Here is the cart item controller service configuration in XML
<service id="sylius.controller.cart_item" class="%sylius.controller.cart_item.class%">
<argument type="service">
<service factory-service="sylius.controller.configuration_factory" factory-method="createConfiguration" class="Sylius\Bundle\ResourceBundle\Controller\Configuration">
<argument>sylius</argument>
<argument>cart_item</argument>
<argument>SyliusCartBundle:CartItem</argument>
</service>
</argument>
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
If I understand it correctly this code instantiates the controller class and passes as the constructor argument the result of a call to the factory-method "createConfiguration" in the factory-service class. Arguments are specified, so everything is fine.
My question is twofold:
1) Where is this documented? I could not find one example of this kind of arguments-as-a factory-callable in the docs.
2) What would be the YAML version of this?
Thanks...

Here is the way:
<service id="sylius.controller.cart_item" class="%sylius.controller.cart_item.class%">
<argument type="service">
<service factory-service="sylius.controller.configuration_factory" factory-method="createConfiguration" class="Sylius\Bundle\ResourceBundle\Controller\Configuration">
<argument>sylius</argument>
<argument>cart_item</argument>
<argument>SyliusCartBundle:CartItem</argument>
</service>
</argument>
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
Can be written as the following in yml
sylius.controller.cart_item:
class: %sylius.controller.cart_item.class%
arguments:
- "#=service('sylius.controller.configuration_factory').createConfiguration('sylius', 'cart_item', 'SyliusCartBundle:CartItem')"
calls:
- [setContainer, ["#service_container"]]

You can find the answer to both of your questions in the dependency injection docs.
As far as defining a service nested under another service in YAML, it doesn't seem the parser that ships with Symfony can handle that, but I did find someone's pet project that seems to aim for this functionality: https://gist.github.com/Mikulas/8004470

I was trying to override the CartItemController and came across this, because I thought i needed to do it this way. But it's not the way to go. Anyways, to answer your question. Here is how the xml transforms into yaml
(because the solution suggested by Alexei Tenitski didn't work for me, I did it like so)
sylius.controller.cart_item:
class: Sylius\Bundle\ResourceBundle\Controller\ResourceController
arguments: ["#sylius.cart_item.config_factory"]
calls:
- [setContainer, ["#service_container"]]
sylius.cart_item.config_factory:
class: Sylius\Bundle\ResourceBundle\Controller\Configuration
factory_class: Sylius\Bundle\ResourceBundle\Controller\ConfigurationFactory
factory_method: createConfiguration
arguments: ["sylius", "cart_item", "SyliusCartBundle:CartItem"]
But I'm guessing you were trying to override the CartItem controller, right? :) that's what I was trying to do anyways.
In the Sylius Docs is explained how you would go about doing that. Like this :
location : yourbundle/resources/config/config.yml
sylius_cart:
classes:
item:
controller: YourBundle\Controller\CartItemController
Also, make sure that if you configure the route to your new controller action, you use the controller service instead of the normal approach.
location : yourbundle/resources/config/routing.yml
mybundle_ajaxcart_add:
path: /ajax/cart/add
defaults: { _controller: sylius.controller.cart_item:addAjaxAction }
I wanted to post it here, because I was looking for this for about half a day and probably someone is going to be looking for the same solution. And I like to save that person the headache ;)

Related

Explain These Services.xml File Terms In Shopware 6

What does argument and argument type mean,
What does tag and tag name mean,
in Shopware 6 plugin services.xml file?
<?xml version="1.0" ?>
<services>
<service id="Winner\Service\ExampleService" />
<service id="Winner\Service\ExampleServiceDecorator" decorates="Winner\Service\ExampleService">
<tag name="kernel.event_listener" event="product.loaded" />
<argument type="service" id="Winner\Service\ExampleServiceDecorator.inner" />
</service>
</services>
The services.xml file is the configuration file for the symfony dependency injection container.
By default, Symfony allows autowiring of the config, but the Shopware default is to configure the DI container manually. Please refer to the Symfony docs for more information on manually configuring the DI.
And the Symfony docs also have more information on service tags.
So technically this is not a Shopware specific question, but rather a Symfony question. I hope my answer and the provided docs help you.

Mixing positional and keyed arguments in service definition, using YAML

When configuring a service using XML, we can do the following:
<service id="foobar" class="App\Foobar" public="false" abstract="true">
<argument type="service" id="doctrine" />
<argument>null</argument>
<argument type="service" id="logger" on-invalid="ignore" />
<argument key="$bombastic" type="service"
id="bombastic.service" on-invalid="ignore" />
</service>
The first three arguments are positional (the first three arguments in the constructor), and the last one is keyed to the parameter name. Since the actual service has 5 arguments, the fourth argument is left undefined so it can be defined by a service that extends the foobar service.
Which is very nice.
In YAML the documentation shows how to use keyed arguments like this:
App\Updates\SiteUpdateManager:
arguments:
$someService: '#manager'
and positional arguments like this:
App\Updates\SiteUpdateManager:
arguments:
- '#manager'
But I'd like to do the same as the above XML configuration, but using YAML (because all the service configuration for this application is already in YAML, and I would not want to add a single XML configuration file just for this service).
How can I combine the two styles with YAML configuration?
Try combining indexed arguments with keyed ones, e.g:
App\Updates\SiteUpdateManager:
arguments:
0: '#doctrine'
1: null
2: '#?logger'
$bombastic: '#?bombastic.service'

Extends a KnpMenu from a Symfony Bundle

I am using Sylius as a shop Symfony bundle, and I would like to extend the KnpMenu used in "/admin" path of this bundle.
In Sylius, the menu is made from a service :
<service id="sylius.menu_builder.admin.main" class="Sylius\Bundle\AdminBundle\Menu\MainMenuBuilder"
parent="sylius.menu_builder" public="false">
</service>
<service id="sylius.menu.admin.main" class="Knp\Menu\MenuItem">
<factory service="sylius.menu_builder.admin.main" method="createMenu" />
<tag name="knp_menu.menu" alias="sylius.admin.main" />
</service>
Is there a way to add an entry in this menu from my own Bundle ?
Thanks for your help !
Yes, you have to create MenuListener, add child elements in there, and register it as a service. You have it explained in the documentation: http://docs.sylius.org/en/latest/customization/menu.html
Good luck!

how to user inject a different user manager to sonata user bundle

I try to use sonata admin bundle and sonata bundle. I have created the ApplicationSontaUserBundle and everything works fine if I use fosUserInterface. But i have another user Bundle, named Sso for example. So in my app i need to use Sso userManger instead of FosUserManager. The sonata user bundle contains a "admin_orm.xml", and in this services configuration file, I have this line in file:(in sonata user bundle)
<service id="sonata.user.admin.user" class="%sonata.user.admin.user.class%">
<tag name="sonata.admin" manager_type="orm" label="users" label_catalogue="SonataUserBundle" label_translator_strategy="sonata.admin.label.strategy.underscore" />
<argument />
<argument>%sonata.user.admin.user.entity%</argument>
<argument>%sonata.user.admin.user.controller%</argument>
<call method="setUserManager">
<argument type="service" id="fos_user.user_manager" />
</call>
<call method="setTranslationDomain">
<argument>%sonata.user.admin.user.translation_domain%</argument>
</call>
</service>
And in Application/Sonata/UserBundle/Resources/config/admin_orm.xml, I have this one:
<call method="setUserManager">
<argument type="service" id="sso.user_manager" />
</call>
Because i defined another userAdmin in Application/Sonata/Admin/Model/UserAdmin.php
use xxx\SsoBundle\Entity\UserManager;
....
/**
* #param UserManager $userManager
*/
public function setUserManager(UserManager $userManager)
{
$this->userManager = $userManager;
}
Instead of the original FosUserInterface. But, it never take my settings in
Application/Sonata/UserBundle/Resources/config/admin_orm.xml
It always complain the user manager is a FosUserInterface object and I need my own user manager. If i change the
/vendor/sonata-project/user-bundle/Sonata/UserBundle/Resources/config/admin_orm.xml
and set my own user manager, everything works fine. Anyone can tell me how to override this file?
well, after 2 years, I found the solution. And it is so stupid.
I need to define the dependencyInjection class to load the xml and the service will be loaded. The child bundle's configuration file will not be loaded if u do not write the dependencyInjection class. Just as a normal bundle. The configuration file can not be "overwrite", every bundle's configuration file will be red and the last one will win.

How to programically inject a dependency to N classes in Symfony2? Can I somehow inherit injected services?

In my app I am generating n number of classes. They all have the same skeleton and serve a similar purpose. They also share dependencies.
Instead of adding n entries in services.xml like so:
<service id="acme.security.first_voter" class="Acme\SecurityBundle\Security\Authorization\Voter\FirstVoter" public="false">
<tag name="security.voter" />
<argument type="service" id="logger" />
</service>
<service id="acme.security.second_voter" class="Acme\SecurityBundle\Security\Authorization\Voter\SecondVoter" public="false">
<tag name="security.voter" />
<argument type="service" id="logger" />
</service>
I'd like to simply add one entry like this:
<service id="acme.security.base_voter" class="Acme\SecurityBundle\Security\Authorization\Voter\BaseVoter" public="false">
<tag name="security.voter" />
<argument type="service" id="logger" />
</service>
and in each Voter simply add
use Acme\SecurityBundle\Security\Authorization\Voter\BaseVoter;
class FirstVoter extends BaseVoter
But that does not work.
I've seen Managing Common Dependencies with Parent Services, but it does not solve my issue, becouse it requires I add a
<service id="acme.security.first_voter" class="Acme\SecurityBundle\Security\Authorization\Voter\FirstVoter" parent="base_voter"/>
<service id="acme.security.second_voter" class="Acme\SecurityBundle\Security\Authorization\Voter\SecondVoter" parent="base_voter"/>
for each voter... but thats exacly what I'm trying to avoid, becouse n can be 5 or.. 500.
I've read some old Richard Miller blog posts about injecting a dependency into an interface, and all classes implementing that interface would "inherit injected dependencies" (also be injected that service). Thats exacly what I need! Unfortunately, this has been dropped for some reason and it does not work for Symfony2.3.
Is there any solution to my problem?
You can well use parent services for this purpose.
You just have to register them all using a CompilerPass instead of adding each one manually.
Use the Finder component to search all bundle's i.e. Voter folder for classes extending your base voter - then register them in the CompilerPass.
Improve by caching your results for performance reasons :)
Or you use JMSDiExtraBundle
use JMS\DiExtraBundle\Annotation\Service;
/**
* #Service("some.service.id", parent="another.service.id", public=false)
*/
class Voter extends BaseVoter
{
}
It basically does exactly that ( using a compilerpass ).

Resources