Pull to refresh
29
0
Пётр Грибанов @ghost404

Symfony professional developer

Send message

Всё просто.
Если вашей сущности не хватает данных для проверки бизнес ограничения вы можете:
А) передать ей эти данные в аргументе функции
Б) вынести проверку бизнес ограничения в сервис доменного слоя


У Вернона есть упоминание о сервисах доменного слоя. Рекомендую почитать.

Я видел уже эту схему, но она мне показалась странной. Я ее не понял и прошел мимо.
Теперь я понял эту архитектуру.
Спасибо вам за это.
Правда мне всё равно она кажется немного странной.
Мне больше по душе классическая архитектуру DDD приложений + CQRS.

Всё здорово, но этого ещё нет в LTS. Со следующего года можно внедрять.


Я все еще не понимаю почему ты считаешь такой подход "неправильным".

Я уже исправился. Я уже не считаю его "неправильным". Он просто мне не нравится.

и откуда берется ids?

И зачем ты дурачком прикидываешся? Прекраснож понимаешь откуда.


 Тегетируем наши сервисы репозитории и возвращаем в фабрике.

Естественно ids мы получаем из Compiler Passes.
Если тебе нужны зависимости в репозитории, то ты в любом случае должен объявить их как сервисы. А чтоб можно было их получить из EntityManager, мы просто добавляем им метку.

А зачем много фабрик? Одной достаточно. Кода то всего на 10 строчек.


public function getRepository(EntityManagerInterface $entity_manager, $entity_name)
{
    $class = $entity_manager->getClassMetadata($entity_name)->getName();

    if (isset($this->ids[$class])) {
        return $this->container->get($this->ids[$class]);
    }

    return $this->default->getRepository($entity_manager, $entity_name);
}

Эээ… Вы читать не умеете? Я уже тысячу раз сказал, что я не хочу давать пользователю интерфейс ObjectRepository. На каком ещё языке мне это сказать чтоб вы меня поняли? Может на белорусском?

А что тут сложного?
Берём и реализовывает свой RepositoryFactory для доктрины. Тегетируем наши сервисы репозитории и возвращаем в фабрике.


Вот простейший пример реализации фабрики репозиториев.

Хотя мы можем добавить интерфейс ObjectRepository к DoctrineArticleRepository и не добавлять его к ArticleRepository и таки образом не будем нарушать контракт, но смысла в этом особого нет, так как создаст ненужные, скрытые, пустые методы в репозитории.

что меня не устраивает от слова совсем.

Ну это ваши личные трудности. Меня тоже много чего не устраивает и что с того?


только через setter injection, что опять же меня не устраивает.

В смысле? Использовать constructor injection религия не позволяет?


не чувствую. Information hiding надо рассматривать исключительно с точки зрения клиента

Разницы для клиента между двумя реализациями нет. Они обе дают конкретный интерфейс ArticleRepository.


Или вы намекаете что "это чудаки могут где-то заинджектить EntityManager в обход Dependency Injection?

А почему в обход? Если EntityManager вы инжектите в репозиторий, то и в любой другой сервис его можно спокойно инжектить.


Ваш же способ просто позволяет сразу всегда и везде получать инстанс EntityRepository. Вообще никакой изоляции.

Как раз наоборот. Мой вариант позволяет полностью заблокировать возможность получения EntityRepository. Полная изоляция.


Хотя я должен признаться. Я забыл что метод EntityManagerInterface::getRepository() должен возвращать ObjectRepository. Он может возвращать и другие объекты, но это будет нарушением контракта.


Так что мой вариант нарушает контракт и ваше решение правильней, хотя я считаю его неприемлемым, от слова совсем.

А что тут представлять?
Если компания хочет выходить на мировой рынок, то ей нужно переводить на другие языки:


  • Документациию к своему оборудованию или ПО
  • UI программного обеспечения
  • UI сайта

Стандартом для Российских компаний является наличие документов на русском и английском языках. Следующим как правило идут немецкий, французский и итальянский.
Если компания хочет развивать свою деятельность на востоке, то к этому списку очень быстро добавляются такие специфические языки как японский, китайский, корейский и арабский.
Всё делается для пользователей.
Вам ведь легче читать инструкцию на русском языке чем на английском, по например, эксплуатации стиральной машины произведенной в Японии, японский компанией.

нас интересовать должно не это, а information hiding. Мы не добавлять должны методы а изолировать текущее. Все методы "инфраструктурные" должны быть приватными и изолированы красивым интерфейсом исключающим "неправильное использование".

Вот с изоляцией то у вас и проблемы.
Вместо того чтобы сделать один репозиторий с единой точкой доступа. Вы создали второй репозиторий и создали вторую точку доступа к данным и старательно пытаетесь оправдаться инверсией зависимостей и интерфейсом доменного слоя.


А вы не думали что доктрина может возвращать вам репозиторий реализацующий интерфейс доменного слоя?


$em->getRepository(Article::class) instanceof ArticleRepository == true;

Вы можете все так же использовать зависимости в репозитории. Все также можете внедрять репозиторий как зависимость, но ещё вы можете получать его из EntityManager-а. И точка доступа к данным у вас в этом случае одна (если не брать в расчет прямые запросы к EntityManager и Connection).


use GuzzleHttp/Client;

class DoctrineArticleRepository implements ArticleRepository
{
    private $em;

    private $client;

    public function __construct(EntityManagerInterface $em, Client $client)
    {
        $this->em = $em;
        $this->client = $client;
    }

    public function get(ArticleId $id): Article
    {
        $article = $this->em->find(Article::class, $id);
        if (!$article instanceof Article) {
            throw new \RuntimeException();
        }
        return $article;
    }

    public function add(Article $article): bool
    {
        $response = $this->client->request(
            'put',
            sprintf('/article/%s/', $article->id()),
            ['body' => $article->text]
        );

        return $response->getStatusCode() == 201;
    }
}

Условный пример использования в зависимостях сервиса доменного слоя


class ArticleService
{
    private $rep;

    public function __construct(ArticleRepository $rep)
    {
        $this->rep = $rep;
    }

    public function createWithText(
        ArticleId $id,
        ArticleText $text,
        ArticleEditor $editor
    ): bool {
        return $this->rep->add(new Article($id, $text, $editor);
    }
}

Чувствуете разницу? Нет лишней прослойки. Вот это и есть information hiding, а не то что вы предлагаете.


И это решение, в отличии от вашего, ни сколько не нарушает использование устоявшегося и нормального способа получения репозитория из EntityManager.
Поди объясни новичку, что то, что он привык делать годами у вас делается через… иначе.


Да. Использовать EntityManager для получения репозитория не всегда хорошо, а иногда и плохо, но это лучше чем возможность получить через него репозиторий который вскрывает все кишки наружу, а не репозиторий реализующий интерфейс доменного слоя.


Сами сказали:


красивым интерфейсом исключающим "неправильное использование".

Читайте мой комментарий выше.

Давайте не будем уходить от темы статьи. Мы здесь обсуждаем Doctrine Specification.
Вернёмся к первоисточнику.


Let your repositories extend Happyr\DoctrineSpecification\EntitySpecificationRepository instead of Doctrine\ORM\EntityRepository.

Это значит что вы должны создать класс \App\Infrastructure\Doctrine\DoctrineSpecificationArticleRepository для работы со спецификациями.
Вот об этих двух репозиториях на инфраструктурном слое я говорю.


Особенно это актуально если вы хотите в репозиторий добавить какие-то методы которых вам не хватает в основном репозитории. Для меня это countOf().


Да. Мы можем изменить базовый репозиторий для Doctrine. И даже можем создать свой базовый репозиторий наследующийся от EntitySpecificationRepository и добавить в него нужные нам методы. А что если нам нужно добавить методы не в общий репозиторий и не в репозиторий DoctrineArticleRepository реализующий доменный интерфейс, а нужно создать именно DoctrineSpecificationArticleRepository? Мы получим два репозитория на инфраструктурном слое.

Ну так, Fesor совершенно четко написал \App\Infrastructure\Doctrine\DoctrineArticleRepository. То есть это репозиторий на инфраструктурном уровне.
Про ArticleRepository я ничего не говорил. Понятное дело что он на доменном слое.

Один репозиторий завернули в другой репозиторий.
Два репозитория на инфраструктурном уровне для одной сущности.
Да вы батенька, мастер извращений.

Мы вроде говорим о Doctrine Specification. В Doctrine Specification ты вынужден использовать реализацию репозитория с публичными методами на подобии match().
Согласен, лучше если интерфейс репозитория максимально чист и полностью принадлежит тебе, но в контексте Doctrine Specification для того чтоб этого добиться этого, придется переопределять уровень доступа для всех методов в каждом репозитории


class ArticleRepositoryDoctrine implements ArticleRepository
{
    use EntitySpecificationRepositoryTrait {
        match as private;
        matchSingleResult as private;
        matchOneOrNullResult as private;
        getQuery as private;
        setAlias as private;
        getAlias as private;
    }

    // ...
}

и еще придется реализовывать в каждом репозитории аналог EntityRepository::createQueryBuilder(), только приватный.

Зачем findBySpecification если есть match?

Это RulerZ. Я знаю о нем и о нем уже упоминали ниже. Мне он как-то не очень нравится.
Надо будет при случае по глубже изучить.

Натыкался на реализацию спецификаций на .NET через Expression.
Я не знаком с .NET, но на сколько я могу судить, такие спецификации могут как проверять конкретный инстанс, так и могут быть трансформированы в SQL.
Интересно былоб увидеть что-то подобное в PHP.

Information

Rating
Does not participate
Location
Россия
Registered
Activity