Querying Entity Relation Tables In OROCRM & ORO Platform
Entity Relation Tables
If you have worked with the OROCRM or ORO Platform, then you are likely familiar with extended entities. If you are not familiar, extended entities are any entity, say a Task or Contact record where you want to add additional custom fields for your application. These extended entity files and any relations that you have added via schema migrations are stored in the
app/cache/prod/oro_entities and the
In your schema migrations, if you do not explicitly set what you want the relation table name to be, Oro generates one consisting of a prefix and a random string of numbers. You can see this done in the
Oro\Bundle\EntityExtendBundle\Tools\ExtendDbIdentifierNameGenerator class and calls the
generateManyToManyJoinTableName function. The table name would end up being named something like
Let’s say that you have a Task entity that is related to a custom entity you created via a many to one relation. For an example, let’s say your custom entity is a Vendor. Assuming you created your schema migration the ORO way, the Task entity will now have an extended entity file in the cache directories I talked about earlier. That file, is the owning side of the relation and will have a property with something like vendor_be56a0 that is the index to the Vendor entity relation. The random string on the property is also populated by an ORO service. The file will have get and set methods with the relation to the Vendor like any other entity file.
I know what you’re asking, if there is a relation, then why not just use the regular Doctrine query like
$task->getVendor(). The first thing is that the getter wouldn’t be
getVendor(), it would be
getVendorBe56a0() instead. You could look in the extended entity file and get that property and hard code it, but it would be safer and more reliable to use the ORO service that I will talk about below. The next problem is if you want to query every Task that has a relation to the one Vendor entity. Typically you would write a query using the Doctrine
createQuery methods. This won’t work, you can try if you don’t believe me. Instead you have to directly query the relation table to find all rows where the
vendor_id matches the parameter passed that you are looking for.
The reason you have to directly query the entity relation table, is because Doctrine lacks the capability to build the query in some cases. Why? Doctrine (DQL) only works with entities, and the relation tables are not classes or entities that are managed by Doctrine. It is also much more efficient to directly query the database instead when working with entity relation tables.
Getting Your Class Ready
I am going to assume that you already know how to inject dependencies into your class via a services.yml file or if you instantiate your class with the
new keyword. There are only two services that you will need in your class. The
Let’s Break It Down
Step One: Get Association Name
In the scenario above the first step to querying the database to get the relation table name is to use the
ExtendHelper class. This class has a public static function
buildAssociationName that takes two parameters. The fully qualified class name, and the relation kind which defaults to null if nothing is passed. In our case you would want to pass
'activity' because the Task entity implements the
Oro\Bundle\ActivityBundle\Model\ActivityInterface. This is the method I mentioned I would get to earlier, that should be used instead of hard coding
$associationName = ExtendHelper::buildAssociationName(get_class($class), 'activity');
Step Two: Get Metadata For Task Entities
The next step is using the DoctrineHelper class and calling the
getEntityMetadataForClass method which requires the class name for the entity you want the data from and an optional argument to throw an exception that defaults to
true. This gives you an instance of the
$entityMetadata = $this->doctrineHelper->getEntityMetadata('OroTaskBundle:Task');
Step Three: Get Association Mapping Information
The third step is to get the association mapping, which is, you guessed it, is called
getAssociationMapping and you pass the association name that you retrieved from step one. This returns a multidimensional array of information for the mapping that includes, wait for it, the relation table name!
$association = $entityMetadata->getAssociationMapping($associationName);
Step Four: Retrieve Table Name
Step four is very easy, set a variable that will hold the table name, which is retrieved from the $
$tableName = $association['joinTable']['name'];
Step Five: Get Default Database Connection Object
To get the core Doctrine connection to the database,
Doctrine\DBAL\Connection, you can use the DoctrineHelper class to call the getEntityManagerForClass() method, and then call the
Step Six: Write A Statement
Next you can write the SQL statement.
$dql = "SELECT * FROM $tableName WHERE vendor_id = ?";
Step Seven: Execute Statement & Fetch Results
Finally, you simply need to execute your statement and fetch the results.
$stmt = $connection->prepare($dql); $stmt->bindValue(1, $class->getId()); $stmt->execute(); $relations = $stmt->fetchAll();
The last two steps are not just for OROCRM or ORO Platform, it is native to Symfony or any application that is using Doctrine for the ORM.
The full code output is below so that you can see it all in one easy to copy and paste block.
$associationName = ExtendHelper::buildAssociationName(get_class($class), 'activity'); $entityMetadata = $this->doctrineHelper->getEntityMetadata('OroNoteBundle:Note'); $association = $entityMetadata->getAssociationMapping($associationName); $tableName = $association['joinTable']['name']; $connection = $this->om->getConnection(); $dql = "SELECT * FROM $tableName WHERE business_id = ?"; $stmt = $connection->prepare($dql); $stmt->bindValue(1, $class->getId()); $stmt->execute(); $relations = $stmt->fetchAll();
I hope you can use this! Let me know if you did or if you know another way to get a relation table name with a comment below!