How is symfony normalizer instantiated? - symfony

I'm looking at a symfony project that is declaring a custom normalizer. It has an optional argument in the constructor which is getting ignored.
public function __construct(SomeInterface $firstArg, $secondArg = false)
{
//$secondArg is always false
}
There is some yml config:
Path/To/Custom/Normalizer:
arguments:
$secondArg: true
tags: [serializer.normalizer]
I'm trying to understand what is instantiating this class, and why is the second argument always false, despite the fact that the yml config defines it as true.
Is the normalizer instantiated using the yml config, or does symfony instantiate these using some other mechanism?
More info:
if i make the second constructor arg mandatory, then the container wont compile. complaining that it can't autowire the second arg and that i must configure its value explicitly. Which is what i am trying to do.

Autowiring of symfony doesn't know how to autowire scalar parameters type.
Your manual configuration will never be execute if you enable autowiring for this service.. So the container tries to create new instance of your service but he doesn't know how to instantiate the boolean parameters.
What you can to is to exclude this service from the autowiring
It is a bad idea and can become quickly a very big headache to have different default value on constructor and the service configuration itself.

The order in which the bundles are loaded, defined in AppKernel.php makes a difference to the configuration.
Even though there does not seem to be any other configuration of this class in any other bundle.

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.

Access Config Parameter without Container

What is the recommended way of accessing the global config parameters? I know you should inject just what you need, but the whole point of global parameters (in most cases) is to set application defaults. And I WANT the defaults to be universal, even if I allow passing in overrides on a function by function basis.
THE PROBLEM
I have a ViewVersion entity with a ViewVersionRepository where I have common functions for interacting with that entity.
One of these functions requires that I know a certain parameter from the config.yml (e.g. cms.save.newVersionSeconds so I know when it's been more than 30 minutes since the last save and I should create a new version record). The entity repository is not container aware though, so I can't access the config with the normal method:
$myParam = $container->getParameter('my.custom.param');
Inject From Controller
If I call this function from a controller, no problem I can get the config in the controller and inject the parameter into the repo function.
$myParam = $container->getParameter('my.custom.param');
$viewVersionRepository->myFunction($myParam)
Global Service
Or I can configure the the repo as a service:
gutensite_cms.view_version_repository:
class: Gutensite\CmsBundle\Entity\View\ViewVersionRepository
factory_service: 'doctrine.orm.cms_entity_manager'
factory_method: 'getRepository'
calls:
- [setLimit, ['%cms.limit.list.admin%']]
- [setNewVersionSeconds, ['%cms.save.newVersionSeconds%']]
And then set the parameter with setter injection via the calls arguments in the services.yml. And then load the service:
$viewVersionRepository = $this->container->get('gutensite_cms.view_version_repository');
Complication
However... I have a situation where I'm loading this viewVersionRepository within a doctrine onFlush event listener. And there I don't have access to the container (natively), so I can't actually load the global service. I have access to the entity though so I was loading the repo via:
$repo = $em->getRepository('GutensiteCmsBundle:View\ViewVersion);
But when you load it that way, it's not loaded as a service so the setter injection doesn't happen...
ONE SOLUTION
So I could pass the container into the global onFlush event listener service, so that I can load the ViewVersionRepository as a service (and for that matter have the container to the load the parameters if I wanted).
gutensite_cms.listener.is_versionable:
class: Gutensite\CmsBundle\EventListener\IsVersionableListener
#only pass in the services we need
arguments: [ "#gutensite_cms.entity_helper" ]
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: doctrine.event_listener, event: onFlush }
But I'm told over and over and over again* to **ONLY inject the parameters you need. And to avoid injecting the entire container. This onFlush event is really quite dynamic, loading the corresponding onFlush event in any entity's repository that has an onFlush method. So in one entity repository the custom function may need access to one parameter, but in another entity repository the may need access another. So I can't really pass in just one parameter.
And it seems to me that passing the entire container makes your application far more coupled than if you were to just used the vilified $GLOBALS or CONSTANT for your global default config. So surely there is a better way to avoid both these evils. Not to mention you should NEVER inject the container into an entity repository.
So isn't there some way to access the global config parameters WITHOUT injecting the entire blessed container? Does no one EVER need global config parameters in an entity repository? Sure I could move this function to another service... but why? This is one of the basic functions of this entity, e.g. it determines if it should be versioned or not.

Symfony nested services

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.

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.

Resources