Ответы пользователя по тегу Symfony
  • Symfony Eventlistener идет зацикливание?

    @Flying
    Вы вызываете EntityManager::flush() внутри обработчика события, который сам вызывается в процессе работы
    EntityManager::flush() (а точнее UnitOfWork::commit()), разумеется вы получаете бесконечный цикл.

    Корректным подходом будет реализация, которая накапливает информацию об изменениях и передаёт их для дальнейшего сохранения. Это можно сделать с помощью Doctrine event listener'а, который содержит в себе обработчики следующих событий:
    1. preFlush
    2. postPersist, postUpdate, postRemove
    3. postFlush


    Также, хотя механизмы сохранения изменений могут быть разными, с точки зрения уменьшения времени обработки запроса, логично отложить сохранение изменений "на потом", воспользовавшись поддержкой асинхронных очередей в Symfony Messenger.

    Таким образом ваш обработчик должен действовать примерно следующим образом:
    1. На событие preFlush вы очищаете внутреннюю переменную (например private array $changes = []), в которой будет
      накапливаться информация об изменениях
    2. Обрабатывая события postPersist, postUpdate, postRemove вы сохраняете в $changes
      информацию об изменениях, которую хотите сохранить
    3. В обработчике postFlush вы передаёте накопленные изменения, формируя message, для которого настроен роутинг в async транспорт. Обработчик этого message сможет спокойно сохранить все накопленные изменения, не мешая runtime коду.

    Ответ написан
    2 комментария
  • Как отрефакторить классы с неоднородными конструкторами?

    @Flying
    Я бы использовал service locator для формирования списка сервисов и передавал его в конструктор фабрики.

    Альтернативно можно определить фабрику как service subscriber, тогда формирование service locator'а будет, возможно, ещё проще.

    Если же каким-то образом (например через общий интерфейс или статический метод класса-фабрики), определить список сервисов, которые умеет создавать фабрика, то можно создать compiler pass, который будет формировать service locator на этапе компиляции контейнера через анализ их зависимостей. Это чуть сложнее, но не потребует править код при расширении списка создаваемых фабрикой классов.
    Ответ написан
    Комментировать
  • В чем разница между ModelTransformer и ViewTransformer?

    @Flying
    Информация об этом есть в документации.

    Если вкратце - разница состоит в том, как будет использоваться результат трансформации.
    • ModelTransformer преобразует данные элемента формы для сохранения в модели или получения данных из модели в форму,
    • ViewTransformer преобразует данные элемента формы для рендера во view или получения данных из запроса в форму,
    Ответ написан
    Комментировать
  • Особенности по миграциям в Symfony?

    @Flying
    В Symfony по-умолчанию используется Doctrine, для неё есть Doctrine Migrations.

    Поскольку Doctrine - data mapper, то вам не нужно "добавить к таблице колонку / индекс / поменять тип колонки", вместо этого вам нужно менять свои entities и mappings для них (подробнее здесь), а затем создать для неё миграцию, используя команду doctrine:migrations:diff для Symfony console. Т.к. команда работает на сравнении схем базы данных - в итоге вы получите миграцию с SQL скриптами для применения и отката изменений, внесённых в ваши mappings.
    Ответ написан
    6 комментариев
  • Как проверить mx при валидации email через аннотации?

    @Flying
    checkMX был объявлен deprecated в 4.2 и удалён в 5.0.

    Можно сделать свою реализацию, использовав в качестве основы код из 4.2, но при этом нужно будет учесть причины по которым этот код был убран (1, 2)
    Ответ написан
    Комментировать
  • Есть ли конвертер аннотаций в атрибуты?

    @Flying
    Посмотрите на Rector, он специально предназначен для подобных манипуляций. Есть отдельные пакеты с правилами для Symfony и Doctrine, но вероятно придётся дописать какие-то правила. Ну и статья в тему.

    Прямо совсем автоматически всё, скорее всего, не переведёте хотя бы потому что полноценной замены для сложных аннотаций Doctrine сейчас на атрибутах нет из-за отсутствия поддержки вложенности для атрибутов. Не отслеживал что по этому поводу решили разработчики Doctrine, стоит поинтересоваться.

    Да, и Symfony 5.2 уже вышла из поддержки, нужно целиться в 5.4
    Ответ написан
    Комментировать
  • Как сделать общий сервис во всех контроллерах?

    @Flying
    Простейший вариант - написать свой compiler pass, в нём выбрать сервисы (через findTaggedServiceIds) по тегу controller.service_arguments и дополнить определение сервисов либо вызовом вашего метода либо добавлением аргумента в конструктор, это можно делать по имени.

    Можно сделать ещё более общее решение: определить интерфейс, например SettingsAwareInterface примерно такого вида:
    namespace App\Contracts;
    
    interface SettingsAwareInterface
    {
        public function getSettings(): Setting;
    
        public function setSettings(Setting $settings): void;
    }

    затем в services.yaml добавить:
    _instanceof:
      App\Contracts\SettingsAwareInterface:
        tags:
          - {name: 'app.settings-aware'}

    и в compiler pass работать с тегом app.settings-aware, это позволит вам передавать данный сервис не только в контроллеры.
    Ответ написан
  • Можно ли как-то теговать сервисы через implements interface?

    @Flying
    Да, можно начиная с версии 3.3, примеры есть в документации. В вашем случае это будет:

    # app/config/services.yml
    services:
        _instanceof:
            MyNamespace\Service\MyInterfaceForTagging:
                tags: ['app.my_tag']


    Также, начиная с версии 5.3, которая выйдет на днях, можно будет использовать для этой же цели атрибуты из PHP8, статья в блоге по этому поводу.
    Ответ написан
    2 комментария
  • Стоит ли использовать ACL в Symfony?

    @Flying
    Ответ на ваш вопрос есть в документации к ACL bundle:

    Using ACL's isn't trivial, and for simpler use cases, it may be overkill. If your permission logic could be described by just writing some code (e.g. to check if a Blog is owned by the current User), then consider using Symfony built-in security voters.

    Поэтому если вашему приложению необходима гибкая и настраиваемая извне (к примеру через админку) система разделения прав - то в использовании ACL нет ничего плохого. Просто в большинстве случаев достаточно более простой логики разделения доступа, а это как раз и решается через security voters.

    Настраиваемость извне подразумевает наличие внешнего источника, в случае Symfony ACL это база данных. Таким образом вы на каждый запрос будете лезть в базу данных что может сказаться на производительности. Кроме того само по себе вычисление доступности того или иного ресурса может быть более сложным, а значит более затратным по времени.

    Ну и, естественно, правильно настроить ACL, для того чтобы закрыть всё что нужно и не закрыть что-то лишнее, может стать весьма нетривиальной задачей.

    Поэтому ответ на вопрос "стоит ли использовать ACL" очевиден: "стоит, если вам в проекте нужен именно ACL".

    На производительности будет сказываться, насколько сильно - опять же зависит от вашего проекта, если там просто CRUD - то возможно влияние будет заметным, но если у вас там не highload - то вероятно это не будет критичным.
    Ответ написан
  • Авторизация разными ключами?

    @Flying
    Security firewalls имеют возможность указания паттерна запроса для своего применения.

    Таким образом вам достаточно определить множество firewall'ов для разных url pattern'ов и для каждого из них настроить разные условия авторизации пользователя.

    Подробности есть в документации.
    Ответ написан
    2 комментария
  • Не удается обновить плагин в composer.json?

    @Flying
    Там ведь прямо написано: у вас установлен Composer 2, а в composer.lock находится symfony/flex версии 1.4.6, работающий только с Composer 1.x.

    Либо откатывайте Composer на 1.x (чего лучше не делать без веских причин) либо обновляйте symfony/flex на совместимую версию, 1.8 или выше.

    Судя по всему этот процесс у вас идёт где-то в CI pipeline или чём-то похожем. Поэтому локальная уствновка пакета не поможет, важно чтобы нужная версия была в composer.lock.
    Ответ написан
  • Как ограничить очередь на выполнение только 200 задач в час?

    @Flying
    В целом Максим дело говорит, но ситуации бывают разными, поэтому есть вероятность что вам действительно нужно уметь влезать в эти лимиты.

    На самом деле, поскольку вы используете Messenger - то вы уже весьма близки к цели, поскольку Messenger перезапускает обработку сообщения в случае неполной обработки. Просто настройте retry_strategy под свои нужды и оно будет работать. Надо только мониторить failed messages.

    Также, если вы можете позволить себе использовать Symfony 5.2 - то в этой версии появился отдельный компонент RateLimiter, который специально предназначен для решения именно таких задач. Сделайте Messenger middleware для того чтобы помечать сообщения на отправку только если это разрешает RateLimiter, а для остальных бросайте RecoverableMessageHandlingException чтобы отменить обработку. Но, опять же, не забудьте про настройки retry strategy чтобы это не приводило к потерянным сообщениям.
    Ответ написан
    5 комментариев
  • Можно ли и нужно ли и нужно ли выносить Symfony форму в сервис или её лучше оставить в контреллере?

    @Flying
    1. В Symfony best practicies рекомендуют, да
    2. Собственно см.п.1. В частности это улучшает поддерживаемость кода, особенно в случае более-менее сложных форм или форм, требующих дополнительной логики инициализации / конфигурирования. Собственно ваш код как раз и использует форму как сервис, попробуйте скопировать содержимое класса CandidateType сюда и сравнить полученный код.
    3. Код обработки формы скорее всего выносить в сервис не стоит т.к. он прямо завязан на преобразование Request => Response, а для этого контроллеры и существуют.
    Ответ написан
    Комментировать
  • Symfony - Как изменить форму Бандла?

    @Flying
    Если форма реализована в виде сервиса - то всё становится довольно просто. Любой сервис имеет идентификатор и вся работа с сервисом идёт по этому идентификатору.

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

    Простейший вариант: сделать свой класс формы, наследованный от оригинала (который, естественно, в конечном итоге должен реализовывать FormTypeInterface) и переопределить в нём метод buildForm() на что-то вроде:
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);
        $builder->remove('nameOfTheFieldToRemove');
    }

    далее, в config/services.yaml вы просто регистрируете свой класс как сервис с идентификатором оригинального сервиса.

    Ситуация может оказаться чуть сложнее в случае если оригинальный сервис формы не имеет публичного идентификатора. Маловероятно что такое произойдёт в случае формы, но если вдруг это так - нужно будет смотреть код формирования контейнера в целевом bundle и писать свой compiler pass для внесения нужных вам изменений.
    Ответ написан
  • Как получить getUser из JWT в контроллере Symfony?

    @Flying
    Метод getUser() есть в AbstractController, так что вы можете просто вызывать его если ваши контроллеры наследуются от AbstractController.

    Альтернативно вы можете получать пользователя через аргумент action метода благодаря UserValueResolver

    Дешифровать в контроллерах вам точно ничего не нужно, почитайте как работает система Security в Symfony. User, после его получения в результате процесса аутентификации, хранится в security токене в том виде в котором вы его туда отдали, в вашем случае - в виде entity.
    Ответ написан
  • Как правильно работать с сторонним api?

    @Flying
    Конечно контроллер - не место для подобных действий.

    Как правильно указал Владимир Коротенко - перенос кода, отвечающего за работу с внешним API в отдельный сервис - это первое что вам нужно сделать. Это действие открывает для вас массу возможностей: подмена сервиса для разных сценариев, использование заглушек для тестов, вызовы вне контроллеров и т.д. Кроме того сам код при этом становится более простым и поддерживаемым, а также лучше соответствует принципам SOLID.

    Если после выделения сервиса вы поймёте что для вас необходима возможность изменения его реализации - важно выделить ключевые методы в интерфейс и использовать именно интерфейс для внедрения зависимостей в те же контроллеры.

    Для реализации самих запросов внутри сервиса логично использовать Symfony HTTP Client.

    После этого у вас возможны различные сценарии в зависимости от того что же этот API делает.

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

    Если вы забираете из API данные для подготовки своего ответа, но сами эти данные меняются не постоянно - логично будет добавить в ваш сервис поддержку кэширования с использованием Symfony Cache.

    Если вы забираете данные из API или отправляете данные туда, но они не зависят от контекста текущего запроса - этот процесс логичнее всего вынести в консольную команду, реализованную через Symfony Console и дёргать её по cron'у.

    Альтернативно, если вы используете Symfony 4.4 или 5.x - вы можете организовать этот процесс через новый Symfony Messenger. Он же будет лучшим вариантом если, к примеру, запросов много или они тяжёлые и вам нужно организовать их распределение.
    Ответ написан
    Комментировать
  • Как победить Undefined index в UnitOfWork при сохранении внутри postRemove?

    @Flying
    В документации к событию postRemove прямо указано что это событие вызывается внутри метода flush(). Таким образом вызывая $this->entityManager->flush() внутри postRemove вы, фактически порождаете потенциально бесконечный цикл, поэтому "Undefined index" на самом деле - наименьшая из ваших проблем :)

    Более корректно будет организовать работу примерно следующим образом:
    1. Вынести логику сохранения изменений в отдельный сервис
    2. Обернуть процесс сохранения изменений в транзакцию
    3. В lifecycle методах не пытаться писать данные сразу, а вместо этого собирать информацию для записи в некую коллекцию, в простейшем случае - массив
    4. После основного flush'а проверять содержимое коллекции и если там что-то есть - формировать отдельный набор изменений в entities и делать новый flush.
    5. Если всё прошло хорошо - в конце делать commit транзакции
    Ответ написан
    7 комментариев
  • Зачем нужен fosrestbundle в symfony?

    @Flying
    FOSRestBundle упрощает создание REST endpoints, преобразовывая имена методов в роуты + беря на себя ещё ряд базовых функций по упрощению создания REST API на Symfony. К примеру вам ведь нужно выдавать нормальные ответы в случае ошибок и exceptions, а ошибки, они ведь очень разные бывают. Неприятно когда у тебя вроде бы REST API, а он вдруг начинает HTML рендерить.

    В целом, безусловно, всё это можно сделать и руками, но по сути в итоге что-то близкое и получится, ну может кроме роутинга.

    В целом FOSRestBundle не особо большой, в экосистеме Symfony есть гораздо более мощные решения для создания API
    Ответ написан
  • Как результат dd() записать в переменную?

    @Flying
    dd() - всего лишь обёртка над VarDumper::dump(), так что источник получения этого html для вас очевиден.

    Любой перехват вывода в PHP делается через output bufering control.
    Ответ написан
    Комментировать
  • Как работать в symfony env в продакшне?

    @Flying
    Ответ на ваш вопрос есть в документации Symfony:

    Начиная Symfony Flex 1.2 появилась команда composer dump-env prod которая создает обычный PHP файл из имеющихся у вас в .env файлах переменных, тем самым устраняя необходимость их разбора.

    Сам загрузчик Symfony Framework поддерживает загрузку переменных окружения из этого PHP файла.
    Ответ написан
    1 комментарий