Ответы пользователя по тегу Symfony
  • Существует ли Translatable Behavior для Doctrine с хранением переводов в json типе?

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

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

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

    Maksclub
    @Maksclub
    maksfedorov.ru
    Для выбора сущностей в формах есть EntityType:
    https://symfony.com/doc/3.4/reference/forms/types/...
    Ответ написан
    Комментировать
  • Версионность 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 мелких фич на сотни тысяч строк кода прошлых версий, а не другой фреймворк
    Ответ написан
    1 комментарий
  • Как обнулить или объединить миграции и оставить одну?

    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 в конструкторе
    Ответ написан
    5 комментариев
  • Что за Undefined offset?

    Maksclub
    @Maksclub Куратор тега PHP
    maksfedorov.ru
    У вас дампится все отлично — если увидите, на скрине вверху есть слово login
    Очевидно код срабатывает еще раз, там где нет метода и крашится, тк в новый раз этих данных нет...

    Чтобы это узнать, перед получением $s сделайте:
    dd(get_class($event->getController()));
    И покажите полный вывод ВСЕХ значений...

    Вообще потратьте время и поставьте xDebug, вы бы все поняли в течение 2 секунд что и куда прилетает и почему...

    Если выбрали дебаг как раньше на коленке, то у симфы есть функция dump(), все вызовы которой выводятся в панеле разработчика!
    Ответ написан
  • Как в symfony создать сервис, у которого зависимость определяется окружением?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Вариант самый простой и удобный от BoShurik — регистрация сервисов для конфигов под нужное окружение

    Если у вас это все в бандле происходит, то:
    ВАРИАНТ — ФАБРИКА

    Использовать фабрику и с ее помощью регистрировать ваш сервис.
    Фабрика уже сама создаст сервис с нужной зависимостью. Можете в фабрике сами зависимости для вашего сервиса занести например в приватную переменную или регистрировать как-то более хитро — просто их регать через Service Method Calls — мой рекомендуемый вариант.

    Итого — рекомендуемый и готовый код (неймспейсы поменять):
    // Класс, который содержит ваши сервисы под разные окружения
    class MyServiceDependencyRegistry
    {
        private $dependencies = [];
        
        public function register(DependencyInterface $dependency, string $env)
        {
            // Тут всякие проверки можно сделать
            // (особенно, что константа допустима)
            $this->dependencies[$env] = $dependency;
        }
        
        public function getDependency(string $env): DependencyInterface
        {
            // Тут проверочки
            // особенно, что константа допустима и есть с такой константой сервис
            // иначе исключение
            return $this->dependencies[$env];
        }
    }
    
    class MyServiceFactory
    {
        private $dependencies;
    
        public function __construct(MyServiceDependencyRegistry $dependencies)
        {
            $this->dependencies = $dependencies;
        }
    
        public function createNewService(string $env)
        {
            $envDependency = $this->dependencies->getDependency($env);
    
            // Если у вашего сервиса больше зависимостей
            // то вам нужно их передать в фабрику и тут уже передать в ваш сервис
            return new MyService($envDependency);
        }
    }
    
    
    
    # Регаем ваши зависимости (если сервисы  — они должны быть зареганы) 
    # Помним, что сами значения ключей (окружения) лучше константой использовать
    App\Email\MyServiceDependencyRegistry:
        calls:
             - [register, ['@App\Email\RealDependency', 'prod']]
             - [register, ['@App\Email\FakeDependency', 'test']]
    
    # Регаем фабрику, которая принимает в себя сервис, 
    # который отдаст вам  зависимости по ключу
    App\Email\MyServiceFactory:
       arguments: 
          - '@App\Email\MyServiceDependencyRegistry'
    
    # Регаем ваш сервис, указав фабрику и ее метод,
    # который в зависимости от переменной создаст нужный инстанс
    App\Email\MyService:
       factory:   ['@App\Email\MyServiceFactory', createNewService]
       arguments: 
          - '%env(APP_ENV)%'
    Ответ написан
    5 комментариев
  • Как получить значения одной колонки из ArrayCollection в Symfony?

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

    https://www.doctrine-project.org/api/collections/l...

    Если очень большой объём, то чтобы туда-сюда массивы не генерировать — используйте \Generator, и уже по факту итерации берите id сущности и работайте с ним
    Ответ написан
    2 комментария
  • Как в Symfony дебажить event?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Продебажить event или все же listener/subscriber?
    Если последнее, то напишите тест, в котором соберёте нужный евент, и передадите его в слушателя, попутно проверив, что все выполняется...

    Если нужно именно протестировать интеграционный характер работы, то собственно напишите интеграционный тест, подняв Kernel (в Симфони уже есть необходимые кейсы) и через контейнер дёрните листенер и передайте событие, результаты соберите уже реальные.

    Если без тестов — Запилите простенький контроллер, в котором киньте событие, и продебажьте результат, этот способ займёт 5 минут...

    UPD: С телефона нашёл пример: www.inanzzz.com/index.php/post/04ad/testing-event-...
    суть передает
    Ответ написан
    Комментировать
  • Как в Symfony посмотреть лог миграций?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Все выполненные лежат в таблице migration_versions
    Ответ написан
    Комментировать
  • Как указать дополнительный путь к миграциям?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Вам нужно указать путь к настройкам с вашими миграциями (если они лежат например в бандле)
    Например завести конфиг yml с содержанием, где важное самое migrations_directory

    migrations_directory: "<path>"
    migrations_namespace: Application\Migrations
    table_name: migration_versions


    И потом можете указать его при запуске миграций:
    doctrine:migrations:migrate  --configuration=<path_to_configuration_of_migration_yml>
    Ответ написан
    9 комментариев
  • Как сгенерировать миграции по сущности вне src/Entity?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Нужно добавить маппинг в настройках /config/doctrine.yml (ну или в том файле, где эти настройки лежат):
    Для 4 версии: https://github.com/symfony/demo/blob/master/config...

    doctrine:
         orm:
                mappings:
                      UserEntities:
                           type: annotation
                           dir: "%kernel.root_dir%/../src/Models/User/Entity"
                           is_bundle: false
                           prefix: <YOUR_NAMESPACE>\Models\User\Entity
    Ответ написан
    1 комментарий
  • Как добавить поле в entity Doctrine во время сериализации?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Removed deprecated method JsonSerializationVisitor::addData, use :visitProperty(new StaticPropertyMetadata('', 'name', null), 'value') instead

    Источник: https://github.com/schmittjoh/serializer/blob/mast...

    В UPGRADING.md обычно в библиотеках пишут, что поменяли из обратносовместимого...

    Для чего сделали: (довольно поверхностно посмотрел код на Гитхабе)
    повысили уровень абстракции — использовали полиморфизм, чтобы можно было более гибко работать с сериализацией/десериализацией/группами и прочее для работы со свойством сущности, для управления условиями добавления этого поля и т.д. возложив ответственность на объект MetaData (на языке паттернов: Стратегия)

    Дискусия появления решения: https://github.com/schmittjoh/serializer/pull/45
    Ответ написан
    Комментировать