1

找到在不同数据库平台之间重用实体注释的解决方案我达到了这一点:
我有一个解决方案,用于在实体类元数据监听 loadClassMetada 事件上重命名 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、regexp 替换和 file_put_contents)...但不喜欢我。

我的问题(最后)是如果可能的话,当 symfony2-doctrine2 生成注释缓存和/或代理类时,设置正确的 tableName?然后我的实体代码保持不变,并且 loadClassMetadata 方法逻辑仅在生成缓存时才被调用。

4

2 回答 2

1

您可以使用您的容器参数覆盖容器参数annotations.file_cache_reader_class并实现您自己的逻辑。

于 2013-11-08T17:37:04.557 回答
1

终于,我有了车轮上的东西,感谢 Fluency Dev Team 给了我一个北方。这些家伙找到了一个解决方案,触及 Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector 来修复模式的创建。Doctrine Team 有一个漂亮的 TODO。现在缓存问题已经通过 Doctrine\Common\Annotations\FileCacheReader 上的 harcode hacking 解决了。我更喜欢不要接触 Sensio 和 Doctrine 代码,在 Doctrine 进行必要的修复之前,我有最终的解决方案,因为由于 Internet 限制,我无法在 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),我们可以在数据库平台之间切换而不会出现表名问题。感谢 Peter Bailey,但这些代码是我在谈论的。

于 2013-11-07T13:14:40.547 回答