Doctrine DQL with interface - symfony

I have an entity in one of my portable bundles with MappedSuperClass set. I extend it to the real entity for each separate project / website and implement the additional properties in a main project bundle. I'm using the interface and the ResolveTargetEntities to relate the entities using the interfaces. This part works flawlessly so far.
The problem comes with my DQL queries, which need to address that particular implemented entity class, the one that extends the mapped superclass. If I use the interface or the mapped superclass, I get the Symfony errors. But I don't want to use my implementation of the superclass name because that would mean I have to change my portable bundle's repository code for each project, which is unacceptable.
One of the ideas was to get the orm mappings from config in the repository and inject the correct class there. But I can't find the way to extract that info from the config file.
The other, better, one was to create some sort of a listener, which would replace the interface with the real thing for me.
So my questions: Does anyone know if this is the usual behavior for the DQL not to be resolved? Does anyone have any ideas how I would implement the code to achieve the resolution by myself?
Any info is appreciated.

I assume you have your concrete class set as a parameter that you are accessing later in your parent bundle. If so you can create a repository for that class (half copied from Sylius).
parameters:
acme.repository.something.class: Doctrine\ORM\EntityRepository
// Or your custom repository
services:
acme.manager.something:
alias: doctrine.orm.entity_manager
acme.metadata.something:
class: Doctrine\ORM\Mapping\ClassMetadata
factory_service: acme.manager.something
factory_method: getClassMetadata
arguments:
- %acme.model.something.class%
acme.repository.something:
class: %acme.repository.something.class%
arguments:
- #acme.manager.something
- #acme.metadata.something
Then you can just call the repository using $this->container->get('acme.repository.something)`

Andrej, I had a similiar situation. You can make a setter method in your custom repository that sets the class name to be used in the dql. You can then have that setter method called with the call tag in your service declaration.
This is what I did in my post repo service declaration. I hope it helps. Good luck.
<service id="cfs_blog.post_repository"
class="%cfs_blog.post_repository.class%"
factory-service="doctrine.orm.entity_manager"
factory-method="getRepository">
<argument>%cfs_blog.post.class%</argument>
<call method="setUserEntityClass">
<argument>%user_class%</argument>
</call>
</service>

Related

Symfony 4 setter injection not working in bundle with or without autowire

I thought I fully understood Symfony's autowiring behaviour, but there must be something I'm missing and I'm hoping someone can fill in the blanks.
Three questions to begin with:
Is there a difference in the Symfony autowire behaviour in the main application vs in a bundle?
If you ask Symfony to autowire bundle services, does it completely ignore service definitions in the bundles services.yaml file?
Are there known issues with the setter injection functionality in Symfony 4?
I have a service definition within a bundle that uses setter injection but it seems to be completely ignored by Symfony, when I've asked Symfony to autowire my bundle services and even when I've asked Symfony to exclude services from the autowiring.
My application is using Symfony v4.1.3.
I have included my Bundle in my applications bundles.php file.
<?php
return [
//... core bundles,
Acme\\Symfony\\AcmeCustomBundle\\AcmeCustomBundle::class => ['all' => true]
];
In the default Symfony application services.yaml file, I have asked Symfony to autowire my bundles services with the following:
Acme\Symfony\AcmeCustomBundle\:
resource: '../vendor/acme-symfony/custom-bundle/*'
exclude: '../vendor/acme-symfony/custom-bundle/{Model,Tests}'
In my bundles services.yaml file located in ../vendor/acme-symfony/custom-bundle/Resources/config/services.yaml, I have the following:
parameters:
services:
Acme\Symfony\AcmeCustomBundle\Search\ConfigurationReader:
calls:
- method: setIndex
arguments:
$index: '%elasticsearch.index%'
- method: setSchema
arguments:
$schema: '%elasticsearch.schema%'
The parameters are set in my bundle extension class (extends configurable extension), and I have already verified that the parameters do exist and are getting set using the following method:
$container->setParameter('elasticsearch.index', $mergedConfigs['elasticsearch']['index']);
$container->setParameter('elasticsearch.schema', $mergedConfigs['elasticsearch']['schema']);
Now back to the problem. Symfony is not performing the setter injection, even when I tell Symfony to not autowire the above service by doing the following:
Acme\Symfony\AcmeCustomBundle\:
resource: '../vendor/acme-symfony/custom-bundle/*'
exclude: '../vendor/acme-symfony/custom-bundle/{Model,Tests,Search}'
I did however get Symfony to configure my service when I
Turned off the autowiring for the above service
Used the factory method of service creation
This kind of answered my second question above, but I'm not 100% sure of it. Nonetheless, I would much rather not have to use a factory class just to get around what may be an issue with Symfony, or my lack of understanding of how the setter injection / autowiring really work.
Can anyone see something obvious that I'm missing?
You can autowire other methods (e.g. Setters) if you want, just by using the #required annotation in your service:
/**
* #required
*/
public function setFoo(FooInterface $foo)
{
$this->foo = $foo;
}
Autowiring other methods
First, autowiring in bundles that are meant to be shared is not part of the best practices. You must explicitly define the services of a bundle you are going to share, and doing it in xml.
Now, that being said, what autowire does is that wires up the services in the constructor of a class, trying to autoresolve them. It does not perform setter injection.
What you need is to do is to configure an auto-cofiguration entry. In your bundle extension class, you can do:
$container->registerForAutoconfiguration(ConfigurationReader::class)
->addMethodCall('setIndex', ['%elasticsearch.index%'])
->addMethodCall('setSchema', ['%elasticsearch.schema%']);
Now, this is really meant to be used with interfaces or abstract classes, not really with concrete implementations. But is what you can do if you want to autoconfigure your service.
My suggestion is, if you are going to reuse this bundle, to define the services explicitly and in xml. Autowiring is for your business logic and services.

Where to store various configuration parameters in Symfony 3.4

Is there best practice to store various config parameters like length of zip code, minimum length of the last name and so on?
I would like something like a php class with static functions and properties, which I can use at any place of my project.
Your looking for parameter service.
In just released Symfony 4.1 by default: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service
In older Symfony with package like
https://github.com/Symplify/PackageBuilder/blob/master/README.md#2-all-parameters-available-in-a-service
or own implementation. It's simple :)
In the symfony best pracitces they suggest to use parameters in services.yml that changing, if you will never change this parameter put in Entity as const or in some abstract class that you can create on by yourself.
Documentation about it:
https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
For the 3.* branch, I find that the services.yml file is the best place to do so. You can then inject those values into the services that need it, or even access them in your controllers with
$this->getParameter('param_name')
More info on this: see Service Parameters
As other answers point, you can store parameters using the parameters.yml file.
But, for me, you are asking for limitations on entity properties. If you are using Doctrine, you can use annotations for this purpose like described in docs: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/annotations-reference.html

How does Symfony know where to find the services.yml config file?

In Symfony 3.0 (and I'm sure 2.X as well), if I want to make a custom constraint validator with a dependency, I have to register that validator as a service in the dependency injection container (which is described by default in project_directory/app/config/services.yml) using a special tag ( as described here).
This means that the Validator component must know where to look for the service container. This issue also comes up for the ControllerResolver. Since controllers can be registered as services, the ControllerResolver must know where the service container is.
How do the Symfony components know where to look for the service container, and how can I configure this? I ask because I want to build a custom framework using the Symfony components, which means I'll be making my own service container, and I'd like to be able to point the Validator and the ControllerResolver to that service container.
In Symfony Standard Edition, the container is initialised by the Kernel. Have a look at the AppKernel and its parent class.
The kernel loads a configuration file in the registerContainerConfiguration() method:
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
}
This code will load an environment specific configuration file (config_prod.yml, config_dev.yml etc). In a standard setup that file imports the main config.yml file.
The services.yml file is loaded with an import in config.yml:
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
If you're thinking of building your own framework (which is a good learning experience), you'll need to read more code of existing frameworks first. Also, it's worth to read the excellent Create your own PHP framework.
This means that the Validator component must know where to look for the service container
This is scary. I see no connection between validation and the service container. I'd think of a better design.
P.S. Everyone should write their own framework once. The next step should be deleting it.

Silex - real DI vs injecting $app which looks like ServiceLocator?

I read the article yesterday: https://igor.io/2012/11/09/scaling-silex.html
And another one http://davedevelopment.co.uk/2012/10/03/Silex-Controllers-As-Services.html
So a conceptual question rised in my head:
Currently I do have a lot of controllers in separate classes. I overwrite controller_resolver to create a controller class instance and inject $app into contoller's constructor.
I define routes like this $app->get('/hello', 'HelloController::indexAction') <- my controller resolver will create new HelloController($app); - so far so good.
But to be honest it became a ServiceLocator pattern, not a DependencyInjection, because I do inject whe whole $app which looks like ServiceLocator usage.
Now I am in doubt: should I leave it as is (because it works well) or try "controllers as services" to inject only those sevices on which my controller really depends on? May be my SeviceLocator approach will hit me some day? (people say that DI is better for tests).
I have also looked into Symfony Framework Bundle: class Controller extends abstract class ContainerAware which also have the whole $container injected! ServiceLocator approach in full stack framework?
Any recomendation? Pros/cons?
The symfony2 full-stack framework
The framework uses the Dependency Injection pattern and not the Service Locator pattern.
All controllers aren't services by default. The ContainerAware class includes methods to get access to the service container, so you can call Services inside the Controller.
If you are going to use a Controller as a Service you need to remove the Controller extend. All dependencies you want to use inside the controller needs to be injected by the Service Container.
Read more about this in a blogpost by richard miller, one of the core contributors of Symfony2.
The Silex micro-framework
The Silex micro-framework provides the bare bones of a framework, it's up to you how the architecture looks and which patterns you use.
The Silex documentation uses Controllers that aren't Services. It injects the complete Service Container inside very Controller:
$app->post('/post/{id}-{slug}', function($id, $slug) use ($app) {
// ...
});
If you want to use controllers as service, you should only inject the services you want to use inside the controller.
EDIT: The Controller::action syntax refers also to a Controller that isn't a Service. The Controller:action notation is used to refer to Controllers as Services.
There's lot's of personal preference involved here. What you've done already is a good (enough) step to organising your code base. Some people like myself take things a step further and move our controllers to the container, rather than injecting it in to some kind of BaseController. This happens in both Silex and the full stack Symfony framework.
My advice would be to leave everything you have as is, then try defining your next controller as a service, by practising BDD.
For example, the behaviour of a UserController view action:
It should retrieve the user from a database
It should render the user with a template
Not once does it mention retrieving the database or the template renderer from a container. I'm comfortable not injecting the container, so I'll only inject what I'm led to believe I need by BDD.

Doctrine 2 with Symfony DI Container

I am working on a Zend Framework project that leverages Doctrine 2. I'm trying to get Symfony's DI container working with the project as well, but I'm having trouble. Suppose I have a class "Foo" that needs two instances injected. I can set up a Symfony DI container no problem to fetch me a new "Foo" with the dependencies injected. Great! But now what if I want to make "Foo" a Doctrine entity? All is well when I insert the entity to the DB because I can grab a new one from the DI container and simply persist the entity. However, when I query the entity from the DB Doctrine is instantiating my "Foo" instances and they will not have the proper dependencies injected. How do I get Doctrine to use my DI container so that the entity will have the appropriate dependencies? I know that Doctrine offers a "postLoad" hook in that I could use to inject dependencies on my entity, but that kind of defeats the purpose and benefit of the DI container.
A Doctrine Entity is a newable, not an injectable. Entities are not supposed to be created through a DIC. See this following blog post on the difference between newable and injectable:
http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/
Its a bit tricky, but it can be done. There exists a patch for symfony di container which allows you to pass a already existing object to the container builder instance and let the di container configure it for you (e. g. inject dependencies based on interfaces). The patch is implemented in this repository on github: https://github.com/lstrojny/symfony but didn´t make it upstream to symfony master repository.
You use it like this:
$user = new User();
$container->configure('someId', $user);
Then you could register a PostLoad event handler with Doctrine´s EventManager (see here for more details: http://www.doctrine-project.org/docs/orm/2.0/en/reference/events.html ). In this event handler you configure the loaded entity via the aforementioned method. It´s obvious but you cant use constructor injection in this case, only setter.
This is a bit tricky to set up, but can be very powerful especially in conjunction with the ability of symfony di container to inject dependencies based on interfaces.

Resources