【问题标题】:How to override bundled Doctrine repository in Symfony如何在 Symfony 中覆盖捆绑的 Doctrine 存储库
【发布时间】:2016-03-11 14:37:09
【问题描述】:

我有一个独立的 Symfony 包(与 Composer 一起安装),其中包含实体和存储库,可在连接同一数据库的应用程序之间共享。

使用配置(显示 yml)将实体附加到每个应用程序:

doctrine:
    orm:
        mappings:
            acme:
                type: annotation
                dir: %kernel.root_dir%/../vendor/acme/entities/src/Entities
                prefix: Acme\Entities
                alias: Acme

嗯,这是在应用程序中包含外部实体的最简单方法,但看起来有点难看。

每当我从实体管理器获得存储库时:

$entityManager->getRepository('Acme:User');

我得到预配置的存储库(在实体配置中)或默认的Doctrine\ORM\EntityRepository

现在我想覆盖单个实体的捆绑(或默认)存储库类。有没有机会通过一些配置/扩展/等来做到这一点?

我认为,最好看的方式是:

doctrine:
    orm:
         ....:
             Acme\Entities\User:
                 repositoryClass: My\Super\Repository

或者带标签:

my.super.repository:
    class: My\Super\Repository
    tags:
        - { name: doctrine.custom.repository, entity: Acme\Entities\User }

【问题讨论】:

标签: php doctrine-orm doctrine symfony


【解决方案1】:

您可以使用 LoadClassMetadata 事件:

class LoadClassMetadataSubscriber implements EventSubscriber
{

    /**
     * @inheritdoc
     */
    public function getSubscribedEvents()
    {
        return [
            Events::loadClassMetadata
        ];
    }

    /**
     * @param LoadClassMetadataEventArgs $eventArgs
     */
    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
    {
        /**
         * @var \Doctrine\ORM\Mapping\ClassMetadata $classMetadata
         */
        $classMetadata = $eventArgs->getClassMetadata();

        if ($classMetadata->getName() !== 'Acme\Entities\User') {
            return;
        }

        $classMetadata->customRepositoryClassName = 'My\Super\Repository';
    }

}

Doctrine Events

使用配置(显示 yml)将实体附加到每个应用程序: 嗯,这是在应用程序中包含外部实体的最简单方法,但看起来有点难看。

您可以启用auto_mapping

【讨论】:

  • 感谢 auto_mapping 提示。我不知道为什么它在我的项目中不起作用。仔细阅读提供的文档后,我意识到我使用 Entities 命名空间,尽管学说预定义了 Entity
【解决方案2】:

适用于 Doctrine 版本

除了 Artur Vesker 的回答之外,我还找到了另一种方法:覆盖全局 repository_factory

config.yml:

doctrine:
    orm:
        repository_factory: new.doctrine.repository_factory

services.yml:

new.doctrine.repository_factory:
    class: My\Super\RepositoryFactory

存储库工厂:

namespace My\Super;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;

class RepositoryFactory extends DefaultRepositoryFactory
{
    /**
     * @inheritdoc
     */
    protected function createRepository(EntityManagerInterface $entityManager, $entityName)
    {
        if ($entityName === Acme\Entities\User::class) {
            $metadata = $entityManager->getClassMetadata($entityName);
            return new ApplicationRepository($entityManager, $metadata);
        }

        return parent::createRepository($entityManager, $entityName);
    }
}

毫无疑问,实现LoadClassMetadataSubscriber 是一种更好的方法。

【讨论】:

  • 不幸的是,这不再可能,因为 DefaultRepositoryFactory 类自 2.4 版以来已成为最终版本。
  • 悲伤但真实。我已经添加了版本通知。谢谢!
【解决方案3】:

使用当前的 symfony 5.3 和教义 2.9.5

在您的配置中定义服务和教义.orm.repository_factory:

doctrine:
    orm:
        #Replace repository factory
        repository_factory: 'MyBundle\Factory\RepositoryFactory'

services:
    MyBundle\Factory\RepositoryFactory:
        arguments: [ '@router', '@translator', '%kernel.secret%' ]

添加您的 MyBundle/Factory/RepositoryFactory.php 文件:

<?php declare(strict_types=1);

namespace MyBundle\Factory;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
use Doctrine\Persistence\ObjectRepository;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * This factory is used to create default repository objects for entities at runtime.
 */
final class RepositoryFactory implements RepositoryFactoryInterface {
        /**
         * The list of EntityRepository instances
         *
         * @var ObjectRepository[]
         */
        private $repositoryList = [];

        /**
         * The kernel secret
         *
         * @var string
         */
        private $secret;

        /**
         * The RouterInterface instance
         *
         * @var RouterInterface
         */
        private $router;

        /**
         * The TranslatorInterface instance
         *
         * @var TranslatorInterface
         */
        private $translator;

        /**
         * Initializes a new RepositoryFactory instance
         *
         * @param RouterInterface $router The router instance
         * @param TranslatorInterface $translator The TranslatorInterface instance
         * @param string $secret The kernel secret
         */
        public function __construct(RouterInterface $router, TranslatorInterface $translator, string $secret) {
                //Set router
                $this->router = $router;

                //Set secret
                $this->secret = $secret;

                //Set translator
                $this->translator = $translator;
        }

        /**
         * {@inheritdoc}
         */
        public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository {
                //Set repository hash
                $repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_hash($entityManager);

                //With entity repository instance
                if (isset($this->repositoryList[$repositoryHash])) {
                        //Return existing entity repository instance
                        return $this->repositoryList[$repositoryHash];
                }

                //Store and return created entity repository instance
                return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName);
        }

        /**
         * Create a new repository instance for an entity class
         *
         * @param EntityManagerInterface $entityManager The EntityManager instance.
         * @param string $entityName The name of the entity.
         */
        private function createRepository(EntityManagerInterface $entityManager, string $entityName): ObjectRepository {
                //Get class metadata
                $metadata = $entityManager->getClassMetadata($entityName);

                //Get repository class
                $repositoryClass = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName();

                //Return repository class instance
                //XXX: router, translator and secret arguments will be ignored by default
                return new $repositoryClass($entityManager, $metadata, $this->router, $this->translator, $this->secret);
        }
}

然后定义你的 MyBundle/Repository/EntityRepository.php:

<?php declare(strict_types=1);

namespace MyBundle\Repository;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository as BaseEntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * EntityRepository
 *
 * {@inheritdoc}
 */
class EntityRepository extends BaseEntityRepository {
        /**
         * The RouterInterface instance
         *
         * @var RouterInterface
         */
        protected RouterInterface $router;

        /**
         * The table keys array
         *
         * @var array
         */
        protected array $tableKeys;

        /**
         * The table values array
         *
         * @var array
         */
        protected array $tableValues;

        /**
         * The TranslatorInterface instance
         *
         * @var TranslatorInterface
         */
        protected TranslatorInterface $translator;

        /**
         * The kernel secret
         *
         * @var string
         */
        protected string $secret;

        /**
         * Initializes a new LocationRepository instance
         *
         * @param EntityManagerInterface $manager The EntityManagerInterface instance
         * @param ClassMetadata $class The ClassMetadata instance
         * @param RouterInterface $router The router instance
         * @param TranslatorInterface $translator The TranslatorInterface instance
         * @param string $secret The kernel secret
         */
        public function __construct(EntityManagerInterface $manager, ClassMetadata $class, RouterInterface $router, TranslatorInterface $translator, string $secret) {
                //Call parent constructor
                parent::__construct($manager, $class);

                //Set secret
                $this->secret = $secret;

                //Set router
                $this->router = $router;

                //Set slugger
                $this->slugger = $slugger;

                //Set translator
                $this->translator = $translator;

                //Get quote strategy
                $qs = $manager->getConfiguration()->getQuoteStrategy();
                $dp = $manager->getConnection()->getDatabasePlatform();

                //Set quoted table names
                //XXX: remember to place longer prefix before shorter to avoid strange replacings
                $tables = [
                        'MyBundle:UserGroup' => $qs->getJoinTableName($manager->getClassMetadata('MyBundle:User')->getAssociationMapping('groups'), $manager->getClassMetadata('MyBundle:User'), $dp),
                        'MyBundle:Group' => $qs->getTableName($manager->getClassMetadata('MyBundle:Group'), $dp),
                        'MyBundle:User' => $qs->getTableName($manager->getClassMetadata('MyBundle:User'), $dp),
                        //XXX: Set limit used to workaround mariadb subselect optimization
                        ':limit' => PHP_INT_MAX,
                        "\t" => '',
                        "\n" => ' '
                ];

                //Set quoted table name keys
                $this->tableKeys = array_keys($tables);

                //Set quoted table name values
                $this->tableValues = array_values($tables);
        }
}

然后在 MyBundle/Repository/UserRepository.php 中简单地扩展它:

<?php declare(strict_types=1);

namespace MyBundle\Repository;

/**
 * UserRepository
 */
class UserRepository extends EntityRepository {
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-04
    • 1970-01-01
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 2014-11-02
    • 1970-01-01
    相关资源
    最近更新 更多