1

異なるデータベース プラットフォーム間でエンティティ アノテーションを再利用するためのソリューションを見つけると、次のようになります。
エンティティ クラス メタデータの tableName を名前変更するためのソリューションがあります。

私のservices.xml

<service id="framework.loadclassmetadata.listener" class="%framework.loadclassmetadata.listener.class%">
        <tag name="doctrine.event_listener" event="loadClassMetadata" method="loadClassMetadata"/>
        <call method="setContainer"><argument type="service" id="service_container" /></call>
    </service>

私のリスナークラス:

<?php

namespace Fluency\Bundle\FrameworkBundle\EventListener;

use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Symfony\Component\DependencyInjection\ContainerAware;

/**
 * Class LoadClassMetadataListener
 *
 * @package Fluency\Bundle\FrameworkBundle\EventListener
 */
class LoadClassMetadataListener extends ContainerAware
{
    /**
     * @param LoadClassMetadataEventArgs $args
     */
    public function loadClassMetadata(LoadClassMetadataEventArgs $args)
    {
        $connection = $this->container->get('database_connection');
        $classMetadata = $args->getClassMetadata();

        if(!$connection->getDatabasePlatform()->supportsSchemas())
        {
            $tableName = $classMetadata->table['name'];
            $classMetadata->table['name'] = str_replace('.', '_', $tableName);

            foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
                if ($mapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
                    if(isset($classMetadata->associationMappings[$fieldName]['joinTable']['name']))
                    {
                        $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
                        $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = str_replace('.', '_',
                            $mappedTableName);
                    }
                }
            }
        }
    }
}

また、postgresql などのデータベース プラットフォームでスキーマを作成するためのソリューション:

<?php

namespace Fluency\Bundle\FrameworkBundle\EventListener;

use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
use Symfony\Component\DependencyInjection\ContainerAware;

/**
 * Class SchemaCreateTableListener
 *
 * @package Fluency\Bundle\FrameworkBundle\EventListener
 */
class SchemaCreateTableListener extends ContainerAware
{
    /**
     * @var array
     */
    private $_createdSchemas = array();

    /**
     * @param SchemaCreateTableEventArgs $args
     */
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
    {
        $connection = $this->container->get('database_connection');

        if ($args->getPlatform()->supportsSchemas())
        {

            $tableName = $args->getTable()->getName();

            $separetedTableName = explode('.', $tableName);

            if (count($separetedTableName) == 2)
            {
                $schemaName = $separetedTableName[0];

                if (!in_array($schemaName, $this->_createdSchemas))
                {
                    try
                    {
                        $connection->exec(sprintf("CREATE SCHEMA %s", $schemaName));
                    }
                    catch (DBALException $e)
                    {

                    }

                    $this->_createdSchemas[] = $schemaName;
                }
            }
        }
    }
}

すべて正常に動作しますが、クラスのメタデータが必要になるたびに、イベントリスナーがメインエンティティと各関連エンティティによって呼び出されるのは最適な原因ではないと思います。私は難しい方法で注釈を変更し、エンティティを読み取り、正規表現を使用してテーブル名を置き換えるコマンドを使用した汚いソリューションを考えました。(file_get_contents、正規表現の置換、および file_put_contents)...しかし、私は好きではありません。

私の質問 (最後に) は、可能であれば、symfony2-doctrine2 が注釈キャッシュやプロキシ クラスを生成するときに、正しい tableName を設定しますか? その後、私のエンティティコードは変更されずに残り、キャッシュが生成されたときにのみ呼び出される loadClassMetadata メソッドロジックが残ります。

4

2 に答える 2

1

最後に、Fluency Dev Team に道を譲ってくれたことに感謝します。彼らは、Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector に触れてスキーマの作成を修正する解決策を見つけました。Doctrine Team の美しい TODO があります。キャッシュの問題は、Doctrine\Common\Annotations\FileCacheReader のハーコード ハッキングによって解決されました。私は Sensio と Doctrine のコードから手を離すことを好みます。Doctrine が必要な修正を行うまで、最終的な解決策があります。インターネットの制限により GitHub で作業できないためです。

サービス.xml

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>
        <parameter key="fluency.framework.loadclassmetadata.listener.class">Fluency\Bundle\FrameworkBundle\EventListener\LoadClassMetadataListener</parameter>
        <parameter key="fluency.framework.schemacreatetable.listener.class">Fluency\Bundle\FrameworkBundle\EventListener\SchemaCreateTableListener</parameter>
    </parameters>

    <services>
        <service id="fluency.framework.loadclassmetadata.listener" class="%fluency.framework.loadclassmetadata.listener.class%">
            <tag name="doctrine.event_listener" event="loadClassMetadata" method="loadClassMetadata"/>
            <call method="setContainer"><argument type="service" id="service_container" /></call>
        </service>
        <service id="fluency.framework.schemacreatetable.listener" class="%fluency.framework.schemacreatetable.listener.class%">
            <tag name="doctrine.event_listener" event="onSchemaCreateTable" method="onSchemaCreateTable"/>
            <call method="setContainer"><argument type="service" id="service_container" /></call>
        </service>
    </services>
</container>

LoadClassMetadataListener.php

<?php

namespace Fluency\Bundle\FrameworkBundle\EventListener;

use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Symfony\Component\DependencyInjection\ContainerAware;

/**
 * Class LoadClassMetadataListener
 *
 * @package Fluency\Bundle\FrameworkBundle\EventListener
 */
class LoadClassMetadataListener extends ContainerAware
{
    /**
     * @param LoadClassMetadataEventArgs $args
     */
    public function loadClassMetadata(LoadClassMetadataEventArgs $args)
    {
        if (!$this->container->get('database_connection')->getDatabasePlatform()->supportsSchemas())
        {
            $classMetadata = $args->getClassMetadata();
            $tableName = $classMetadata->table['name'];
            if (strpos($tableName, '.'))
            {
                $reflectionClass = $classMetadata->getReflectionClass();

                $hashedName = sha1($reflectionClass->name);
                $cacheFileName = strtr($hashedName, '\\', '-') . '.cache.php';
                $this->refreshAnnotationsCache($cacheFileName);

                $classMetadata->table['name'] = str_replace('.', '_', $tableName);

                foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping)
                {
                    if ($mapping['type'] == ClassMetadataInfo::MANY_TO_MANY)
                    {
                        if (isset($classMetadata->associationMappings[$fieldName]['joinTable']['name']))
                        {
                            $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
                            if (strpos($mappedTableName, '.'))
                            {
                                $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = str_replace('.', '_',
                                    $mappedTableName);

                                $cacheFileName = strtr($hashedName, '\\', '-') . '$' . $fieldName . '.cache.php';
                                $this->refreshAnnotationsCache($cacheFileName);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * @param $cacheFileName
     */
    private function refreshAnnotationsCache($cacheFileName)
    {
        $cachePath = $this->container->getParameter('kernel.root_dir') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR .
            $this->container->getParameter('kernel.environment') .
            DIRECTORY_SEPARATOR . 'annotations';

        $cacheFilePath = $cachePath . DIRECTORY_SEPARATOR . $cacheFileName;

        $tableAnnotation = 'Doctrine\\ORM\\Mapping\\Table';
        $joinTableAnnotation = 'Doctrine\\ORM\\Mapping\\JoinTable';

        $data = include $cacheFilePath;

        $newData = array();
        foreach ($data AS $annotationClass)
        {
            if (get_class($annotationClass) == $tableAnnotation OR get_class($annotationClass) == $joinTableAnnotation)
            {
                $annotationClass->name = str_replace('.', '_', $annotationClass->name);
            }
            $newData[] = $annotationClass;
        }

        file_put_contents($cacheFilePath, '<?php return unserialize(' . var_export(serialize($newData), true) . ');');

    }
}

両方のリスナー (SchemaCreateTableListener を思い出してください) を使用すると、テーブル名の問題なしにデータベース プラットフォームを切り替えることができます。ピーター・ベイリーに感謝しますが、これらのコードは私が話していたものです。

于 2013-11-07T13:14:40.547 に答える