如何在Symfony 2中为数据库视图设置实体(Doctrine)

32

假设我有一个视图表,并且我想将数据从它传递到一个实体中。 我能否(以及如何)创建实体类来完成此操作(无需保存操作)? 我只想显示它们。

假设你已经有了一个视图表,你可以通过创建一个新的实体类来获取该视图表中的数据。实体类不需要任何保存操作,只需用属性来映射视图表的列即可。在显示数据时,您可以使用该实体类并访问其属性来获取所需的数据。
7个回答

48

接受的答案是正确的,但我想提供一些额外的建议,供您考虑:

将您的实体标记为只读。

使构造函数私有化,这样只有Doctrine才能创建实例。

/**
 * @ORM\Entity(readOnly=true)
 * @ORM\Table(name="your_view_table")
 */
class YourEntity {
    private function __construct() {}
}

1
但是我该如何在ORMPurger中使用它呢?它无法删除视图。 - Ionel Lupu

29

在查询视图方面并没有什么特别的 — 它只是一个虚拟表。按照以下方式设置实体的表格,然后享受它:

/**
 * @ORM\Entity
 * @ORM\Table(name="your_view_table")
 */
class YourEntity {
    // ...
}

3
@GusDeCooL,那么请添加一个标识符。 ;) - Elnur Abdurrakhimov
18
非常不好的做法。调用 doctrine:schema:update... 表将被重写为普通表格(而不是视图)。 - user1954544
5
使用ORM生成模式是一个不好的实践。 - Elnur Abdurrakhimov
20
使用ORM生成模式是Symfony推荐的做法。另外,由于查询的特性,许多视图没有标识符。 - Tom T
1
@SergeVelikanov,如果你让ORM为你生成表格,那你就麻烦了。 ;) - Elnur Abdurrakhimov
显示剩余7条评论

22
之前的回答都是正确的,但是如果你使用Doctrine迁移工具并执行schema:update,它会失败...
所以,除了将实体标记为只读并将构造函数设为私有(如Ian Phillips的回答中所解释的),还需要做以下操作:
/**
 * @ORM\Entity(readOnly=true)
 * @ORM\Table(name="your_view_table")
 */
class YourEntity {
    private function __construct() {}
}

你需要将模式工具设置为在进行模式更新时忽略该实体...
为了做到这一点,你只需要在你的捆绑包中创建这个命令,并将你的实体设置在ignoredEntity列表中。

src/Acme/CoreBundle/Command/DoctrineUpdateCommand.php:

<?php

namespace Acme\CoreBundle\Command;

use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\Tools\SchemaTool;

class DoctrineUpdateCommand extends \Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand {

  protected $ignoredEntities = array(
      'Acme\CoreBundle\Entity\EntityToIgnore'
  );

  protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) {

    /** @var $metadata \Doctrine\ORM\Mapping\ClassMetadata[] */
    $newMetadatas = array();
    foreach ($metadatas as $metadata) {
      if (!in_array($metadata->getName(), $this->ignoredEntities)) {
        array_push($newMetadatas, $metadata);
      }
    }

    parent::executeSchemaCommand($input, $output, $schemaTool, $newMetadatas);
  }

}

(感谢Alexandru Trandafir Catalin提供的信息,来源于这里:https://dev59.com/DWcs5IYBdhLWcg3w0HIa#25948910
顺便说一下,这是我找到的与Doctrine视图一起使用的唯一方法...我知道这是一个权宜之计...如果有更好的方法,我愿意听取建议。

1
但是这仅在调用更新时有效,验证显示失败。 - Marcus Wolf
1
你说得对,马库斯,我刚试了一下,它失败了... 我想如果你使用验证,你应该将实体添加到排除列表中进行验证(类似于我在更新时所做的)。这是可能的,但从更新中排除实体已经是一个hack了,我认为Symfony应该为视图实现一些内置功能... - Alex Mantaut
你不能在一个单独的实体管理器中创建视图吗?我认为这将解决“schema:update”问题。 - Marco Veenendaal

22

除了上述答案之外,如果您正在使用Doctrine迁移来更新模式,则以下配置完美适用。

/**
 * @ORM\Entity(readOnly=true)
 * @ORM\Table(name="view_table_name")
 */
class YourEntity {
    private function __construct() {}
}

到这里和以上的答案是一样的。在这里,你需要配置Doctrine不绑定模式。

doctrine:
    dbal:
        schema_filter: ~^(?!view_)~

上述过滤器定义过滤所有以“view_”为前缀的表和视图,并可使用正则表达式进行扩展。只需确保您已将视图命名为“view_”前缀即可。

但是,doctrine:schema:update --dump-sql仍然显示了视图,希望他们也能将相同的过滤器集成到模式更新中。

我希望这个解决方案能帮助其他人。

来源:http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html#manual-tables


3
至少在Doctrine 2.4中,这无法工作。模式更新尝试创建一个表。 - albert
“console d:s:u” 仍然想要使用 “doctrine/doctrine-migrations-bundle:2.1.2” 创建表格,但是使用迁移功能时一切正常,所以对我来说没问题。 - Pierre de LESPINAY
这似乎适用于Doctrine 3+。解决方案也在Symfony文档中提到:https://symfony.com/bundles/DoctrineMigrationsBundle/current/index.html#manual-tables - undefined

2
除了上面的答案之外,您必须有一个视图实体和虚拟表的命名策略,例如:view_your_table,并且您必须将以下代码添加到doctrine.yaml中,以禁用为视图创建新的迁移文件: schema_filter: ~^(?!view_)~

1

我花了一整天的时间,因为需要在Zend实现中引入一个视图到我的数据库中。

正如之前所说,您应该创建一个实体,这个实体必须有Id()注释:

/**
 * @Doctrine\ORM\Mapping\Table(name="your_view")
 * @Doctrine\ORM\Mapping\Entity(readOnly=true)
 */
class YourViewEntity
{
    /**
     * @var SomeEntityInterface
     * @Doctrine\ORM\Mapping\Id()
     * @Doctrine\ORM\Mapping\OneToOne(targetEntity="SomeMainEntity", fetch="LAZY")
     * @Doctrine\ORM\Mapping\JoinColumn(nullable=false, referencedColumnName="id")
     */
    protected $some;

    /**
     * @var AnotherEntityInterface
     * @Doctrine\ORM\Mapping\ManyToOne(targetEntity="AnotherEntity", fetch="LAZY")
     * @Doctrine\ORM\Mapping\JoinColumn(nullable=false, referencedColumnName="id")
     */
    protected $another;

    // Make the constructor private so that only Doctrine can create instances.
    private function __construct() {}
}

同时,还可以使用私有构造函数,如Ian Phillips的回答所述。然而,这并不能防止orm:schema-tool:update基于新实体创建表格,试图覆盖我们的视图...尽管在生产环境中应该避免使用orm:schema-tool:update,而应该使用迁移脚本,但对于开发目的来说,这非常有用。

由于schema_filter: ~^(?!view_)~似乎都不起作用了,而且已经过时了,我设法在Kamil Adryjanek页面上找到了一个技巧,它提供了向实体管理器添加EventListenerSubscriber的选项,这将防止为我们创建表格。我的实现如下:

class SkipAutogenerateTableSubscriber implements EventSubscriber
{
    public const CONFIG_KEY = "skip_autogenerate_entities";

    private $ignoredEntities = [];

    public function __construct($config)
    {
        if (array_key_exists(self::CONFIG_KEY, $config)) {
            $this->ignoredEntities = (array) $config[self::CONFIG_KEY];
        }
    }

    public function getSubscribedEvents()
    {
        return [
            ToolEvents::postGenerateSchema
        ];
    }

    public function postGenerateSchema(GenerateSchemaEventArgs $args)
    {
        $schema = $args->getSchema();
        $em = $args->getEntityManager();

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

        foreach ($schema->getTables() as $table) {

            if (in_array($table->getName(), $ignoredTables)) {
                $schema->dropTable($table->getName());
            }
        }
    }
}

这不仅解决了orm:schema-tool的问题,也解决了doctrine/migrations模块的migrations:diff问题。


1
除了上面的答案之外,我混合了一些你的示例代码来扩展DoctrineUpdateCommand。
这是我的DoctrineUpdateCommand:
class DoctrineUpdateCommand extends UpdateSchemaDoctrineCommand{
   protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas) {
      $container = $this->getApplication()->getKernel()->getContainer();  

     $filterExpr = $container->get('doctrine')->getEntityManager()->getConnection()->getConfiguration()->getFilterSchemaAssetsExpression();
     $emptyFilterExpression = empty($filterExpr);

     /** @var $newMetadatas \Doctrine\ORM\Mapping\ClassMetadata */
     $newMetadatas = array();

     foreach ($metadatas as $metadata) {
        if(($emptyFilterExpression||preg_match($filterExpr, $metadata->getTableName()))){
            array_push($newMetadatas, $metadata);
        }        
     }

     parent::executeSchemaCommand($input, $output, $schemaTool, $newMetadatas);
 }
}

感谢正确的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接