We are building a CMS and website building platform with a lot of different vendors and clients sites, so there are a lot of different content type entities, which are edited by generic controllers and helper services that don't necessarily (easily) know what the entity manager is for a given entity.
NOTE: We have several entityManagers to separate access to different databases, e.g. Global, Billing, Local, etc
There are many cases where we need to detect what is the entity's EntityManager. For example, we have a MediaHelper that dynamically associates media from a database with matching fields on the entity (this doesn't work with associations because the Media has to connect with literally any entity and you can't have that kind of dynamic association and we don't want a hundred different associations).
The media is in a bundle managed by the 'Local' EntityManager. But the entity may be in a 'Global' EntityManager (you can't assume it's in the same entity manager). So we need to detect and persist the right entity manager for the right entity.
So how do you recommend dynamically detecting the entityManager for an entity?
Original Custom Method
NOTE: the accepted answer is a much better solution. This is just here for archival purposes.
Here is a simple solution that works. But I don't know enough about Symfony and Doctrine to know if it's a bad idea? Does anyone else know? If not, I don't know why this wouldn't be in the core, as a Doctrine Utility.
I created an EntityHelper service that injects the Doctrine service into it:
gutensite_cms.entity_helper:
class: Gutensite\CmsBundle\Service\EntityHelper
arguments:
- "#doctrine"
Then in the entity helper is one simple function to get the Entity Manager for an entity (the config.yml registers the bundles for entity managers already):
/**
* Automagically find the entityManager for an entity.
* #param $entity
* #return mixed
*/
public function getManagerForEntity($entity) {
$className = \Doctrine\Common\Util\ClassUtils::getRealClass(get_class($entity));
foreach (array_keys($this->doctrine->getManagers()) as $name) {
if(in_array($className, $this->doctrine->getManager($name)->getConfiguration()->getMetadataDriverImpl()->getAllClassNames())) return $em;
}
}
NOTE: Doctrine Registry#getAliasNamespace already does something nearly identical to this foreach loop, I just modified the idea to return the entity manager instead of the namespace, e.g.
public function getAliasNamespace($alias) {
foreach (array_keys($this->getManagers()) as $name) {
try {
return $this->getManager($name)->getConfiguration()->getEntityNamespace($alias);
} catch (ORMException $e) {
}
}
throw ORMException::unknownEntityNamespace($alias);
}
Update 10/21/15: Entity Detection Code updated per #Cerad's suggestion.
Per the suggestion of #qooplmao, there is an easy method already in the Doctrine core.
// 1) get the real class for the entity with the Doctrine Utility.
$class = \Doctrine\Common\Util\ClassUtils::getRealClass(get_class($entity))
// 2) get the manager for that class.
$entityManager = $this->container->get('doctrine')->getManagerForClass($class);
Updated 10/22/15 After suggestions from Cerad and Qooplmao
Can you not give the entity manager a alias that suits the context it is for? You talk about Global, Billing, Local, so for example:
'service_manager' => array(
'aliases' => array(
'global_entity_manager' => 'My\Global\EntityManager',
'billing_entity_manager' => 'My\Billing\EntityManager',
'local_entity_manager' => 'My\Local\EntityManager',
),
)
You could also map the entity manager to the namespace of the entity.
So let's say you have a folder for your Global entities that is Global\Entity, then you could alias the entity manager for those entities Global\Entity\EntityManager. The advantage of this solution is that you can map several namespaces to the same entity manager, so your Billing and Global entities can easily share the same entity manager:
'service_manager' => array(
'aliases' => array(
'Global\Entity\EntityManager' => 'My\Global\EntityManager',
'Billing\Entity\EntityManager' => 'My\Global\EntityManager', // <-same name
'Local\Entity\EntityManager' => 'My\Local\EntityManager',
),
)
This only works if your entities in one namespace are manager by the same EntityManager instance. I can hardly believe this would not be the case in any project, but otherwise you should maybe reorganize a bit? :D
Related
So my question is very specific but I couldn't figure out how to make it even after several months of reflection. The following topic will be about Symfony, Doctrine and generating fixtures on-the-go for the tests.
I want to generate fixtures on the go from a test. The goal is to provide a very specific set of fixtures for each tests using helpers without sacrify the readability. That is the goal, so my idea was to create a tests/Resources/EntityProxy which is a mirror of the src/Entity folder, containing the same amount of classes with the exact same name. Each EntityProxy extends from its related Entity, use a custom trait to fill the properties easily.
You guessed it, I want to only use in tests the EntityProxy and use it directly into the functions to tests them. And there is a major issue with that, as Doctrine doesn't recognize the EntityProxy as an entity even if it extends from a real Entity.
Is there a way to say to Doctrine to persist an EntityProxy as its extended Entity?
__
The following code is an example of what I want as en EntityProxy:
namespace Tests\Resources\EntityProxy;
class User extends App\Entity\User
{
use FixtureGenerationTrait;
public function static makeDefault(): self
{
return static::generate([
'username' => self::getFaker()->username,
'email' => self::getFaker()->email,
...
]);
}
public function static make(array $data = []): self
{
$entity = static::makeDefault();
$entity = static::setValues($entity, $data);
return $entity;
}
}
And can be used in the tests like following: User::make(['name' => 'John Wick']);
I have an object $user that has a one to many relation with $establishment. I can use:
$user->getEstablishments();
The user can select a stablishment to work on. I have this method that I call in the controller:
$user->setCurrentEstablishment($establishment);
And this one that I call in the view:
$establishment = $user->getCurrentEstablishment();
I want to be able to call:
$user->setCurrentEstablishmentBy Slug($establishment_slug);
where the slug is a string, and let the user object look for the establishment.
Doctrine discourages the practice of accessing the Entity Manager inside the Entity object, but I think that using it in the controller is even worse.
I suspect that some special Doctrine annotation exists that takes care of non persistent relations like this, or some method other than serving the Entity Manager through a service should be used here. Some easy way of referencing other entities from inside the model.
¿Is there any? ¿How could I do that?
There is no Annotation in Doctrine which could convert slug into object.
What can help You is ParamConverter, with it you can automatically convert slug from query into object. But it still must be used in Controller.
Example usage:
/**
* #Route("/some-route/{slug}")
* #ParamConverter("object", class="AppBundle:Establishment", options={"id" = "slug", "repository_method" = "findEstablishmentBySlug"})
*/
public function slugAction(Establishment $object)
{
...
Docs about param converter: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
I would like to retrieve a record from another entity (or record from the DB) within a entity.
They there are no relationship between the two entities.
I am using #ORM\HasLifecycleCallbacks() and #ORM\PrePersist so when the main entity is created it will also create another entity (save a record to another table)
The above is working fine, there are no issues with this.
What I am having an issue with is I would like to link that entity with another table but I need to retrieve the object based on the value of the first entity.
Usually I would write a function in the entity repository but I am not calling the entity manager within the entity.
An Entity in Doctrine is an object representation of a concept, with attributes and methods. It is meant to be lightweight, a POPO (plain old php object). It must not know anything about its persistence. Therefore if you see reference to the EntityManager in a model, it probably stinks.
Solutions? You could use an entity listener called on entity creation and then use a service dedicated only to properly compose your object(s), maybe something like a Factory. In this way, your entity stays lightweight, the lifecycle management is satisfied and the entity composing is responsibility only of your service.
Entity manager is accessible in an entity repository. You can legally use it to fetch data from other entities and to compose your business logic. This is what entity repositories are made for: Doctrine Custom Repositories, Symfony Custom Repository Classes.
/**
* #ORM\Entity
*/
class Beta {}
/**
* #ORM\Entity
*/
class Alpha {}
class AlphaRepository extends EntityRepository
{
public function getDataFromAnotherEntity($something)
{
$query = 'select * from MyBundle\Entity\Alpha alpha where alpha.id = :something';
return $this->getEntityManager()
->createQuery($query)
->setParameter('something', $something)
->getResult();
}
}
In Symfony 3.1 you can use the entityManager to set a reference. This is still lightweight as it does not instance a complete Doctrine Record.
Example: I have an entity Status which has some states, and it's referenced in another entity. On create i use this method inside EventSubscriber:
public function preAction(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
if (method_exists($entity, 'setStatus')) {
if ($entity->getStatus() === null) {
$entity->setStatus($entityManager->getReference('AppBundle\Entity\Status', Status::STATUS_REGULAR));
}
}
}
I'm building an application where users are tied to accounts (as in, multiple users all share an account), then other entities - lets call them products are tied to the accounts. The products are associated with the accounts and only users that are tied to that account can edit the products.
The difference being in my case, there are many different entities being shared in the same model.
If it was just the one (product) entity, it wouldn't be a problem to have a method in my ProductRepository like:
public function checkOwnership($id, $account)
{
$count = $this->createQueryBuilder('s')
->select('count(s.id)')
->where('s.account = :acc')
->andWhere('s.id = :id')
->setParameters(array('id' => $id, 'acc' => $account))
->getQuery()
->getSingleScalarResult();
if($count == 0)
throw $this->createNotFoundException('ye..');
return;
}
To make sure the id of the product is tied to the user's account.
The account argument is passed to this function by using:
$this->getUser();
In the controller.
What is the best way for me to make this function available to the other entities to prevent code duplication? There are (currently) only 3 other entities, so it wouldn't be the end of the world to just have it in each repository, but I'm just wondering if there were a way to create a 'common' or global repository that follows good standards? I'd sure like to know about it.
Edit:
Or have I just completely over-thought this? Can I just create a 'Common' directory in my 'MainBundle', define a namespace and add a use statement at the start of my controllers that need access to the function?
I hope I fully understand your question.
Solution one, duplication, easy: let #ParamConverter do the job (automatic response to 404 if doesn't exist)
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/pets/{id}")
*/
public function showAction(Pet $pet)
{
// Current logged user
$currentUser = $this->get('security.context')->getToken()->getUser();
// Owner
$ownerUser = $pet->getUser();
if(!$currentUser->equals($ownerUser)) {
// 401, 403 o 404 depends on you
}
}
Solution two, DRY, a bit harder: use JMSAOPBundle and define an interceptor that intercepts all request to you controller and actions (show, delete, etc.). The pointcut (connected to the interceptor) will get the current user from the context and the owner from the entity. Then you do the check...
At the moment I am learning how to use Symfony2. I got to the point where they explain how to use Doctrine.
In the examples given they sometimes use the entity manager:
$em = $this->getDoctrine()->getEntityManager();
$products = $em->getRepository('AcmeStoreBundle:Product')
->findAllOrderedByName();
and in other examples the entity manager is not used:
$product = $this->getDoctrine()
->getRepository('AcmeStoreBundle:Product')
->find($id);
So I actually tried the first example without getting the entity manager:
$repository = $this->getDoctrine()
->getRepository('AcmeStoreBundle:Product');
$products = $repository->findAllOrderedByName();
and got the same results.
So when do i actually need the entity manager and when is it OK to just go for the repository at once?
Looking at Controller getDoctrine() equals to $this->get('doctrine'), an instance of Symfony\Bundle\DoctrineBundle\Registry. Registry provides:
getEntityManager() returning Doctrine\ORM\EntityManager, which in turn provides getRepository()
getRepository() returning Doctrine\ORM\EntityRepository
Thus, $this->getDoctrine()->getRepository() equals $this->getDoctrine()->getEntityManager()->getRepository().
Entity manager is useful when you want to persist or remove an entity:
$em = $this->getDoctrine()->getEntityManager();
$em->persist($myEntity);
$em->flush();
If you are just fetching data, you can get only the repository:
$repository = $this->getDoctrine()->getRepository('AcmeStoreBundle:Product');
$product = $repository->find(1);
Or better, if you are using custom repositories, wrap getRepository() in a controller function as you can get auto-completition feature from your IDE:
/**
* #return \Acme\HelloBundle\Repository\ProductRepository
*/
protected function getProductRepository()
{
return $this->getDoctrine()->getRepository('AcmeHelloBundle:Product');
}
I think that the getDoctrine()->getRepository() is simply a shortcut to getDoctrine()->getEntityManager()->getRepository(). Did not check the source code, but sounds rather reasonable to me.
If you plan to do multiple operations with the entity manager (like get a repository, persist an entity, flush, etc), then get the entity manager first and store it in a variable. Otherwise, you can get the repository from the entity manager and call whatever method you want on the repository class all in one line. Both ways will work. It's just a matter of coding style and your needs.