寻找在不同数据库平台之间重用实体注释的解决方案我达到了这一点:
我有一个在实体类元数据上重命名 tableName 监听 loadClassMetada 事件的解决方案:
我的服务.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 方法逻辑。
终于我有了轮子上的东西,感谢 Fluency 开发团队给了我一个方向。这些人找到了一个涉及 Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector 的解决方案来修复模式的创建。 Doctrine 团队有一个漂亮的 TODO。 现在,通过 Doctrine\Common\Annotations\FileCacheReader 上的 harcode hacking 解决了缓存问题。 我更喜欢远离 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),我们可以在数据库平台之间切换,而不会出现表名问题。 感谢 Peter Bailey,但这些代码正是我在谈论的。
您可以使用
annotations.file_cache_reader_class
覆盖容器参数并实现您自己的逻辑。