How can I get the Doctrine entity metadata before compiling Symfony container? - symfony

In one Symfony bundle I define a compiler pass to preprocess some configuration. Part of that config is based on Doctrine entities, so I need to get the full metadata information for all application entities.
The compiler pass is executed very late (PassConfig::TYPE_BEFORE_REMOVING). I'm using $container->get('doctrine') like this to get the entity metadata:
$em = $container->get('doctrine')->getManagerForClass($entityClass);
$entityMetadata = $em->getMetadataFactory()->getMetadataFor($entityClass);
However, this is causing random failures for some users because of the use of the doctrine service during the Symfony container compilation.

I'd recommend to change your entities addressing. Mainly - create your models with interfaces and make entities implementing them.
Using resolve_target_entities Doctrine will "convert" them to the particular classes.
An example code is here: https://github.com/Sylius/SyliusResourceBundle/blob/master/DependencyInjection/Compiler/DoctrineTargetEntitiesResolverPass.php
Just make sure your bundle is registered before DoctrineBundle is registered.
Then - in your whole app - instead of AppBundle::Entity addressing, use FQDN of interface bound to an entity earlier.
I've experimented a bit with compilers and services and it's a very bad idea to base on cross-bundle services under compiling container process... Why? It's not reliable - sometimes it will work as you want, sometimes it will fail as you described.

Thank you all for your comments and ideas. I post an answer to explain how I solved this problem.
Trying to use the Doctrine service in a compiler pass was creating more and more problems for our users (and it was creating other minor issues with other services such as Twig). So this was definitely a bad solution for our needs.
So at the end I decided to change everything. We no longer use a compiler pass to process the configuration and instead we use a regular PHP class called during runtime.
In the dev environment, the configuration is processed for each request. It's a bit slower than before, but we prevent any caching issue. In the prod environment we use Doctrine Cache to process the configuration once. Besides, we've create a cache warmer to create the cached configuration before the first request hits the application.

Related

Symfony 4 and Microservices

Say I'm going to create few microservices: Alpha, Beta, Gamma.
In terms of Application structure using older Symfony version like 2, I'd create a bundle for each service, but bundles are no longer recommended in Symfony 4. So... Should I create separate repositories for every service or still create a bundles in a one App?
If you have different microservices, as in different applications, you will not need bundles. You can keep them in different repositories, but a common practice is to use a so called mono-repository. As the name suggests, with a mono-repository you keep all of the projects in a single repository. This has the benefit that changes spanning all projects can be done more easily and in sync. The drawback is that it requires more effort when managing and might cause additional overhead when building and deploying as it will not be easy to see which service has changed so must likely you rebuild all of them. There are a few books and presentations on mono-repositories you might want to check out. In short, Symfony does not restrict how you manage your services. You can have a single repository for all projects or multiple repositories.
If you want to serve all "services" through the same application, even without bundles, you can do so by using namespaces to separate the logic, e.g. for controllers:
my_app
- src
- Controller
- Alpha
- IndexController
- Beta
- IndexController
This should work out of the Box with the default configuration and even if you deviate you can make things like argument resolvers work by just pointing the configuration to the correct folder. Obviously this will require you to make sure that code is not shared between services should you ever want to extract them into their own application. There are some static code analyis tools that help you with keeping your architecture clean, i.e. make sure Alpha does not use code from Gamma and vice versa.
If you want to separate the apps more clearly by doing something like this:
my_app
- src
- AlphaApp
- ...
- BetaApp
- ...
You can still do that but it will require more manual work and the recipes will not work anymore, requiring you to do manual changes to most configurations and moving around files. How to do it depends on whether you want a shared kernel or a separate kernel for each service, but if you go that route I recommend keeping separate projects in the same repository, as it will probably yield cleaner results and be less work.
You can still create bundles in symfony4 though its not recommended by best practices. see https://symfony.com/doc/current/best_practices/creating-the-project.html

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

When use Extension class, when use Compiler Passes in Symfony?

What is the point of using compiler passes in Symfony?
When we should use Extension class and when Compiler Passes in Symfony?
They come with services definition.
By creating a compiler pass, you are able to update the arguments passed to services.
It's most often done with tagged services.
Also, It can be used for :
Creating new services that require information about other defined services before being defined.
Swapping or adding arguments to a service that you did not write.
Creating and modifying parameters in the container.
I used a compiler pass to register a Factory that make me able to override the doctrine Repository.
You can see the code for more comprehension of how it works:
https://gist.github.com/chalasr/77be8eee5e3ecd3c06ec
Update
Thank's to #Sruj, I added the part I've forgotten about the Extension
Extension are part of dependency injection too, especially of the configuration.
Its primary role is to load the configuration of services across the bundles of your application.
Instead of load your configuration manually by using imports, you can create an extension that does it for you. All your services configuration are registered from your bundle and shared in your whole application.
When you register a vendor in your app configuration, the service container extension of the vendor is invoked.
See "Importing configuration via container extensions" part of the documentation

Namespace alias for Doctrine

I became responsible for a large legacy web application and I am trying to slowly refactor it into the Symfony2 framework. The first thing I have done is to include Doctrine.
I have installed Doctrine with the help of Composer and set up a bootstrap file for it. My entities, to avoid future complications, already follow the namespacing scheme Company\BundleName\Entity\Object. The following works:
$em->getRepository('Company\\BundleName\\Entity\\Object')
->find($id)
;
I was unable to find any reference of how to inform Doctrine of namespace aliases as Symfony2 does, so I can write
$em->getRepository('CompanyBundleName:Object')
->find($id)
;
instead. How can I achieve that?
There is an easier way now:
$config = Setup::createAnnotationMetadataConfiguration(...);
$config->addEntityNamespace('CompanyBundleName', 'Company\BundleName\Entity');
will do what you want. It took me several hours hunting to find this! Its not in the docs anywhere I could find.
The functionality for this is set up in Symfony2 by the DoctrineBridge bundle, specifically the getMappingDriverBundleConfigDefaults function.
If you want to reflect this functionality without Symfony2, you'll need to extend the Doctrine entity manager and generate the prefix yourself in the getRepository function. It is not part of the Doctrine system.

Symfony2 where to place custom helper classes

I'm starting with a Symfony2 project. I know the framework basics but I have a question:
Where is the right place to pot those helper classes I create for help or for the business logic?
Max's answer is correct. However I question the path he recommends for your code.
The following classes and files have specific emplacements:
Service Container Extensions (belong in) DependencyInjection/
from http://symfony.com/doc/current/cookbook/bundles/best_practices.html
That says your Services should be placed in a folder called 'DependencyInjection', not 'Services'. In full, it should be src/Foo/BarBundle/DependencyInjection
I say this as someone that had the former and has just finished moving them all to the latter (!)
What #Adam says is wrong, you have to store your Dependency Injection Extensions in DependecyInjection directory, not the services itself. In the documentation says that you can store your (custom) business logic classes in any place you like.
http://symfony.com/doc/current/best_practices/business-logic.html
The best way to keep the business logic is create service to handle all the logic. So it will be in:
src/Foo/BarBundle/Service
and you need to call the service in the services.yml.
I recently did some small work on an existing Symfony2 project. As described by answer from Tuong Le, I created my Helper classes under the Helper directory of the bundle and class name with Helper suffix i.e. the helper class is located at:
src/MyBundle/Helper/MyUtilHelper.php
I can use MyUtilHelper class in my bundle without calling the service container i.e. I didn't need to call.
$container->get('my_util');
I don't really know whether there is some special config. in my setup; someone already got it setup and I was just adding new functionality.
You can create the custom classes under your Bundle, such as under a folder Helper/..
However, to use those helper in your code, you'll need to define those Helper(s) in your service description file (such as services.xml)... Then you can use $container->get('your_helper')->
According to official documentation - in particular - Symfony Best Practices - you should store your services in Utils folder under the src. I belive, that this is correct way regardless of whether you want or don't wont to make the functionality provided by services of your bundle available to other parts of application via Service Container. Furthermore, you can store helper classes in any place you consider suitable. Concerning #Adam Knowles and #PachinSV answers - they are not quite right because they do not answer your question - "Where is the right place to pot those helper classes I create for help or for the business logic?" or "Where to store classes which I want to register and use via Service Container" - but not where to put bundle Extension class - which main purpose is to provide information about configuration which should be automatically loaded from your bundle to apps Service Container during the process of booting the Kernel.

Resources