Ответы пользователя по тегу Symfony
  • Авторизация разными ключами?

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

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

    Подробности есть в документации.
    Ответ написан
  • Не удается обновить плагин в 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.
    Ответ написан
  • Как не повторять один и тот же код в Symfony?

    @Flying
    Вынесите весь нужный код в отдельный класс и подключайте его в качестве зависимости либо к самим контроллерам в конструкторе либо прямо к нужным методам.
    Ответ написан
  • Как ограничить очередь на выполнение только 200 задач в час?

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

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

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

    @Flying
    Если, судя по тегам, речь идёт от Symfony - то зачем вам вообще базовый абстрактный класс для ValueObject, которые по-идее должны представлять значения, а не логику их валидации. Для валидации в Symfony, как вы наверняка знаете, есть соответствующий компонент, поэтому вам достаточно просто описать правила валидации через соответствующие аннотации.

    Для первой валидации стоит использовать LessThan с датой. Для второй придётся писать свой валидатор, но там нет ничего сложного.

    Если данные приходят в запросе - то можно воспользоваться подходом, который предложил BoShurik в своём ответе на похожий вопрос (смотрите комментарии, там больше информации и примеры), в этом случае до контроллера у вас гарантированно будет долетать только заполненный и валидный объект.
    Ответ написан
  • Можно ли и нужно ли и нужно ли выносить 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 транзакции
    Ответ написан
  • Зачем нужен 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 файла.
    Ответ написан
  • Как symfony загружает классы через входные данные?

    @Flying
    Этому посвящена добрая часть документации компонента Dependency Injection и Service Container'а, который является центральной частью этого компонента.

    Если совсем вкратце - то вы либо описываете сервисы вручную через конфигурацию (есть несколько поддерживаемых форматов, включая аннотации, хотя они вроде реализуются внешним пакетом) либо используете преимущества autowiring'а, предоставляя Symfony всё описать за вас (с помощью compiler pass'ов, если потребуется). Собственно основная "магия" преобразования всех источников данных в кучу компилированного PHP находится как раз в compiler pass'ах, их много и можно писать свои.

    Поскольку у вас в примере - controller's action - то здесь вся магия реализуется через обработку тега controller.service_arguments, который по-умолчанию добавляется ко всем контроллерам в конфиге сервисов.
    Ответ написан
  • Doctrine как разрешить состояние гонки?

    @Flying
    По сути в вашем случае запись в базу данных является критической секцией. Соответственно вам необходима реализация любого из способов синхронизации потоков для избегания состояния гонки. Вариантов множество, но конкретно в Symfony именно для этого (и для других подобных сценариев) существует компонент Lock, он предоставляет реализации готовых примитивов синхронизации.

    Вот здесь можно посмотреть на практически готовый пример того как должен выглядеть ваш код коммита изменений с учётом использования lock'а.
    Ответ написан
  • Как правильно конфигурировать сторонний бандл в своём?

    @Flying
    Переопределение конфигурации в Symfony вне основного процесса загрузки конфигурационных файлов - не самое простое дело. Этот процесс закрыт от прямой кастомизации т.к. основной процесс загрузки и обработки конфигурации bundles целиком отдан на откуп им самим. Причина подобных ограничений в том что загрузка конфигурации - один из самых ранних этапов работы и, как и для всех подобных частей кода - там довольно мало пространства для манёвров. В связи с этим путь (и сама возможность) переопределения конфигурации будет очень сильно зависеть от того что же именно вы хотите переопределить.

    Для того чтобы лучше понять как это происходит - вам стоит подробнее изучить вопрос того как именно организована загрузка и обработка конфигурации в Symfony.

    Если вкратце:
    1. В процессе конфигурации контейнера происходит сбор данных конфигурации из конфигурационных файлов. Эти данные сохраняются в container'е через ContainerBuilder::loadFromExtension().
    2. Далее, собранные данные передаются в качестве основы в bundle extension (через метод load()). То, как именно bundle extension будет работать с собранными данными - целиком зависит от кода bundle'а, так что в целом с ними может произойти всё что угодно.

    Как вы, надеюсь, понимаете - из-за п.2 какого-то общего подхода к переопределению данных в Symfony не существует. Поэтому, как я уже писал выше, вам необходимо определиться с тем что именно вы хотите переопределить, а дальше смотреть на код загрузчика соответствующего bundle (вот он для LiipImagineBundle).

    Как видно - в случае LiipImagineBundle в целом ничего страшного не происходит, полученная конфигурация сразу отправляется в processing и потом используется для регистрации сервисов и параметров.

    Поскольку процесс сборки контейнера начинается с merge pass, то вы не можете вмешаться в этот процесс через compiler pass, как это делается обычно, однако вы можете воспользоваться вот этим куском логики для того чтобы добиться своей цели: вам достаточно определить в своём bundle собственный extension и отметить что он реализует PrependExtensionInterface. Это позволит вашему extension получить экземпляр ContainerBuilder до того как будут загружаться остальные extensions, именно это позволит вам воспользоваться ContainerBuilder::loadFromExtension() для добавления параметров конфигурации к нужному вам bundle.

    Несколько нетривиально, но должно работать :)
    Ответ написан
  • Symfony 4 - {{ app.request.getSchemeAndHttpHost() }} выдает http при https?

    @Flying
    Если в посмотрите в исходный код, то увидите что Request::getScheme(), которую использует Request::getSchemeAndHttpHost() вернёт вам https в зависимости от того признан ли запрос безопасным или нет методом Request::isSecure().

    Сам метод isSecure(), как видно по коду, возвращает true в двух случаях:
    1. Либо это HTTPS запрос и есть соответствующий заголовок
    2. Либо это запрос от доверенного proxy сервера и сервер передаёт информацию о том что оригинальный запрос безопасен


    Таким образом я бы предположил что у вас используется схема nginx + php-fpm и либо nginx некорректно сконфигурирован и не передаёт нужные данные либо nginx не указан в качестве trusted proxy.
    Ответ написан
  • Обновление php приложения на symfony?

    @Flying
    С точки зрения Symfony консольная команда - это просто класс, наследуемый от Symfony\Component\Console\Command\Command. Любая консольная команда - это просто запуск класса Symfony\Bundle\FrameworkBundle\Console\Application, как можно видеть в файле bin/console.

    Таким образом ничто не мешает вам "запустить консольную команду" просто создав Console\Application и запустив нужную команду в нём, сформировав правильные входные параметры.

    Изменение структуры базы данных зависит от того как именно вы ею управляете. Используете ли Doctrine или что-то ещё? Используете ли Doctrine Migrations или нет? Без этой информации дать точный ответ нельзя. Скорее всего в вашем случае было бы логичнее всего использовать Doctrine Migrations, они ведь именно для этого и созданы.
    Ответ написан
  • Symfony - как поместить в контейнер свою реализацию сервиса?

    @Flying
    Здесь есть несколько путей:
    1. Если это что-то простое и вы работаете с Symfony Framework (т.е. не со standalone контейнером) - то вам просто необходимо описать ваш сервис в конфигурации сервисов (в Symfony 4 и выше это config/services.yaml). Как это делается - описано в документации. Описание стандартного controller_resolver можно увидеть здесь, в Yaml это будет выглядеть вот так:

      services:
        controller_resolver:
          class: Full\Name\Of\Your\Implementation
          arguments:
           - '@service_container'
           - '@?logger'
         tags: 
           - {'name:':'monolog.logger', channel: 'request'}

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

    2. Если это что-то более сложное - то настройка сервисов производится через compiler pass. В них вы имеете полный доступ к ContainerBuilder и можете программно сформировать в нём всё что вам необходимо. Однако сомнительно что в данном случае вам это потребуется.


    В целом все эти вопросы очень подробно разобраны в документации компонента Dependency Injection.
    Ответ написан