Ответы пользователя по тегу Symfony
  • Как выставить переменную окружения при acceptance тесте в codeception?

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

    Итого: Сделать так, чтобы приложение при запуске тестов смотрело в ту же БД, что и тестовый фреймворк

    Для Symfony пара вариантов:
    - сделать тестовый домен, который бы поднимался с тестовым Kernel
    - поднимать приложение с переменными окружения не через имитацию окружения в .env, а через само собственно окружение (грубо говоря на http://172.17.0.1 в переменных окружения (не в файле их имитирующем .env) должны стоять верные параметры подключения
    - использовать модуль Symfony с явным указанием env: test, но подойдет для функциональных тестов, а не приемочных
    Ответ написан
  • Почему не добавляет таблицу в БД после миграции?

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


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

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

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

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

    Maksclub
    @Maksclub
    maksfedorov.ru
    Twig Extension

    Создайте свой экстеншн, передав ему ваши сервисы (репозиторий например)
    В методе getFunctions() зарегайте свою твиг-функцию

    Зарегайте его с тегом twig.extension
    Вызывайте в ваших шаблонах
    Ответ написан
  • Как осуществить пере адресацию после авторизации на ранее запрашиваемый ресурс?

    Maksclub
    @Maksclub
    maksfedorov.ru
    По дефолту работает так, как вы и хотите:

    By default, the form will redirect to the URL the user requested (i.e. the URL which triggered the login form being shown). For example, if the user requested http://www.example.com/admin/post/18/edit, then after they have successfully logged in, they will be sent back to http://www.example.com/admin/post/18/edit.

    Redirecting after Success¶


    Видимо у вас поведение изменено через always_use_default_target_path:
    Changing the default Page¶

    ...или иным способом...
    Ответ написан
  • 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);


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

    Maksclub
    @Maksclub
    maksfedorov.ru
    EasyAdmin контроллеры довольно простые, вы просто наследуете EasyAdminController и называете свой контроллер так: EntityName + Controller, то есть для User это будет UserController.

    И для ваших контроллеров как и везде в Symfony вы можете использовать свои роли, например так:
    /**
     * @Security("is_granted('ROLE_CAN_EDIT_USER')")
     */
    class UserController extends EasyAdminController
    Подробнее:
    Аннотация Security
    Symfony Uses Voters

    Также ознакомьтесь с главой EasyAdmin: Security and Permissions¶, особенно про item_permission
    Ответ написан
  • Symfony - phpstan, как устранить это замечание?

    Maksclub
    @Maksclub
    maksfedorov.ru
    try {
         // логика
    } catch(\Throwable $e) {
         // тут например логируем
    
         // тут мы 3-м параметром для нового исключения добавили прошлое исключение
         // чтобы не потерять стектрейс изначальной ошибки, мало ли откуда текущий код был вызван
         // о чем и говорит PhpStan
         throw new MyException('msg', 0, $e);
    }
    Ответ написан
  • Существует ли Translatable Behavior для Doctrine с хранением переводов в json типе?

    Maksclub
    @Maksclub
    maksfedorov.ru
    По указанной ссылке нормально решение сам автор себе подкинул c ValueObject, в независимости от того, есть JSON поддержка в СУБД или нет.

    Если вы захотите в этот тип заинжектить какое-то поведение из фреймворка, то можно это сделать так:
    достать Type и добавить ему поведение в Kernel::boot(), так сделано например тут:
    https://github.com/dunglas/doctrine-json-odm/blob/...

    Кстати можете целиком взять указанный бандл, тогда вся ваша работа сведется к тому, что вам нужно будет просто написать нормалайзер/денормалайзер для вашего ValueObject
    Ответ написан
  • Версионность API в Symfony?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Предлагаю работать как все норм ребята
    • Создать папку контроллеров /V2/ (ну или/и добавить V2 в название контроллера для удобного поиска в IDE)
    • Настроить префикс для всех этих контроллеров как /v2 одной строкой в роутах (ну или если в аннотациях задаете -- в них указать префикс)

    Дока: Route Groups and Prefixes¶

    И не мудохаться с квери-параметром
    Такие возможности у вас под рукой, какой параметр вы там надумали как в нулевых?!

    Ну это конечно, если вы в контроллерах говна не нагородили, если сделано чисто -- то контроллер как расходка,
    контроллер + парочка ДТО под нужную версию

    UPD от tommy-vercetti:
    Не стоит юзать JMS сериалайзер или Symfony Serializer с аннотациями для сущностей. Для респонсов можно использовать обычные DTO-шки либо fractal.

    API доку удобно писать с помощью https://github.com/bukalapak/snowboard
    Ответ написан
  • Component\Validator - Как сделать проверку на "Отсутствует или массив"?

    Maksclub
    @Maksclub
    maksfedorov.ru
    new Assert\Collection([
    'address' => ...

    У данного ассерта нет поля 'address', есть например fields...

    Можете сделать через Expression
    new Assert\Expression([
        'expression' => 'this.isValidAddress()',
        'message'    => 'Адрес или пустой или массив',
    ])

    Тогда нужно будет добавить в класс, где поле address метод
    public function isValidAddress(): bool
    {
          return $this->address === null || \is_array($this->address);
    }


    UPD: Если доменную модель нужно валидировать, значит она может пребывать в невалидных состояних, что говорит о плохом дизайне (что само в свою очередь допустимо, если не раскидываться словами "доменная" и "модель").
    Такое бывает, когда объекты значатся объектами номинально и представляют из себя анемичные (без поведения и инкапсуляции) объекты, а обычные структуры (пусть и типизированные и называющиеся со слова class). Это способ процедурного программирования через ООП-синтаксис. В силу слабой типизации PHP такое программирование является обходным вариантом, чтобы типы сохранить, но к ООП парадигме отношения не имеет. Потому тут сильно вперед вырывается тот же Golang, проще описывать такие процедурные вещи и типизированный.

    Валидировать можно и нужно ДТО к примеру, модель доменную не нужно -- она всегда валидна в любом из состояний, по определению. Автомобиль либо едет сам, если есть бензин, либо нет, никто его извне не "едет" после некой проверки наличия бензина извне.
    Ответ написан
  • Как добавить префикс к url?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Возможности, начиная с 3 версии: https://symfony.com/blog/new-in-symfony-3-4-prefix...
    Начиная с 4.1: https://symfony.com/blog/new-in-symfony-4-1-prefix...

    5 версия - это 20-40 мелких фич на сотни тысяч строк кода прошлых версий, а не другой фреймворк
    Ответ написан
  • Как обнулить или объединить миграции и оставить одну?

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

    На машине разработчика
    Если все сделано аккуратно (когда работали с миграциями), то можно удалить все таблицы и попробовать сгенерировать diff — он сделает актуальную миграцию, которая необходима. Далее нужно удостовериться — не было ли придуманных вне ORM миграций и таблиц, крч сверить с боевой схемой. Также возможно миграции несли некоторые данные (ну например коды валют, которых всего 5-10 вы решили миграцией занести или пара дефолтных значений из таблицы settings, и подобные случаи) — нужно будет руками это повторить (или не повторять, если не нужно).
    Если вы добились, что ПУСТОЙ БД накаткой миграций получается ровно то, что достигалось ранее, то:
    • вы спокойно можете удалить старые миграции и коммитить эту одну большую
    • перед выкаткой на прод — вы должны текущую миграцию занести в таблицу migration_versions руками


    Done!

    Все делается очень легко, мне как-то достался проект без доступа к старому GIT (и почему-то без миграций), но с боевой БД. Пришлось проделать приблизительно все тоже самое (не считая болевого обновления Symfony с 2.7 до 3.4 с Сонатой, было больно :):):))...
    Все проделывания с миграциями заняли от силы полтора часа (в виду молодости и не опытности), было пару подводных моментов (типа у некоторых бандлов из vendor были свои папки со своими мииграциями) и данные нужны были. некоторые, а так все относительно легко
    Ответ написан
  • Symfony, Argument resolver используется по назначению?

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

    Так неявно сделано в разных админ-панелях. Только не в аргументах контроллера, а просто в реквесте, в EasyAdmin так
    Ответ написан
  • Symfony - в чём разница между listener и subscriber, и что означает теговать (tags)?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Listener — слушатель одного или более событий, некий такой атомарный исполнитель. Не знает на что он подписан, но легко управляется через конфигурацию. Если разные слушает — нужно самому логику разделения обработки писать.
    Subscriber — подписчик имеет реализацию интерфейса SubscriberInterface с методом getSubscribedEvents(), то есть сам объект слушателя знает, на что он подписан и какие методы какие события будут отрабатывать

    По сути одно и тоже, но такая разница иногда пригождается. Иногда нужно иметь контроль снаружи, иногда наоборот.

    Теги — отдельный механизм контейнера. Проставляя тег на набор сервисов — мы во время компиляции контейнера можем достать помеченные определенным тегом сервисы и заинжектить в др сервис (очень часто используется мной например), отсортировать по допатрибутам или сделать любую иную логику
    https://symfony.com/doc/current/service_container/...
    Ну например есть десяток сервисов, которые обрабывают каждый свой тип файла, еще и лежат в разных бандлах. Как получить их в одной фабрике? Правильно — проставить им тег, в компиляторе приложения достать все и в фабрику засунуть. При появлении нового сервиса с тегом - он автоматом будет в фабрике.

    В коде самого фркймворка этот механизм активно используется. Например есть разного рода констрейнты - добавить новый вы можете и автоматом зарегистрировать, проставив тег (на самом деле симфони-разработчики пошли дальше и тег навешивают по интерфейсу, но важен механизм) и данный констрейнт будет доступен коду, в котором требуются все констрейнты (опять той же фабрике)
    Ответ написан
  • Как создать собственный валидатор в symfony?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Я вроде читаю https://symfony.com/doc/4.4/validation/custom_cons... но чё то понять не могу.

    /**
    * @Annotation
    */
    class ContainsAlphanumeric extends Constraint
    {
    public function validate($value, Constraint $constraint) {}
    }


    Небольшая путаница произошла по спешке: аннотация (констрейнт) — это одно, валидатор — другое, они связаны, но это две разные вещи одной системы.

    Constraint — по сути выражение/аннотация, по которой будет срабатывать валидатор. Она принимает в себя некие параметры и содержит логику к чему применяться — к классу или филду класса.

    Создаем свой констрейнт:
    В нашем констрейнте мы планируем, что мы сможем передать некий дополнительный параметр размер, а если не передадим, то его значение будет 55 (ну так хотим). Причем это будет относиться не к кполю, которое валидируем, а именно к констрейнту, То есть то, что он отдаст валидатору. Ну например возраст или некий пинг,да что угодно, в общем ориентируясь на который валидатор применит некую логику.
    /**
     * @Annotation
     */
    final class AnyConstraint
    {
        /**
         * @Required
         * @var int
         */
        public $size = 55;
    
        /**
         * Вот тут мы укажем некий валидатор, тот класс, который и будет валидировать наше значение
         * Дял удобства он будет называться также + слово Validator, в нашем случае AnyConstraintValidator
         */
        public function validatedBy()
        {
            return \get_class($this).'Validator';
        }
    }


    Создаем собственно валидатор
    на который выше в методе validatedBy() мы уже указали

    class AnyConstraintValidator extends ConstraintValidator
    {
        public function validate($value, Constraint $constraint): void
        {
            // Тут логика, при которой или все пройдет до return - все отлично
            // Или вообще исключение вывалится (если пришло чего-то не то)
            // Или (для чего и используется валидатор) создастся violation
    
            // например число в объекте больше того, которое мы указали в size (по дефолту если не указали: 55)
            if($value > $constraint->size) {
                $this->context->buildViolation('Тут пинг пришел вообще большой  —  такой нельзя, брат')
                    ->setParameter('{{ string }}', $value)
                    ->addViolation();
            }
        }
    }


    Используем:
    class AnyEntity
    {
        /**
    
         * Если значение больше 55  — то не пройдет валидацию
         * @AnyConstraint(120)
         */
        protected $name;
    }


    Помимо всего у констрейнта можно указать к чему применять — к методу, классу или к переменной класса
    • Через аннотацию у класса констрейнта: @Target({"PROPERTY", "METHOD", "ANNOTATION"})
    • Через метод переопределенный метод getTarget()
    Ответ написан
  • Как правильно организовать запись в бд, поле пригласил?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Тут не нужна другая сущность.

    One-To-Many, Self-referencing
    You can also setup a one-to-many association that is self-referencing. In this example we setup a hierarchy of Category objects by creating a self referencing relationship. This effectively models a hierarchy of categories and from the database perspective is known as an adjacency list approach.

    class User
    {
        /**
         * Кого пригласил
         * @var ArrayCollection
         * @ORM\OneToMany(targetEntity="User", mappedBy="invitedBy")
         */
        private $invitedMembers;
    
        /**
         * Кем был приглашен
         * @var User
         * @ORM\ManyToOne(targetEntity="User", inversedBy="invitedMembers")
         */
        private $invitedBy;
    
        public function __construct() {
            $this->invitedMembers = new \Doctrine\Common\Collections\ArrayCollection();
        }
    }
    Ответ написан
  • Как использовать Jms Serializer с Doctrine с настройкой Lazy?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Аннотация @Accessor

    /** @Accessor(getter="getName") */
    private $name;
    
    public function getName()
    {
         return $this->name;
    }

    При вызове указанного геттера произойдет вызов и запрос. Соответственно lazy вернет результат.

    Если yaml, то в той же доке указано как задать аксессор.
    Не забудьте почистить кеш (не всегда чистится консольной командой, удалите папку jms - когда с 3й работал, то были такие глюки) .


    А вообще, если пишите с нуля, то знайте — от JMS Serializer все уходят в сторону Symfony Serializer
    В контейнере уже должен быть у вас сервис @serializer сразу после установки компонента и можно просто заинжектить через SerializerInterface в конструкторе
    Ответ написан