Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine: Relations between two entities in two databases

I'm using MySQL and Doctrine 2 (with Symfony 3).

I want to set up a ManyToMany relationship between two entities which are in two separate databases.

I rode that Doctrine doesn't handle this, at least not in an native way. To perform that anyway, I'm using the schema attribute on my tables definitions.

Let's say I have those 2 entities :

/**
 * @ORM\Table(schema="bdd2", name="site")
 */
class Site {}

and

/**
 * @ORM\Table(name="user")
 */
class User {}

I do not use the schema attribute on the User entity because my entity manager is configured to use its database, so it's useless. I set up the relationship in User one this way:

/**
 * @ORM\ManyToMany(targetEntity="[...]")
 */
private $sites;

Quite simple. In the code side, it works, the relation is effective.

In the database side, it's not ok, because the user_site table generated by doctrine has only one foreign key (for the user): it doesn't contain the foreign key to the second database. The field exists, has an index, but not foreign key.

If I add the foreign key by myself, each time I do a php bin/console doctrine:schema:update --dump-sql Doctrine want to remove it.

The question is: How can I make doctrine create this second foreign key? or if it's not possible, How can I make doctrine ignore the one I created myself ?

like image 211
Tmb Avatar asked Feb 23 '17 13:02

Tmb


1 Answers

Here is the solution I suggest, yesterday in my comment I couldn't explain it properly.

When you run doctrine-schema-update (or doctrine-migrations-diff, see https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html), doctrine does 3 things:

  • it generates the schema from mapping informations (e.g. entity annotations), then the postGenerateSchema event is triggered
  • it generates the schema from the current db (through reverse engeneering)
  • it compares the two generated schemas and generate the SQL code that needs for the second schema to be the same as the first.

So if you manipulate the first schema, you can avoid Doctrine from doing what you don't like.

In this example, I have some entities that are actually VIEWS in the db, not TABLES. So I tell doctrine to don't generate their tables and all the FKs contraints on related table. You can easily start from this working script and adapt to your needs.

Best regards. this is AppBundle/Utils/IgnoreTablesListener.php

<?php


namespace AppBundle\Utils;

use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;


class IgnoreTablesListener {
    private $ignoredEntities = null;
    private $ignoredTables = null;


    public function __construct($ignoredEntities) {
        $this->ignoredEntities=$ignoredEntities;
        $this->ignoredTables=array();
    }

    /**
     * Remove ignored entities from Schema
     * This listener is called when the schema as been generated, from entities data mapping (i.e. in doctrine-schema-update or in doctrine:migrations:diff)
     * 
     * @param GenerateSchemaEventArgs $args
     */
    public function postGenerateSchema(GenerateSchemaEventArgs $args)
    {

        $schema = $args->getSchema();
        $em = $args->getEntityManager();

        $ignoredTables = $this->ignoredTables;

        foreach ($this->ignoredEntities as $entityName) {
            $ignoredTables[] = strtolower($em->getClassMetadata($entityName)->getTableName());
        }

        foreach ($schema->getTableNames() as $longTableName) {
            $table=$schema->getTable($longTableName);
            $table_name=strtolower($table->getShortestName($table->getNamespaceName()));

            $fks=$table->getForeignKeys();
            foreach ($fks as $fk) { 
                $foreign_table_name=strtolower($fk->getForeignTableName());

                if (in_array($foreign_table_name, $ignoredTables)) {    //if the fk points to one of the entities i'm ignoring
                    $table->removeForeignKey($fk->getName());   //i remove fk constrains from generated schema (NOT FROM MY CURRENT DB!!!)
//                  dump('removed FK '.$fk->getName().' from '.$table_name.' pointing to '.$foreign_table_name.'.['.implode(', ', $fk->getForeignColumns()).']');
                } 
            }
            if (in_array($table_name, $ignoredTables)) { //if i have to ignore the $table_name table
                $schema->dropTable($longTableName);     //remove the table from generated schema -- NOT FROM DB!!
//              dump('removed ignored table/entity '.$longTableName);
            }

        }

    }

}

and this is the service configuration (app/config/services.yml)

..
app.ignoretableslistener:
    class: AppBundle\Utils\IgnoreTablesListener
    arguments: ['AppBundle:MyEntityToBeIgnoredBecauseItIsAView1', 'AppBundle:MyEntityToBeIgnoredBecauseItIsAView2']
    tags:
        - {name: doctrine.event_listener, event: postGenerateSchema }
..
like image 193
ste Avatar answered Sep 18 '22 23:09

ste