Ответы пользователя по тегу Doctrine ORM
  • Как исключить старые таблицы?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Игнорируемые таблицы можно указать так:
    https://symfony.com/bundles/DoctrineMigrationsBund...

    Если у вас не симфони, а просто Доктрина, то вот так (это тоже самое, что в Симфони-билде и делается):
    https://github.com/doctrine/migrations/blob/e542ad...

    UPD: у вас корректный способ указан. Видимо дело в кеше или чем-то таком
    Ответ написан
  • Как добавить ключ в Symfony/Doctrine (миграция)?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Вы проделываете много работы магическими генерирующими командами, игнорируя результат (сами файлы мигарций и их инструкций)

    У вас не проходит одна миграция — видимо, вы ее уже пытались выполнить, выполнилась част ьпо созданию таблицы и все упало. Надо фиксануть руками — сравнить миграцию и состояние БД и все наладить.

    Тут еще подводный камень, тк миграция старая ен выполнена, то возможно, чт ов ней часть иснтрукций будет конфликтовать с последующими генерациями, которые вы генерируете — тк состав миграции не смотрит на инструкции в предыдущих, а просто на основании схемы в БД и в коде. Тогда как старая битая миграция имеет часть инструкций, которые сломают схему БД к моменту работы следующей

    Совет: просто посмотрите, чего не работает миграция, приведите БД руками к нужному состоянию или миграции к новому виду.
    Ответ написан
    Комментировать
  • Как обновить вложенный объект в doctrine?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Да, ваши поля сущности должны быть иммутабельными, чтобы все корректно работало.
    Делать поле иммутабельным и работать с сущностью через иммутабельность — есть элегантный способ

    // Вариант 1
    $user->setCounter(clone $user->getCounter());


    Доработайте в
    $user->raiseCount();

    // User.php
    
    public function raiseCount(): void
    {
         $this->counter = $this->counter->add();
    }


    // Counter.php
    
    public function add(): self
    {
         $self = clone $this;
         ++$self->value;
    
         return $self;
    }
    Ответ написан
    Комментировать
  • Можно ли использовать сервисы в rich моделях?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    а? Если нет и нужно все равно писать абсолютно всю логику в обьекте модели, то тогда он станет просто god обьектом на 100500 строчек

    Потому что вы все делаете наоборот!

    Пишите по бизнес-процессу ваши UseCase, это некоторый хэндлер к примеру
    Пример
    final class HandleCheckOutShoppingCart
    {
        public function __construct(Carts $carts, PaymentGateway $gateway)
        {
            $this->carts   = $carts;
            $this->gateway = $gateway;
        }
    
        public function __invoke(CheckOutShoppingCart $command) : void
        {
            $shoppingCart = $this->carts->get($command->shoppingCart());
    
            $payment = $this->gateway->captureCharge($command->charge());
    
            $shoppingCart->checkOut($payment);
        }
    }


    Потом у вас вырисовываются границы сущности, и в сущностях уже инварианты, которые контролирует эта сущность. Никаких god object
    Ответ написан
    5 комментариев
  • Есть ли особенности работы Doctrine в долгоживущих приложениях?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Как такое может быть?

    Если вы достаете сущность по id, то она загружается один раз и потом не обращается к БД, следя за изменениями сущности из приложения
    Предлагаю очищать EM после каждой обработки задачи через $em->clear()

    Sometimes you want to clear the identity map of an EntityManager to start over. We use this regularly in our unit-tests to enforce loading objects from the database again instead of serving them from the identity map. You can call EntityManager#clear() to achieve this result.
    https://www.doctrine-project.org/projects/doctrine...


    Чтение можно в обход делать, изменение сущностей лучше не делать в обход EM

    Консьюмеру не хватате параметра какого - то?

    Он считает, что с сущностью вы работаете из приложения, а не в обход
    Ответ написан
    1 комментарий
  • Чем отличается EntityManager->clear() от Doctrine->resetManager()?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Я вспомнил, течь в Доктрине может из-за SQL Logger, который в Connection
    Когда вы делаете resetManager() — вы убиваете и этот SQL Logger с его объектами в памяти

    Вам нужно сделать что-то типа
    $this->em->getConnection()->getConfiguration()->setSQLLogger(null);


    Это можно сделать на уровне настройки всего контейнера или сервиса, или в команде
    Но попробуйте для начала в своей консольной команде!
    Ответ написан
    Комментировать
  • Как в Doctrine, в DBAL указать PDO::ATTR_PERSISTENT => true?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Ентити-менеджер использует один объект Connection, Connection использует объект PDO, всегда везде одно подключение и работает во всем жизненном цикле приложения, в этом и суть работы (ну в том числе) DI контейнера — отдавать один и тот же объект и не плодить их. То есть на жизненный цикл приложения все и так работает

    Тесты функциональные? Может плодится много http запросов в разных процессах? Но если и так — то все равно процессы разные и объекты PDO разные, так что ваша настройка не сработает, тк просто сами приложения будут разные...

    Для таких случаев в Codeception есть такая штука как persistService, по идее сервис (в вашем случае EntityManager, если через него работаете, или Connection, если через DBAL работаете) должен один идти через эту штуку единый, даже если разные http запросы
    https://codeception.com/docs/modules/Symfony#persi...

    Крч, раскройте немного деталей ваших тестов и ситуации, может я все не туда пишу
    Ответ написан
  • Как использовать Doctrine для сохранения связанных сущностей?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Persistence by Reachability: Cascade Persist
    There are additional semantics that apply to the Cascade Persist operation. During each flush() operation Doctrine detects if there are new entities in any collection and three possible cases can happen:

    • New entities in a collection marked as cascade: persist will be directly persisted by Doctrine.
    • New entities in a collection not marked as cascade: persist will produce an Exception and rollback the flush() operation.
    • Collections without new entities are skipped.

    https://www.doctrine-project.org/projects/doctrine...

    /**
     * @OneToMany(targetEntity="Article", mappedBy="topic", cascade={"persist", "remove"})
     */
     private $articles;
    Ответ написан
    Комментировать
  • Гидрация сущности из запроса. Что использовать?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    Используйте Symfony Serializer Component, а именно встроенные ObjectNormalizer или GetSetMethodNormalizer, раз решили через сеттеры/геттеры с сущностями работать

    $obj = $this->normalizer->denormalize($data, YourEntity::class);
    Ответ написан
    2 комментария
  • Как в Symfony загрузить DTO в Entity?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Вообще это плохая практика — маппить на сущность дто.

    Почему: сущность — некоторый бизнес-обьект, он контролирует переходы состояния и инкапсулирует саму логику. Но сущность находится в контексте... бизнес-процесса, который выражен некоторым др видом классов (интеракторы или use cases, если терминами Боба Мартина), например в CQRS таким родом классов являются хэндлеры команд.
    Итог: с сущностями напрямую скорее не стоит работать, тем более в сущностях не должно быть сеттеров :) и методов превращения данных из дто в явном виде ака fromDto(), как указал Flying (при всем моем уважении), тк это не бизнес-логика, а некоторая транспортно-приложенческая... Статические конструкторы могут быть, но не для дто, а для определенных данных, отображающих бизнес-возможность.

    Кроме того, ваше решение чревато высокой связанностью — ваши сущности конструируются под дто, дто под сущности...

    Как делать хорошо:
    Сущности создавать только в рамках бизнес-процесса, то есть всегда явно и напрямую через конструктор или фабричный метод, воплощающий в себе бизнес-логику.
    Ответ написан
    5 комментариев
  • Symfony - Repository Как избежать двойного кода в этом случае?

    Maksclub
    @Maksclub
    maksfedorov.ru
    flush() нужно вынести, потому что вы можете заперсистить 100 объектов и сделать один flush — одна череда обращений к Бд. В вашем же случае будет 100 и более обращений к БД

    Чтобы сделать универсальный метод для персиста в репозиториях, раз уж вы решили так делать (многие считают это верным) — можно сделать универсальный код (трейт к примеру), в котором будет проверка через classMetadata, что сохраняемый объект нужного типа для репозитория (тут не забываем про наследование сущностей, is_a()и вот это все) и если придет не тот объект — кидать InvalidArgumentException

    НО! Если вы репозитории размещаете в своем домене/пакете/неймпсейсе, то вам не избежать дублирования кода, и это не так уж и страшно... Связанность и паутина наследований страшнее. Репозиториев все равно мало во всем коде относительно всей базы кодовой.
    Ответ написан
    2 комментария
  • Как подключить Doctrine в свой проект?

    Maksclub
    @Maksclub
    maksfedorov.ru
    По мануалу создание EM:
    Официальная дока простая же: https://www.doctrine-project.org/projects/doctrine...

    Создаётся подключение, создаётся массив с путями, где лежат сущности и на основе этой инфы создаётся EntityManager

    На примере Slim создание и регистрация EM:
    Можете подсмотреть как тут настраиваюсь доктрину.
    Да, в контексте контейнера, но вы увидите как это все не сильно отличается от оригинального мануала:
    www.slimframework.com/docs/v3/cookbook/database-do...

    Репозитории:
    Репозитории в доктрине либо общие (с ограниченным интерфейсом EntityRepository), либо создаются фабрикой вами или вообще руками как по последней ссылке... Если работали с симфони, то вы знаете, что надо было зарегать репы...
    Ответ написан
  • Как правильно в доктрине построить запрос для фильтра со сложными условиями?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Вы в цикле для разных фильтров добавляет в один объект QueryBuilder параметры с одним ключом values, потом при сборке запроса Doctrine достает первый найденный Parameter с ключом values (а их таких несколько, но ей достаточно первого).

    Код Доктрины:
    5f21da43a9cea902909869.png

    То есть для цвета, года, уровня — всегда достается первый параметр с ключом values

    Предлагаю подставлять вместо этого слова разный алиас, например key:
    [$normalizedKey, $key] = $this->getKeys($qb);
    
    $qb
    	->andWhere(sprintf('%s IN (:%s)', $normalizedKey, $key))
    	->setParameter($key, $this->getValue());
    Ответ написан
    1 комментарий
  • Закон Деметры и Doctrine?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    то как быть с ситуацией, когда в некоторых модулях должны производиться сложные выборки с фильтрами, агрегацией, затрагивающие сущности (entity) других модулей?


    Для решения задач в другом модуле/домене нет необходимости иметь сущности др проекта/модуля/бандла. Пример в модуле доставки
    • не нужно знать ВСЕ о товаре и логике его сбора, нужно знать размеры и название
    • в этом модуле не нужно знать и о заказе все — только адрес, время. История его обработки и статусы (все это есть в агрегате/сущности Заказ), влияние акций и промо — это знать не нужно, но оно есть!... передавая сущность куда-то вы передаете знания и связываете разные модули
    • не нужно знать о пользователе много, кроме телефона, имени и адреса


    Потому вы можете из БД выбирать только необходимые данные конкретному модулю. Этакие проекции по потребностям. Это есть соблюдение закона Деметры — вы не лезете в другой модуль, вы лезете в инфраструктуру (вселенную), которая даст вам ровно те знания и состояние, которые нужны . То есть за своими данными.. Более того эти знания могут собираться различными способами и не обязательно из реляций доставаться, а могут быть агрегированные в NoSQL виде к примеру.

    Крч для одного модуля вы получаете некоторые проекции/данные, которые в этом модуле вы даже не скажете — откуда они пришли, тк нет этих знаний — из персистенс-слоя и все...
    Ответ написан
    Комментировать
  • Как получить товар с последней ценой?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Ответ по теме:
    • или сделайте поле lastPrice и храните последнюю цену там (ну или через кастомный гидратор ее в поле продукта вставляйте)
    • или выбирайте необходимые данные голым SQL/DQL в DTO объекты,
    • или на крайний — отдельно выбирайте последние цены для ваших товаров, но тут по сути некоторое подобие второго варианта


    Получать последнюю цену в массив цен — некорректная и очень поганая задумка, даже если у вас получится сделать это. Вы получите "битый" бизнес-объект и вообще все это плохо и не удобно и не правильно.

    Совет по запросу:
    Лучше сделайте не вложенным запросом, а через джойн
    SELECT product.*, last_prices.*
    FROM product
    LEFT JOIN (
         SELECT product_id AS id, max(id) as price_id
         FROM price
         GROUP BY product_id
    ) last_prices  ON last_prices.id=product.id
    Ответ написан
    Комментировать
  • Почему не добавляет таблицу в БД после миграции?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Ошибка в миграции Version20200628093518
    Она не выполняется из-за того, что в ее инструкции описано добавление поля, которое уже есть в БД...
    [error] Migration DoctrineMigrations\Version20200628093518 failed during...


    Не известно, добавляется ли ваша таблица в этой же миграции или в следующих, но эта ошибка останавливает дальнейшие инструкции (в том числе создание вашей таблицы)

    Вам нужно привести БД в консистентный вид с миграциями таким образом, что схема бд должна совпасть с выполненными инструкциями, которые есть в таблице с отмеченными миграциями
    Ответ написан
    8 комментариев
  • Как правильно спроектировать репозиторий для использования с необязательными параметрами?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Изящно решает паттерн Criteria/Specification.
    Doctrine Specification Pattern или ваш реюзабельны...

    Он может быть сложным, но вашу задачу решает элегантно. Благо он уже упакован в библиотечку, которую вы найдете по ссылке выше.
    Ответ написан
    1 комментарий
  • Doctrine как разрешить состояние гонки?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Помимо варианта с Lock: ON CONFLICT (expression) DO NOTHING и похожие решения в других СУБД могут быть иногда довольно подходящими в виду простоты реализации...

    Это выглядит буквально вот так:
    $sql = <<<SQL
    INSERT INTO table (id, value) 
    VALUES (555, \'uniqValue\') 
    ON CONFLICT (value) DO NOTHING
    SQL;
    
    $this->em->getConnection()->executeQuery($sql);


    Если это некие логи или словари с простой логикой хранения, то в самый раз...
    Ответ написан
    Комментировать