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;
    				}
    			}
    		}
    	}
    }

20 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.

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>