Ответы пользователя по тегу Symfony
  • Почему не работает отношение one-to-many?

    prototype_denis
    @prototype_denis
    Symfony
    Доктрина "лениво" грузит коллекции. Это значит, что результаты будут только при явном обращении, то есть при итеририровании и обращении к свойствам.

    Для "жадной" загрузки в кофиге (в вашем случае аннотации) нужно прописать eager="fetch", тогда коллекция будет проинициализирована сразу же
    Ответ написан
  • Как расшарить форму на все шаблоны twig?

    prototype_denis
    @prototype_denis
    Symfony
    {{ render(controller('App\Controller\FormController::searchForm')) }}
    Ответ написан
  • Как получить список всех базовых Ролей и их дочерних в Symfony 4?

    prototype_denis
    @prototype_denis
    Symfony
    Иерархия ролей компонента security хранится в параметре security.role_hierarchy.roles

    $container->getParameter('security.role_hierarchy.roles');

    Но лучше использовать не роли, а группы ролей.
    Ответ написан
  • Как правильно организовать процесс разработки бандла?

    prototype_denis
    @prototype_denis
    Symfony
    В дополнение ответа, отмеченны, как решение

    https://getcomposer.org/doc/05-repositories.md#path

    {
        "repositories": [
            {
                "type": "path",
                "url": "../../packages/my-package"
            }
        ],
        "require": {
            "my/package": "*"
        }
    }
    Ответ написан
  • Валидация части объекта Symfony?

    prototype_denis
    @prototype_denis
    Symfony
    Вам необходимо использовать группы валидации.

    symfony.com/doc/current/form/validation_groups.html

    Под капотом ваша сущность попадает на эту строку где валидируется весь объект.

    В форму попадают все ошибки и только ошибка пароля (если она есть) попадёт в FormError конкретного поля. Остальные попадут в рутовую форму.

    Соответственно, если малой кровью - добавьте две группы для пароля:

    symfony.com/doc/current/validation/groups.html - одну по умолчанию, для обратной совместимости, если данная сущность уже где-то валидируется и не нужгно ломать обратную совместимость и вторую, специфичную для формы. И при создании формы, или же в опциях сборщика укажите данную группу (ключ validation_groups)

    Решение данной проблемы - 3 строчки кода. (2 на Assert и одну к опциям формы)

    @Assert\NotBlank(message="Пароль не должен быть пустым", groups={...})
    @Assert\Length(..., groups={...})
    
    
    $resolver->setDefaults([
        'validation_groups' => [...]
    ]);
    Ответ написан
  • Как отправить письмо Swift_Mailer из сервиса?

    prototype_denis
    @prototype_denis
    Symfony
    Смотрите конфигурацию приложения.

    По умолчанию используется spool, который обычно вешают на крон.

    Используйте тип memory в конфигурации swiftmailer для отправки почты на событии KernelEvents::TERMINATE
    Ответ написан
  • Почему в Symfony 3.4 через cli маппятся связи, а через web нет?

    prototype_denis
    @prototype_denis
    Symfony
    Певая возможная причина.

    У вас окружение разное.

    export SYMFONY_ENV=prod или запуск команды с ключем -e prod (В этом случае она действительно в прод окружении запуститься)
    Если это так, то смотреть namingStrategy для доктрины.
    Убить кэш (командой доктриновской)

    Вторая причина.

    Если вы на 150% уверены, что окружения действительно одни и те же, то смотреть слушателей на KernelEvents.
    Затем смотреть расширения доктрины, если такие есть.

    Третья причина.

    Настройки кэша и использование данных из Request в качестве ключей (Из консоли Request не доступен) для кэша доктрины. Банальный мемкеш, где превиксом является доменное имя.

    Пятая

    Маловероятная... Одновременное использование нескольких драйверов маппинга (аннотации, yaml)

    Изменения в папке вендоров (compoer validate)

    ...

    Рассуждения.

    Судя по всему вы через что-то деплоете (по структуре папок)

    Шары не напутаны для проекта?
    Кэш точно инвалидирован?
    Симлинки верно указывают?
    Права юзверя, группы на файлы кэша из веб морды/консоли?
    Хост точно тот?
    Веб сервер (nginx точно на current дирекиорию смотрит)?

    ...

    Гадать крайне долго можно, но 90%, что разное окружение и проблемы с кэшем
    Ответ написан
  • Где symfony DI собирает все зависимости?

    prototype_denis
    @prototype_denis
    Symfony
    Привет всем, хотел уточнить. Где symfony DI собирает все зависимости?


    https://symfony.com/doc/current/components/depende...

    И через что (какой компонент) прокидывает зависимости в методы, свойства или конструктор?


    https://symfony.com/doc/current/service_container.html

    В каком месте приложения данный проброс (инжект) инстанса в метод actionIndex произошел?


    symfony.com/doc/current/components/dependency_inje...

    Мне кажется вы неверно задаёте вопрос. "Где", "Через что", "В каком месте" - это прямая ссылка на исходники symfony https://github.com/symfony/symfony

    Более разумно было бы отвечать на вопросы "Как" и "Почему".

    Итак, DI. Что делает этот компонент.

    Ему на вход прилетает конфигурация сервисов, неважно в каком формате и как. Yaml, php, xml и т.д. (хоть свой формат реализуйте)

    Затем он разрешает зависимости и собирает контейнер. На выходе он отдаёт ContainerInterface.

    Вот и всё, на этом и заканчивается основная логика этого компонента. Вся магия, до и после его основной работы.

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

    (Что решает одну очень важную проблему - пролюбленные парметры, несуществующие зависимости обнаруживаются тут же, а не в рантайме)

    Кэш - после основной работы DI - контейнер дампится в файл, лежит он обычно в var/cache/{env}/{container_name} и подключается в Kernel классе.

    Именно там уже лежат все ваши агрументы для ваших классов, конктретные классы реализующие интерфейсы, абстракции и т.д.

    В общем, для общего понимания.
    1. Собирается конфигурация сервисов (yaml, php, annotations, reflection, etc...)
    2. Собирается контейнер (В данный момент - агрументы сервисов, сами сервисы не имеют инстансов классов, всё в памяти и ввиде референсов)
    3. Дампиться контейнер (Тут происходит дамп инстансов, реальных классов)


    Небольшое замечание.

    Не кладите в методы классов вызовы к базе, к стороннему api и прочим 3-им серсвисам, если данный метод участвует в компиляции контейнера. Иначе вы не сможете поднять приложение.
    Банальный пример. Если в какой-то команде в методе configure сделать запрос к базе, то без настроенного коннекта к базе или доступной базы вы не сможете нормально очистить кэш.

    Вышеприведённыя ссылка - https://github.com/symfony/http-kernel/blob/master... для получение аргументов контроллера, отвечает даже не за "инклуд" в контролер сервисов, а за "инклуд" параметров запроса, сесиии и прочих.

    В дополнение приведу вот эту ссылку https://github.com/symfony/http-kernel/blob/master... и вот эту https://github.com/symfony/http-kernel/blob/master...

    Вот так компоненты http-kernel и di связаны друг с другом в данном случае.

    Полноценный ответ тянет на цикл статей на хабре только по одному DI в symfony, здесь же постарался максимально кратко описать то "как оно работает", а не где и в каком месте. Надеюсь мой ответ будет хоть чем-то полезен.
    Ответ написан
  • Как понять Symfony 4?

    prototype_denis
    @prototype_denis
    Symfony
    Начните с демо приложения. Там очень подробные комментарии к исходному коду.

    https://github.com/symfony/demo
    Ответ написан
  • Задавать явно все default значения для полей через php каждый раз при создании объекта?

    prototype_denis
    @prototype_denis
    Symfony
    <?php
    /**
     * @Entity
     */
    class myEntity {
        /**
         * @var string
         *
         * @Column(name="myColumn", type="string", length="50", options={"default": "default_value"})
         */
        private $myColumn = 'myDefaultValue';
        ...
    }
    Ответ написан
  • Symfony 4 использование Validator, как узнать с какого поля пришёл запрос?

    prototype_denis
    @prototype_denis
    Symfony
    есть 3 поля, title_ru, title_de и title_en, если сайт работает на данный момент на locale==ru, то title_ru обязателен для заполнения если locale==en то title_en обязателен для заполнения


    Используейте группы валидации.

    Один из вариантов будет выглядеть примерно так.
    /**
     * @Assert\NotBlank(groups={"foo_en"})
     */
    private $title_ru;
    
    $builder->add('title_ru', null, ['validation_groups' => sprintf('foo_%s', $options['locale'])])
    
    $resolver->setDefaults([
      'locale' => 'en'
    ]);
    
    $this->createForm(AcmeFormType::class, $object, ['locale' => $request->get('_locale')]);
    Ответ написан
  • Как создать каскадную форму Symfony 2?

    prototype_denis
    @prototype_denis
    Symfony
    Всё намного проще и само собой подходов куча.

    Классический подход НЕ подразумевает js и добавляет новые поля после каждого сабмита формы, решается крайне просто:

    <?php
    
    interface ObjectInterface {
        public function getCountry(): ?Country;
        public function getRegion(): ?Region;
    }
    
    class AppType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                ->add('country', EntityType::class, [
                    'class' => \App\Entity\Country::class,
                ])
            ;
    
            $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
                /** @var ObjectInterface $object */
                if (null === $object = $event->getData()) {
                    return;
                }
                $form = $event->getForm();
    
                if ($object->getCountry()) {
                    $form->add('region', EntityType::class, [
                        'class' => \App\Entity\Region::class,
                        'query_builder' => function (EntityRepository $repository) use ($object) {
                            return $repository->createQueryBuilder('e')->where('e.country = :country')->setParameter('country', $object->getCountry());
                        }
                    ]);
                }
    
                if ($object->getRegion()) {
                    $form->add('city', EntityType::class, [
                        'class' => \App\Entity\City::class,
                        'query_builder' => function (EntityRepository $repository) use ($object) {
                            return $repository->createQueryBuilder('e')->where('e.region = :region')->setParameter('region', $object->getRegion());
                        }
                    ]);
                }
            });
        }
    }


    Цена вопроса 3 POST запроса

    Но такой подход не удобен и только путает.

    Как решить?

    Одним из способов будет являться следующее:

    - Создаём контроллер с методами получения регионов, городов.
    - На фронте любыми способами рисуем инпут
    - Динамически (на фронте) делаем с этими формами что угодно
    - Отправляем форму

    В этом случае symfony форма будет выглядить примитивно

    $builder->add('country')->add('region')->add('city');


    Надо будет только задать группу валидации, где вы спокойненько провалидуриете всё дерево, начиная со страны.
    Цена вопроса - 2 ajax запроса, 1 POST

    Или...

    Рисуем форму
    $builder->add('country')->add('region')->add('city');

    Где region и city - ChoiceType пустые.
    Аяксом забираем с сервера регионы, а затем города.
    В событии формы (SUBMIT и POST_SUBMIT с приоритетом нижк валидатора) ручками ссетим эти данные.

    Лично мне предпочтителен первый подход (классика без js), на которую потом уже навешиваются плюшки.
    Запросов больше, но объект собирается, как на фронте, так и на бэке последовательно и нет никаких проблем ни с редактированием, ни с созданием.

    Для понимания. PRE_SET_DATA - это GET запрос, прим перед резолвом данных (имеено по этому в коде проверка на null). PRE_SUBMIT - это POST запрос. В обоих случаях у вас объект в событии.
    Соответственно за последовательность отвечает data_class, объект DTO (или сущнсоть) в котором будет страна, регион и город.
    Бонусом (разобравшись с этой ересью) - валидаторы, всякие ресты и сериализаторы прикручиваются к таким формам на раз-два по необходимости.

    > Пробовал - не работает. В него не попадают значения динамических полей

    Отвечу на ваш коммент тут.

    Во-первых - событие PRE_SET_DATA с проверкой данных на null
    Во-вторых - событие PRE_SUBMIT (пожеланию) на биндинг данных (если у вас в форме не используется сущности)

    symfony.com/doc/current/form/dynamic_form_modifica...
    Ответ написан
  • Как вставить строки в таблицу order?

    prototype_denis
    @prototype_denis
    Symfony
    Зарезервированные слова необходимо брать в кавычки

    /**
     * @ORM\Table(name="`order`")
     */
    class Order {}
    Ответ написан
  • Что может значить этот баг в пагинации?

    prototype_denis
    @prototype_denis
    Symfony
    Используйте другой адаптер, например DoctrineDbalAdapter с небольшим изменением кода.

    В вашем примере "частичная" выборка объекта, а именно только несколько свойств класса User. Именно по этому результатом будет не объект, а массив. Pagefanta вам именно это и говорит.

    Собственно или выбирайте полные объекты, или неполные массивы.
    Ответ написан
  • Как в Symfony3.4 + Codeception при написании функциональных тестов замокать сервис в DI?

    prototype_denis
    @prototype_denis
    Symfony
    Не сервис надо подменять в другом окружении, а клиента.
    Вы ведь тестируете же сервис, верно же?

    <?php
    
    namespace App\Http {
        interface HttpClientInterface {}
        class FakeHttpClient implements HttpClientInterface {}
        class RealHttpClient implements HttpClientInterface {}
    }
    
    namespace App\Service {
        class ThirdPartyService {
            private $client;
            public function __construct(\App\Http\HttpClientInterface $client) {
                $this->client = $client;
            }
        }
    }
    
    ?>


    // prod
    app.http_client:
        class: App\Http\RealHttpClient
    
    // test
    app.http_client:
        class: App\Http\FakeHttpClient
    
    app.third_party_service:
        class: App\Service\ThirdPartyService
        arguments: [app.http_client]


    Под HttpClientInterface можно подогнать что угодно, от curl до soap, а так же логгировать и собирать данные того что отправляется и что приходит, а так же вешаться на события, которые там же можно и генерить.

    Оставьте сервис в покое, пусть делает свою работу.

    Так же можете добавить и "псевдореальное окружение" и время от времени запускать тесты с реальными севисами, собирая данные и предохраняясь от внезапного изменения API. Но это другая история.

    Если не хотите заморачиватся с клиентом, то замените класс сервиса

    // parameters.yml
    app.third_party_service_class: App\Service\ThirdPartyService
    
    // config_test.yml
    app.third_party_service_class: App\Service\MockThirdPartyService
    
    app.third_party_service:
        class: "%app.third_party_service_class%"
    
    class MockThirdPartyService extends ThirdPartyService {
        public function foo($ignoredArguments) {
            return true;
        }
    }


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

    prototype_denis
    @prototype_denis
    Symfony
    Какой-то странный "стандартный" тип формы. Метода getExtendedType() у него вроде как нет...

    Что касается валидации.
    Используйте группы валидации, форма чище будет, да и объект валидации будет доступен в контексте валидатора.

    В setDefaults у вас значение по умолчанию, соответственно для изменения домена в другой форме, которая переиспользует этот тип, достаточно просто переопределить это значение.

    $this->createForm(RegistrationForm:class, null, ['translation_domain' => 'foo'])
    $this->createForm(RegistrationForm:class, null, ['translation_domain' => 'bar'])
    Ответ написан
  • Как зашифровать сессию в symfony?

    prototype_denis
    @prototype_denis
    Symfony
    Фаервол в security.yml настроен верно, он поддерживает сессии?
    Ответ написан