Symfony nested services - symfony

Can Symfony DI config file create a service of services? By that I mean something in the spirit of the following gist :
services:
authenticator:
class: Acme\Foo
arguments: [#foo, #parser.users]
parsers: # not valid, this would not compile
users:
class: Csfd\Parsers\User
messages:
class: Csfd\Parsers\Message
I would like to define all parsers as services, but I also want to have them under a parsers branch, so it's obvious they do not actually represent the (also existing) User or Message entities.
In the example, #parser.users would resolve to instance of Csfd\Parsers\User.

This isn't possible by default. The "problem" lies here: YamlFileLoader.php.
As you can see, it doesn't support nesting and treats first level under "services" as service id.
You could create your own YamlFileLoader which would have support for this and use it in your bundle's extension class.

Related

Public service is handled as private

I have a library used by two Symfony apps, this library defines a set of services that I want to be public (I want to be able to retrieve them directly through the container. When I try to access one service I have this:
The "Library\Service\DerivedServices\OneSpecificImplementation" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.
The problem is that said service is declared public.
Basically there is:
a Library\Service\BaseService class that has two setters for the common dependencies (doctrine and a logger in this snippet);
several derived class (in the Library\Service\DerivedServices namespace), each defines a new service (with its own constructor to handle DI directly).
So here are my services definitions:
# Base: inject common dependencies
Library\Service\BaseService:
abstract: true
calls:
- [setDoctrine, ['#doctrine.orm.entity_manager']]
- [setLogger, ['#Library\Service\Logger']]
# These services are public to be retrieved directly by the container interface
Library\Service\DerivedServices\:
resource: '../vendor/company/library/src/Library/Service/DerivedServices'
public: true
autowire: true
autoconfigure: false
parent: Library\Service\BaseService
Then, the Symfony application retrieves one derived service like:
$this->get('Library\Service\DerivedServices\OneSpecificImplementation');
These didn't make any difference:
I have changed the order of the service definitions
Both apps run Symfony 4.3.3
I think this is something trivial configuration-wise, but I can't pinpoint it (and after 2 hours trying to debug why the framework compiles my service as private, I thought that someone probably had this and could probably help me).
It turns out the order of declaration of the services matter a lot. As I thought so, the problem was configuration-wise.
I had:
Library\Service\BaseService:
...
Library\Service\DerivedServices\:
...
Library\Service\:
resource: '../vendor/company/library/src/Library/Service'
The last instruction redeclared all services as private (by default).
I changed this to:
Library\Service\:
resource: '../vendor/company/library/src/Library/Service'
Library\Service\BaseService:
...
Library\Service\DerivedServices\:
...
This declared all the services private first, then redeclared them with the new declaration: use of parent + public.

Symfony: multiple entity manager (sonata)

I have a hard problem that wants an answear. I am working with Symfony and I installed Sonata to manage the admin area. After I completed to do that, my prompt line give me this error:
This is the error
This is my code:
parameters:
services:
app.security.user_login_form_authenticator:
class: AppBundle\Security\UserLoginFormAuthenticator
autowire: true
app.security.admin_login_form_authenticator:
class: AppBundle\Security\AdminLoginFormAuthenticator
autowire: true
Please, help me.
Autowiring feature is handy, but it has a limitations.
As you say, you have multiple instances of entity manager. So, Symfony doesn't know which of them should be injected into your services. If a service definition were available to change, you would set the autowiring_types parameter to specify a default implementation of dependency. But usualy entity manager services are defined by DoctrineBundle and you can not configure it directly. (As I know, Doctrine configuration doesn't provide options to set up that.)
So, the easiest way is to manually specify the entity manager: just pass a entity manager service ID (doctrine.orm.XXX_entity_manager) to constructor arguments of your services.
services:
app.security.user_login_form_authenticator:
class: AppBundle\Security\UserLoginFormAuthenticator
arguments: [ '#doctrine.orm.XXX_entity_manager' ]
app.security.admin_login_form_authenticator:
class: AppBundle\Security\AdminLoginFormAuthenticator
arguments: [ '#doctrine.orm.YYY_entity_manager' ]
Obviously, if services have other dependendecies, you also need to specify them.

How to extend Doctrine service in Symfony2?

I would like to add some methods to Doctrine service in Symfony2. How can I do that? Should I extend some class and register it as a service? I would prefer the service to remain under "doctrine" name.
You have to extend the Symfony\Bundle\DoctrineBundle\Registry class and add you own methods.
Then all you have to do is define a service in app/config/config.yml for example that is named "doctrine":
// app/config/config.yml
services:
doctrine:
class: MyDoctrineRegistry
arguments: [ #service_container ]
Services defined in config.yml will be loaded last, so it will override default one.
If you want to be more clean, you have to override the service in a DependencyInjection/Extension using:
$container->setAlias('doctrine', 'my_doctrine'); // my_doctrine is the name of you custom doctrine service
In this last case, you have to make sure your bundle is loaded AFTER core bundles.
You could specify a wrapper class for a connection and specify custom methods within it. I used this before to extend Doctrine's DBAL functionality and it worked pretty well for my use case.
Details here:
https://stackoverflow.com/a/8242438/312962

How to access the configuration parameters from the model layer in Symfony 2?

Is there a way to access the configuration parameters in config.yml from the model layer? From the controller I can use $this->container->getParameter('xyz'). But how can it be done from a class in the Model layer?
In symfony2 Entities are designed as POPOs, meaning that they shouldn't really have access to anything outside of their scope.
If you need some config option in one of your entities, consider passing it as a parameter from the controller like so:
$entityName->methodName($param1, $this->container->getParameter('xyz'));
This could (will) break DIC pattern, but you could use a singleton class to "globalize" what you need.
To feed your globals, use bootmethod from Bundle class (where you can access DIC stuff hence configuration).
Or more simple, add a static field to your Entity.
Quick & dirty solution, don't abuse it ;-)
You can use Dependency Injection and add your model to your services.yml file, and like every other service you make you can provide other services as constructor parameters. The only downside is you call $derp = $this->get("your_service_name"); instead of $derp = new Derp();.
For example:
# src/Derp/LolBundle/Resources/config/services.yml
services:
derp:
class: \Derp\LolBundle\Entity\Message
arguments: [#service_container]
#service_container is a service found using php app/console container:debug. It will function identically to $this->container in your controllers and it is provided to the constructor of your class. See here for more information on how to use service containers.
As previously mentioned they are POPOs (Plain Old PHP Objects) and the previous method of dependency injection is poor choice simply because you will have to remember to provide your model entity with the same object every time you use it (which is a hassle) and Symfony2 services are a way to mitigate that pain.

How can I access a service outside of a controller with Symfony2?

I'm building a site that relies quite heavily on a third party API so I thought it would make sense to package up the API wrapper as a service, however I'm starting to find instances where it would be useful to have access to it outside of a controller such as in an entity repository.
Also related to that is it would be useful to be able to get access to config values outside of a controller (again such as in an entity repository).
Can anyone tell me if this is possible and if not is there a suggested approach to doing this kind of thing?
thanks for any help
The Symfony distribution relies heavily on dependency injection. This means that usually, dependencies are injected directly into your object via the constructor, the setters or via other means (like reflection over properties). Your API wrapper service is then a dependency for other objects of your application.
That being said, it would be rather difficult to inject this service in an entity repository's constructor because it already requires some other parameters and I think it would not be possible to inject them because of the way we request the repository for an entity.
What you could do is to create another service which will be responsible of doing the work you were about to do in the entity repository. This way, you will be able to inject the entity manager, which will be used to retrieve the entity repository, you custom service and also another service holding your configuration values (There are other ways to share configuration values).
In my use case, I use a Facebook helper service that wraps Facebook API calls. This service is then injected where I need it. My entity repository is only responsible of doing database calls so it receives only the arguments it needs and not the whole dependency. Thus, it will not receive the helper but rather only the arguments needed to do a request, for example, a Facebook user id. In my opinion, this is the way to do it since I think the entity repository should not have dependencies on such helper objects.
Here a small example using YAML as the configuration:
# app/config/config.yml
services:
yourapp.configuration_container:
class: Application/AcmeBundle/Common/ConfigurationContainer
# You could inject configurations here
yourapp.api_wrapper:
class: Application/AcmeBundle/Service/ApiWrapperService
# Inject other arguments if needed and update constructor in consequence
yourapp.data_access:
class: Application/AcmeBundle/Data/Access/DatabaseAccessService
arguments:
entityManager: "#doctrine.orm.entity_manager"
apiWrapperService: "#yourapp.api_wrapper"
configuration: "#yourapp.configuration_container"
# Application/AcmeBundle/Common/ConfigurationContainer.php
public ConfigurationContainer
{
public function __construct()
{
// Initialize your configuration values or inject them in the constructor
}
}
# Application/AcmeBundle/Service/ApiWrapperService.php
public ApiWrapperService
{
public function __construct()
{
// Do some stuff
}
}
# Application/AcmeBundle/Data/Access/DatabaseAccessService.php
public DatabaseAccessService
{
public function __construct(EntityManager $entityManager, ApiWrapperService $apiWrapperService, ConfigurationContainer $configuration)
{
...
}
}
The at sign (#) in the config.yml file means that Symfony should inject another service ,having the id defined after the at sign, and not a simple string. For the configuration values, as I said previously, there is other means to achieve the same goal like using parameters or a bundle extension. With a bundle extension, you could define the configuration values directly into the config.yml and your bundle would read them.
In conclusion, this should give you the general idea of injecting services. Here a small list of documentation on the subject. Alot of links use the XML service definition instead of the YAML definition but you should be able to understand them quite easily.
Symfony Official DI
Fabien Potencier's articles on DI
Richard Miller's articles on DI (Check in his blog for the other DI articles)
Take note that the configuration I'm giving is working for Beta1 of Symfony2. I didn't update yet to Beta2 so there could be some stuff not working as they are in the Beta2 version.
I hope this will help you defining a final solution to your problem. Don't hesitate to ask other questions if you want clarifications or anything else.
Regards,
Matt
I would wrap this kind of behavior in a Symfony service(like a manager).
i would not inject any parameters or logic into the entity repositories, as they should mainly be used to fetch data using object manager queries.
I would put the logic in the services and if the service , require a database access it will call the entity repository to fetch data.

Resources