Symfony2实体元数据缓存?

时间:2013-11-06 19:58:46

标签: symfony doctrine-orm

找到在不同数据库平台之间重用实体注释的解决方案我达到了这个目的:
我有一个解决方案,用于在实体类元数据上重命名tableName,监听loadClassMetada事件:

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

一切正常,但我认为这不是最佳原因,每次我需要类元数据时,主实体和每个相关实体都会调用eventlistener。 我通过一个带有命令的脏解决方案,通过艰难的方式更改注释,读取实体并使用regexp替换表名。 (file_get_contents,regexp replacement和file_put_contents)......但不喜欢我。

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

2 个答案:

答案 0 :(得分:1)

最后,由于Fluency Dev Team给了我一个北方,我有东西骑在车轮上。这些人找到了一个解决方案,触及Doctrine \ DBAL \ Schema \ Visitor \ CreateSchemaSqlCollector来修复模式的创建。 Doctrine Team有一个漂亮的TODO。 现在缓存问题通过Doctrine \ Common \ Annotations \ FileCacheReader上的harcode hacking解决了。 我更喜欢将我的手从Sensio和Doctrine代码中移开,直到Doctrine进行必要的修复,我才能得到最终的解决方案,因为互联网限制导致我无法在GitHub上工作:

的services.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),我们可以在数据库平台之间切换而不会出现表名问题。 感谢彼得贝利,但这些代码就是我在谈论它。

答案 1 :(得分:1)

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