Как настроить ленивую загрузку с помощью ZF3 (без шаблона ServiceLocator откуда угодно)

Я пишу новое приложение ZF2. Я заметил, что шаблон использования ServiceLocator для вызова служб «из любого места» устарел в ZF3. Я хочу написать код для ZF3.

Я смог настроить свой контроллер для вызова всех зависимостей во время конструктора. Но это означает загрузку объекта Doctrine до того, как он мне понадобится.

Вопрос

Как мне настроить его так, чтобы он загружался только тогда, когда мне это нужно немедленно? (ленивая загрузка). Я понимаю, что ZF3 перемещает загрузку в конструкцию контроллера, что делает неясным, как загружать что-то точно в срок.

Старый код

class CommissionRepository
{

    protected $em;

    function getRepository()
    {
        //Initialize Doctrine ONLY when getRepository is called
        //it is not always called, and Doctrine is not always set up
        if (! $this->em)
            $this->em = $this->serviceLocator->get('doctrine');
        return $this->em;
    }
}

Текущий код после рефакторинга шаблона ServiceLocator

class CommissionRepository
{

    protected $em;

    function getRepository()
    {
        return $this->em;
    }

    function setRepository($em)
    {
        $this->em = $em;
    }

    function useRepository($id)
    {
        return $this->em->find($id);
    }
}


class CommissionControllerFactory implements FactoryInterface
{

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $parentLocator = $controllerManager->getServiceLocator();

        // set up repository
        $repository = new CommissionRepository();
        $repository->setRepository($parentLocator->get('doctrine'));

        // set up controller
        $controller = new CommissionController($repository);
        $controller->setRepository();

        return $controller;
    }
}

class CommissionController extends AbstractActionController
{

    protected $repository;

    public function setRepository(CommissionRepository $repository)
    {
        $this->repository = $repository;
    }

    public function indexAction()
    {
         //$this->repository already contains Doctrine but it should not
         //I want it to be initialized upon use.  How?
         //Recall that it has been set up during Repository construction time
         //and I cannot call it from "anywhere" any more in ZF3
         //is there a lazy loading solution to this?
         $this->repository->useRepository();
    }

person dennismv    schedule 01.05.2016    source источник
comment
Я думаю, вы ищете ленивые сервисы: framework.zend.com/manual/current/en/modules/   -  person marcosh    schedule 01.05.2016
comment
В дополнение к руководству стоит прочитать руководство по миграции сервис-менеджера ZF3 -› zend-servicemanager.readthedocs.io/en/latest/migration/   -  person Crisp    schedule 02.05.2016


Ответы (1)


Если у вас нет веской/веской причины для создания экземпляра пользовательского репозитория сущностей, вам следует предпочесть расширение Doctrine\ORM\EntityRepository в ваших репозиториях, таких как CommissionRepository. Например;

use Doctrine\ORM\EntityRepository;

class CommissionRepository extends EntityRepository
{
    // No need to think about $em here. It will be automatically
    // injected by doctrine when you call getRepository().
    // 
    function fetchCommissionById($id)
    {
        // You can easily get the object manager directly (_em) or
        // using getEntityManager() accessor method in a repository
        return $this->_em->find($id);
    }
}

Таким образом, менеджер сущностей будет автоматически внедряться в репозиторий при построении, когда вы вызываете метод $em->getRepository('App\Entity\Commission').

Я предполагаю, что у вас уже есть объект Commission в пространстве имен Entity вашего приложения:

<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repo\CommissionRepository")
 * @ORM\Table
 */
class Commission
{
}

Затем вы можете упростить процесс внедрения репозитория на своей фабрике примерно так:

// ZF2 Way
class CommissionControllerFactory implements FactoryInterface
{

    public function createService(ServiceLocatorInterface $services)
    {
        $em = $services->getServiceLocator()->get('doctrine');
        $repository = $em->getRepository('App\Entity\Commission');

        return new CommissionController($repository);
    }
}

ОБНОВЛЕНИЕ. С выпуском Service Manager V3 FactoryInterface был перемещен в пространство имен Zend\ServiceManager\Factory (1), фабрики буквально вызываются (2) и работают с любым container-interop совместимый DIC (3) Обновленная фабрика будет выглядеть следующим образом:

// ZF3 Way
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
use Doctrine\ORM\EntityManager;

class CommissionControllerFactory implements FactoryInterface
{

    public function __invoke(ContainerInterface $dic, $name, array $options = null) {
        $em = $dic->get(EntityManager::class);
        $repository = $em->getRepository('App\Entity\Commission');

        return new CommissionController($repository);
    }
}

На вопрос; как сказал Маркош, Lazy Services способ пойти на создание услуг, когда это нужно немедленно. После выпуска ZF3 будет использовать компонент zend-servicemanager 3.0. (В настоящее время zend-expressive использует его) Начиная с servicemanager v3 вы можете создать некоторые прокси-сервисы, определив lazy_services и делегаторы в конфигурации службы:

'factories' => [],
'invokables' => [],
'delegators' => [
    FooService::class => [
        FooServiceDelegatorFactory::class,
    ], 
],
'lazy_services' => [
    // map of service names and their relative class names - this
    // is required since the service manager cannot know the
    // class name of defined services up front
    'class_map' => [
        // 'foo' => 'MyApplication\Foo',
    ],

    // directory where proxy classes will be written - default to system_get_tmp_dir()
    'proxies_target_dir' => null,

    // namespace of the generated proxies, default to "ProxyManagerGeneratedProxy"
    'proxies_namespace' => null,

    // whether the generated proxy classes should be written to disk or generated on-the-fly
    'write_proxy_files' => false,
];

Кроме того, начиная с Service Manager v3 фабрики совместимы с ContainerInterface. Для прямой совместимости вы можете сохранить методы __invoke() и createService() на своих фабриках для плавной миграции.

В итоге ваша совместимая с ZF3 фабрика может выглядеть так:

class CommissionControllerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $name, array $options = null)
    {
        $em = $container->get('doctrine');
        $repository = $em->getRepository('App\Entity\Commission');

        return new CommissionController($repository);
    }

    public function createService(ServiceLocatorInterface $container, $name = null, $requestedName = null)
    {
        return $this($container, $requestedName, []);
    }
}

Надеюсь, поможет.

person edigu    schedule 08.06.2016