Symfony2 Doctrine Table Prefix

I was stuck for a while figuring out how to provide a configurable table prefix within my bundle.

The Doctrine website has a page in their cookbook that tells how to setup a table prefix, but I needed to figure out how to tie it in to Symfony. Well, here is how:


Note #1: This example uses YML for config, but utilizing a different format should not be difficult.
Note #2: This example was written for Symfony2.0 & Doctrine 2.2.


Updated 9/16/2013

– Added a condition that detects single table inheritance to prevent applying the prefix more than once. Thank you greg0ire.

Updated 2/15/2012

– Added a condition so that the table prefix is applied only to the bundles you specify. If you want the prefix to apply to all bundles, just remove the condition inside the loadClassMetadata() method.
– Replaced a call to Doctrine’s deprecated setTableName() method.


Instructions

  1. Open up your bundle’s Resources/config/services.yml
  2. Define a table prefix parameter:
    (Change acmebundle and acme_)

    parameters:
        acmebundle.db.table_prefix: acme_
  3. Add a new service:
    (Change acmebundle and AcmeBundle)

    services:
                acmebundle.tblprefix_subscriber:
                    class: AcmeBundle\Subscriber\TablePrefixSubscriber
                    arguments: [%mybundle.db.table_prefix%]
                    tags:
                        - { name: doctrine.event_subscriber }
  4. Create AcmeBundle\Subscriber\TablePrefixSubscriber.php
    (Change AcmeBundle both in the PHP namespace and in the call to strpos)

    <?php
    namespace AcmeBundle\Subscriber;
    		
    use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
    		
    class TablePrefixSubscriber implements \Doctrine\Common\EventSubscriber
    {
    	protected $prefix = '';
    
    	public function __construct($prefix)
    	{
    		$this->prefix = (string) $prefix;
    	}
    
    	public function getSubscribedEvents()
    	{
    		return array('loadClassMetadata');
    	}
    		
    	public function loadClassMetadata(LoadClassMetadataEventArgs $args)
    	{
    		$classMetadata = $args->getClassMetadata();
    
    		// Do not re-apply the prefix in an inheritance hierarchy.
    		if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
    			return;
    		}
    
    		if (FALSE !== strpos($classMetadata->namespace, 'AcmeBundle')) {
    			$classMetadata->setPrimaryTable(array('name' => $this->prefix . $classMetadata->getTableName()));
    
    			foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
    				if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY
    				  && isset($classMetadata->associationMappings[$fieldName]['joinTable']['name'])) {
    					$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
    					$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
    				}
    			}
    		}
    	}
    }

23 comments » Write a comment

  1. I get a warning in line 20 which is this one:
    $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];

    Notice: Undefined index: name in /var/www/tokam_dev/p103/library/DoctrineExtensions/TablePrefix.php on line 20

    Like this I think it is not useable for me.

  2. Is it possible to add a sequence prefix same way?
    For example i PostgreSQL to store sequences in different schema then public (like tables).

  3. I tried this but it adds the prefix to all the entities in all the bundles. Is it possible to do this on a per-bundle basis.

    • Thank you for bringing this to my attention. I can see how this could be problematic, so I updated the post, showing how you can limit the prefix to only your bundle.

      • I did something similar but I went further by creating a bundle just to handle this. But instead of hard coding the bundle I want to be affected, I passed some parameters to the service; I passed each bundle name and its prefix along a default prefix for all other bundle.

        Maybe a bundle is not needed but it was the best way I could think of to make it reusable.

  4. Hello,

    In the code of TablePrefixSubscriber class, at line 28, the expression of the if structure should include “&& isset($classMetadata->associationMappings[$fieldName]['joinTable']['name'])” in order to verify if that index exist.

    That’s why geek (1st comment) get the warning.

    • Thanks. I’ve updated the post to include that.

      It would be nice if Doctrine included it in their cookbook, but I suppose here will suffice.

  5. Please forgive me on my silly question. I am very new to Symphony2. I am just wondering how to implement it and make it work? Say we use something like

    $em = $this->getDoctrine()->getEntityManager();

    How to trigger this?

    $this->get(‘acmebundle.tblprefix_subscriber’) ?

    What’s next then?

    Thank you for your help!

    • You don’t have to do that at all. Step 4 above is an example of a Doctrine event subscriber. In Symfony2, you simply register the subscriber in the DIC and tag it as such.

  6. I think its better to use on line 28:
    if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide'] == true)

    The isset criteria works fine, but the issue is related to look inside a joinTable of a not owning side association mapping.

  7. Pingback: Adding a table prefix to your Doctrine Entities in a Bundle | deanoj

  8. Would this be the way to go if you want to use schema’s in PostgreSQL? Eg SELECT * from myschema.mytable. Where ‘myschema.’ is the prefix.

  9. Hello, thanks for sharing this approach. it is the best approach i’ve found to set prefixes dynamically to db outside entity classes. but the problem i can’t solve is the use for multiple bundles, each with its own prefix.
    In line 29 of TablePrefixSubscriber class you try to decide statically. but i have to copy this class to each bundle to set the bundle name. in this case it is easier to write it to each entity @ORM\Table(name=”prefix_tablename”).
    i also tried to pass a parameter to service definition but it does not work, do you have any idea to use this class also in multiple bundles without multipling this class?

    • The subscriber doesn’t actually have to be in a bundle and you don’t need a new one for each bundle, though you could if you wanted. You can place the subscriber anywhere you want, as long as the autoloader can reach it and you update the DIC.

      To have different prefixes for each bundle, you only need to modify lines 29, 30 and 36. You could hard code the logic or use an annotation like you said.

  10. Pingback: Agregar prefijo a las entidades Doctrine

Leave a Reply

Required fields are marked *.


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>