• Как правильно организовать процесс разработки бандла?

    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';
        ...
    }
    Ответ написан
  • На что ругается feedvalidator.org в RSS?

    prototype_denis
    @prototype_denis
    Symfony
    <pubDate><?= date(\DateTime::RFC822, strtotime($res['DATE_CREATE']))." GMT" ?></pubDate>

    или
    <pubDate><?= date(DATE_RFC822, strtotime($res['DATE_CREATE']))." GMT" ?></pubDate>
    Ответ написан
  • 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')]);
    Ответ написан
  • Как подсветить цифры в номере телефона с заданным форматированием?

    prototype_denis
    @prototype_denis
    Symfony
    <?php
    
    // no mask, only pattern
    $patterns = [
        '/23.56/is',
        '/99(\d+)8/is', // /99*8/is no correct pattern
    ];
    
    $data = [
        '987 (123) 45-67',
        '799 125 4785',
    ];
    
    foreach ($data as $value) {
        foreach ($patterns as $pattern) {
            $map = [0 => [], 1 => []];
            foreach (preg_split('//u', $value, 0, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY) as $index => $char) {
                $map[(int) is_numeric($char)][$index] = $char;
            }
            foreach (array_combine(array_keys($map[1]), preg_split('//u', preg_replace_callback($pattern, function ($matches) {
                return str_repeat('#', count(preg_split('//u', $matches[0], 0, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY)));
            }, join($map[1])), 0, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY)) as $index => $char) {
                $map[0][$index] = '#' === $char ? '<span>'.$map[1][$index].'</span>' : $char;
            }
            ksort($map[0]);
            echo join($map[0]).PHP_EOL;
        }
    }
    Ответ написан
  • Как создать каскадную форму 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 настроен верно, он поддерживает сессии?
    Ответ написан
  • Как решить данную задачу (Олимпиадная)?

    prototype_denis
    @prototype_denis
    Symfony
    Время выполнения, зависит не только от вашего когда, но и от версии пыха до железки на которой запускаете процесс.

    Смысл простой. В input.txt у нас вначале идут продукты, и только за ними запрос. Мы можем сразу же обрабатывать каждый запрос, как только его получим.
    Проблемным местом является array_diff, его можно заменить на isset, но в этом случае придётся добавить ещё один цикл
    По факту роли не играет никакой.
    Можно заменить на str_replace и скормить ему 2 массива, но в этом случае нужно бут считать кол-во элеметов
    Так же можно добавить кэширование для поиска в массиве $products, небольшой прирост на повторяющихся запросах будет.

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

    <?php
    
    // for ($loop = 0; $loop < 10000; ++$loop) { // for benchmark
        $products = [];
        $section = $index = 0;
        $output = fopen(__DIR__.'/output.txt', 'w+');
        foreach (file(__DIR__.'/input.txt', FILE_IGNORE_NEW_LINES) as $line) {
            if (false === strpos($line, '=')) {
                ++$section;
                continue;
            }
            if (1 === $section) {
                $products[$index] = explode(' ', $line);
                ++$index;
                continue;
            }
            $request = explode(' ', $line);
            $total = 0;
            foreach ($products as $product) {
                $total += (int) !array_diff($request, $product);
            }
            fwrite($output, $total.PHP_EOL);
        }
        fclose($output);
    // }


    Чуток бенчмарков

    // for i in {0..10} ; do time php original.php; done
    //
    // php original.php  0,71s user 0,12s system 99% cpu 0,835 total
    // php original.php  0,69s user 0,13s system 99% cpu 0,820 total
    // php original.php  0,66s user 0,16s system 99% cpu 0,829 total
    // php original.php  0,66s user 0,19s system 99% cpu 0,854 total
    // php original.php  0,68s user 0,15s system 99% cpu 0,830 total
    // php original.php  0,66s user 0,16s system 99% cpu 0,831 total
    // php original.php  0,69s user 0,14s system 99% cpu 0,835 total
    // php original.php  0,68s user 0,15s system 99% cpu 0,832 total
    // php original.php  0,72s user 0,13s system 99% cpu 0,848 total
    // php original.php  0,71s user 0,14s system 99% cpu 0,852 total
    // php original.php  0,66s user 0,17s system 99% cpu 0,836 total
    
    // for i in {0..10} ; do time php optimized.php; done
    //
    // php optimized.php  0,33s user 0,52s system 71% cpu 1,205 total
    // php optimized.php  0,38s user 0,43s system 72% cpu 1,112 total
    // php optimized.php  0,35s user 0,38s system 70% cpu 1,050 total
    // php optimized.php  0,38s user 0,40s system 72% cpu 1,080 total
    // php optimized.php  0,35s user 0,41s system 71% cpu 1,055 total
    // php optimized.php  0,38s user 0,36s system 74% cpu 0,997 total
    // php optimized.php  0,40s user 0,36s system 71% cpu 1,057 total
    // php optimized.php  0,40s user 0,37s system 70% cpu 1,087 total
    // php optimized.php  0,32s user 0,42s system 74% cpu 0,994 total
    // php optimized.php  0,38s user 0,36s system 72% cpu 1,014 total
    // php optimized.php  0,36s user 0,36s system 71% cpu 1,025 total
    Ответ написан