Ответы пользователя по тегу Symfony
  • Не корректно работает Remember Me?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    IS_AUTHENTICATED_FULLY надо заменить на IS_AUTHENTICATED_REMEMBERED

    Вот документация

    access_control:
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: ROLE_ADMIN }
        - { path: ^/system/*, role: ROLE_ADMIN }
        - { path: ^/*, role: IS_AUTHENTICATED_REMEMBERED  }
    Ответ написан
  • Как отделить абстракцию от реализации?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    interface ExecutorInterface
    {
        public function exec(): string;
    }
    
    class ConcreteImplementationA implements ExecutorInterface
    {
        public function exec(): string
        {
            return 'A';
        }
    }
    
    class ConcreteImplementationB implements ExecutorInterface
    {
        public function exec(): string
        {
            return 'B';
        }
    }
    
    class VariantExecutor
    {
        public function __construct(private ServiceLocator $locator)
        {
        }
    
        public function exec(string $variant): string
        {
            return $this->locator->get($variant)->exec();
        }
    }
    
    class Controller
    {
        public function call(VariantExecutor $executor, string $variant): Response
        {
            return new Response($executor->exec($variant));
        }
    }

    #services.yaml
    app.executor.locator:
        class: Symfony\Component\DependencyInjection\ServiceLocator
        arguments:
            -
                a: App\ConcreteImplementationA
                b: App\ConcreteImplementationB
        tags:
            - { name: container.service_locator }
    Ответ написан
  • Как скрестить "symfony/dependency-injection" с переменными окружения?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    https://github.com/symfony/symfony/blob/5.4/src/Sy...

    /**
     * @param bool $resolveEnvPlaceholders Whether %env()% parameters should be resolved using the current
     *                                     env vars or be replaced by uniquely identifiable placeholders.
     *                                     Set to "true" when you want to use the current ContainerBuilder
     *                                     directly, keep to "false" when the container is dumped instead.
     */
    public function compile(bool $resolveEnvPlaceholders = false)


    Судя по всему вы не дампаете контейнер. Вам надо вызывать
    $container->compile(true);
    Ответ написан
    1 комментарий
  • Как сделать нативный SQL запрос для UPDATE с WHERE IN условием Doctrine 2?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Doctrine это умеет из коробки

    Можно через DBAL с помощью обычного SQL (это именно то, что спрашивается в вопросе)
    $ids = [1, 2, 3, 4];
    
    $this->getEntityManager()->getConnection()
        ->executeStatement('UPDATE some_table SET some_field = ? WHERE id IN (?)', [
            'value',
            $ids,
        ], [
            1 => Connection::PARAM_INT_ARRAY,
    ]);


    Можно через QueryBuilder
    $builder = $this->getEntityManager()->getConnection()->createQueryBuilder();
    $builder
        ->update('some_table')
        ->set('some_field', ':value')
        ->where($builder->expr()->in('id', ':ids'))
        ->setParameter('value', 'value')
        ->setParameter('ids', $ids, Connection::PARAM_INT_ARRAY)
    ;
    
    $builder->execute()


    Если все-таки используется DQL и ORM, то можно и через нее:
    $this->getEntityManager()
        ->createQuery('UPDATE App\Entity\SomeEntity se SET se.someValue = :value WHERE se.id IN (:ids)')
        ->execute(new ArrayCollection([
            new Parameter('value', 'value'),
            new Parameter('ids', $ids),
        ]))
    );


    Либо через QueryBuilder
    $builder = $this->createQueryBuilder('se');
    $builder
        ->update()
        ->set('se.someValue', ':value')
        ->where($builder->expr()->in('uu.id', ':ids'))
        ->setParameter('value', 'value')
        ->setParameter('ids', $ids)
    ;
    
    $builder->getQuery()->execute();
    Ответ написан
  • Как преобразовать правильно массив при отдаче формы во вью?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Либо добавьте метод \App\Entity\Group::__toString, либо добавьте опцию choice_label
    ->add('group', null, [
        'choice_label' => 'getName', // Метод, который вернет название группы
    ])
    Ответ написан
  • Как заранее заполнить поля формы?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Надо заполнить объект, который вы передаете в форму, нужными данными:
    $post = new Post();
    // Заполняем поля, которые присутствуют в форме
    $post->setPublishedAt(new \DateTime());
    $post->setTitle('New title');
    
    // Если объекты не используются, то заполнять надо массив
    // $post = [];
    // $post['publishedAt'] = new \DateTime();
    // $post['title'] = 'New title';
    
    $form = $this->createForm(PostType::class, $post);
    
    // ...
    
    return $this->render('post.html.twig', [
        'form' => $form->createView(),
    ])


    Если вдруг нет (почему?) возможности передать заполненные данные, то можно их инициализировать внутри FormType:
    $builder->get('publishedAt')->setData(new \DateTime());
    $builder->get('title')->setData('New title');

    либо
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
        $data = $event->getData();
        if ($data !== null) {
            return;
        }
        $event->setData([
            'title' => 'New title',
            'publishedAt' => new \DateTime(),
        ]);
    });
    Ответ написан
    9 комментариев
  • Как прикрутить телеграм хендлер в монолог через бандлы в Symfony?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    # servces.yaml
    services:
        Monolog\Handler\TelegramBotHandler:
            arguments:
                - '%env(TELEGRAM_BOT_KEY)%'
                - '%env(TELEGRAM_CHANNEL)%'

    # monolog.yaml
    monolog:
        handlers:
            message:
                type: fingers_crossed
                action_level: error
                excluded_http_codes: [ 400, 401, 403, 404 ]
                buffer_size: 50
                handler: deduplicated
            deduplicated:
                type: deduplication
                handler: telegram
            telegram: # Важна только эта часть
                type: service
                id: Monolog\Handler\TelegramBotHandler
                level: debug
    Ответ написан
    7 комментариев
  • Как добавить значение по умолчанию в Symfony form в методе ->add('company')?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Комментировать
  • Как проверить почему передается значение null?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    \App\System\App::__constructor => \App\System\App::__construct

    Надо было посмотреть где инициализируется $this->routes и там воспользоваться дебагером или обычным var_dump. Так как выполнение до этой строчки кода не дошло, вы бы заметили опечатку.
    Ответ написан
    1 комментарий
  • Symfony - Как изменить форму Бандла?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Можно использовать Form Type Extension

    use Symfony\Component\Form\AbstractTypeExtension;
    use Symfony\Component\Form\FormBuilderInterface;
    
    class BundleFormTypeExtension extends AbstractTypeExtension
    {
        public static function getExtendedTypes(): iterable
        {
            return [BundleFormType::class];
        }
    
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->remove('someField');
        }
    }
    Ответ написан
    Комментировать
  • Самодиагностика CRM системы?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Делал нечто подобное, получилось так:
    namespace App\Doctor;
    
    use App\Doctor\Check\CheckInterface;
    
    final class Doctor
    {
        /**
         * @var CheckInterface[]
         */
        private iterable $checks;
    
        public function __construct(iterable $checks)
        {
            $this->checks = $checks;
        }
    
        /**
         * @return Violation[]|array
         */
        public function check(): array
        {
            $violations = [];
            foreach ($this->checks as $check) {
                $violations[$check->feature()] = array_merge($violations[$check->feature()] ?? [], $check->violations());
            }
    
            return $violations;
        }
    }

    namespace App\Doctor\Check;
    
    use App\Doctor\Violation;
    
    interface CheckInterface
    {
        public function feature(): string;
    
        /**
         * @return Violation[]
         */
        public function violations(): array;
    }

    services:
        _instanceof:
            App\Doctor\Check\CheckInterface:
                tags:
                    - { name: app.doctor.check }
    
        App\Doctor\Doctor:
            arguments:
                $checks: !tagged app.doctor.check

    public function doctorAction(): JsonResponse
    {
        return $this->json($this->doctor->check());
    }

    {
        "foo": [], // Ok
        "bar": [
            { "message": "Отсутствуют статусы", "treatment": "Добавьте статусы" }
        ]
    }


    Как вариант, можно группировать проверки по фичам, чтоб не проверять все, когда нужна информация по конкретной фиче, но для этого надо будет CompilerPass заюзать
    Ответ написан
    3 комментария
  • Как сообщить EntityManager о сущности?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    https://symfony.com/doc/current/components/seriali...

    Это можно обернуть в какой-нибудь кастомный нормалайзер
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
    use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
    
    class MyObjectDenormalizer implements DenormalizerInterface
    {
        private ObjectNormalizer $objectNormalizer;
        private EntityManagerInterface $entityManager;
    
        public function __construct(ObjectNormalizer $objectNormalizer, EntityManagerInterface $entityManager)
        {
            $this->objectNormalizer = $objectNormalizer;
            $this->entityManager = $entityManager;
        }
    
        public function denormalize($data, string $type, string $format = null, array $context = [])
        {
            if ($id = $data['id'] ?? null) {
                $object = $this->entityManager->getRepository($type)->find($id);
                $context = [
                    AbstractObjectNormalizer::OBJECT_TO_POPULATE => $object,
                ];
                unset($data['id']);
            }
    
            return $this->objectNormalizer->denormalize($data, $type, $format, $context);
        }
    
        public function supportsDenormalization($data, string $type, string $format = null)
        {
            return $this->objectNormalizer->supportsDenormalization($data, $type, $format);
        }
    }


    Но лучше подставлять объект в контроллере на основании данных из роута (/comment/{id}/edit), т.к. есть возможность подменить id и отредактировать другую сущность (к которой, к примеру, у пользователя доступа нет)
    Ответ написан
    1 комментарий
  • Можно ли в symfony проверить есть ли доступ у определённой роли к url адресу?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Проще зайти с другой стороны.
    - при выходе помечаем этот факт в сессии
    - если после редиректа он ловит access-denied и присутствует флаг, то просто перенаправляем на главную
    - убираем флаг, как только пользователь приземлился на страницу с 2XX кодом, чтоб в дальнейшем его перенапаряляло на страницу логина

    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    use Symfony\Component\HttpFoundation\RedirectResponse;
    use Symfony\Component\HttpKernel\Event\ExceptionEvent;
    use Symfony\Component\HttpKernel\Event\ResponseEvent;
    use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
    use Symfony\Component\Security\Core\Exception\AccessDeniedException;
    use Symfony\Component\Security\Http\Event\LogoutEvent;
    
    class LogoutRedirectSubscriber implements EventSubscriberInterface
    {
        private const KEY = 'logout';
    
        private UrlGeneratorInterface $urlGgenerator;
    
        public static function getSubscribedEvents()
        {
            return [
                ExceptionEvent::class => ['onException', 2], // Before \Symfony\Component\Security\Http\Firewall\ExceptionListener
                ResponseEvent::class => 'onResponse',
                LogoutEvent::class => 'onLogout',
            ];
        }
    
        public function __construct(UrlGeneratorInterface $urlGgenerator)
        {
            $this->urlGgenerator = $urlGgenerator;
        }
    
        public function onException(ExceptionEvent $event): void
        {
            if (!$event->isMasterRequest()) {
                return;
            }
            $exception = $event->getThrowable();
            if (!$exception instanceof AccessDeniedException) {
                return;
            }
            $session = $event->getRequest()->getSession();
            if ($session->has(self::KEY)) {
                $event->setResponse(new RedirectResponse($this->urlGgenerator->generate('index')));
                $event->stopPropagation();
            }
        }
    
        public function onResponse(ResponseEvent $event): void
        {
            if (!$event->isMasterRequest()) {
                return;
            }
            if ($event->getResponse()->getStatusCode() >= 300) {
                return;
            }
            $session = $event->getRequest()->getSession();
            if ($session->has(self::KEY)) {
                $session->remove(self::KEY);
            }
        }
    
        public function onLogout(LogoutEvent $event): void
        {
            $event->getRequest()->getSession()->set(self::KEY, true);
        }
    }


    На вопрос "Можно ли в symfony проверить есть ли доступ у определённой роли к url адресу?" - ответ нет, разве что вы добавите всем роутам метаданные в options и будете проверять их в обработчике LogoutEvent
    Ответ написан
  • В какой момент проверять уникальность?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Комментировать
  • Своя иерархия папок в Symfony?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    1. https://github.com/symfony/recipes/blob/master/doc...
    Но идея плохая, т.к. миграции - это не код.
    2. Можно при конфигурировании контейнера и роутов в Kernel.php разбирать структуру папок и подключать все динамически там.
    3. Нет. Как создать entity не по стандартному пути?

    FYI, моя структура папок сейчас выглядит так:
    migrations/
    src/
    -- Controller/
    ---- User/
    ---- ModuleName/
    -- Entity/
    ---- User/
    ---- ModuleName/
    -- User/
    --- Dto/
    --- Repository/
    --- Service/
    -- ModuleName/
    --- Dto/
    --- Repository/
    --- Service/

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

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    По вашей же ссылке предлагается решение:
    // src/AppBundle/Form/CustomerType.php
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        //....
        $builder
            ->add('phones',CollectionType::class, array(
                'by_reference' => false,
                // ...
            ));
        //....
    }


    class Customers implements UserInterface
    {
        /**
         * @ORM\OneToMany(targetEntity="AppBundle\Entity\Phone", mappedBy="customer_id", cascade={"persist", "remove", "merge"})
         */
        private $phones; // Это же коллекция, нужно множественное число, чтобы работали adder и remover
    
        public function addPhone(Phone $phone)
        {
            $phone->setCustomer($this);
            $this->phones->add($phone);
        }
    
        public function removePhone(Phone $phone)
        {
            $phone->setCustomer(null);
            $this->phones->removeElement($phone);
        }
    }


    // src/AppBundle/Form/PhoneType.php
    // $builder->add('customerId', HiddenType::class); // не нужен
    Ответ написан
    1 комментарий
  • Как добавить первичного администратора в Symfony?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Если используется FOSUserBundle, то надо использовать команду
    bin/console fos:user:create username em@ai.il 'p@55w0rd'
    .

    В случае своего решения, эта команду надо создать самому. У меня она выглядит как-то так (по сути повторяет код экшена контроллера со своими нюансами)

    CreateCommand

    /**
     * @psalm-suppress PropertyNotSetInConstructor
     */
    class CreateCommand extends AbstractCommand
    {
        private DocumentManager $documentManager;
        private ValidatorInterface $validator;
        private PasswordGenerator $passwordGenerator;
        private AdministratorMapper $mapper;
    
        public function __construct(
            DocumentManager $documentManager,
            ValidatorInterface $validator,
            PasswordGenerator $passwordGenerator,
            AdministratorMapper $mapper
        ) {
            parent::__construct();
    
            $this->documentManager = $documentManager;
            $this->validator = $validator;
            $this->passwordGenerator = $passwordGenerator;
            $this->mapper = $mapper;
        }
    
        /**
         * @psalm-suppress MissingReturnType
         */
        protected function configure()
        {
            $this
                ->setName('app:administrator:create')
                ->addArgument('username', InputArgument::REQUIRED, 'Username')
                ->addOption('password', 'p', InputOption::VALUE_REQUIRED, 'Password')
                ->setDescription('Creates administrator')
            ;
        }
        
        protected function execute(InputInterface $input, OutputInterface $output)
        {
            /** @var string $username */
            $username = $input->getArgument('username');
            /** @var string|null $plainPassword */
            $plainPassword = $input->getOption('password');
            if (!$plainPassword) {
                $plainPassword = $this->passwordGenerator->generate();
            }
    
            $model = new AdministratorModel();
            $model->enabled = true;
            $model->username = $username;
            $model->password = $plainPassword;
    
            $errors = $this->validator->validate($model);
            if (\count($errors) > 0) {
                $this->io->error('Can\'t create administrator');
                $this->printConstraintViolations($errors);
    
                return 1;
            }
    
            $administrator = $this->mapper->map($model);
    
            $this->documentManager->persist($administrator);
            $this->documentManager->flush();
    
            $this->io->writeln(sprintf('Administrator <info>%s</info> with password <info>%s</info> has been created', $administrator->getUsername(), $plainPassword));
    
            return 0;
        }
    }



    bin/console app:administrator:create vasx3

    Создавать пользователей, а тем более администраторов, через миграции и фикстуры - риск, т.к. в случае утечки кода все эти данные будут доступны третьим лицам
    Ответ написан
    4 комментария
  • Как создать entity не по стандартному пути?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Так нельзя

    Как вариант (если у вас все сущности все равно находятся в неймеспейсе Entity, т.к. он захардкожен) - конфигурировать бандл перед каждой генерацией, а потом возвращать назад
    maker:
        root_namespace: 'App\Common'


    Не забудьте добавить неймспейс в конфиг доктрины до генерации, т.к. иначе вы сгенерируете класс сущности и репозиторий, но добавить поля вам не дадут.
    mappings:
        App:
            is_bundle: false
            type: annotation
            dir: '%kernel.project_dir%/src/Entity'
            prefix: 'App\Entity'
            alias: App
        AppСommon:
            is_bundle: false
            type: annotation
            dir: '%kernel.project_dir%/src/Common/Entity'
            prefix: 'App\Common\Entity'
            alias: AppСommon
    Ответ написан
    2 комментария
  • Как в Symfony работать с вложенными объектами и формами?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Формы не очень хорошо работают с динамическим контентом, оптимальным решением будет перенести форму на фронтенд, а со стороны бекенда использовать serializer + validator + argument-resolver (см. Как правильно фильтровать и мапить данные при реализации API на Symfony4?)
    Если у вас в specs всегда один и тот же набор объектов и нет динамических параметров, то вложенные формы вполне могут сработать
    Ответ написан
    2 комментария
  • Как в symfony secure проверить авторизованность пользователя?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    https://symfony.com/doc/current/components/securit...
    /** @var AuthorizationCheckerInterface $authorizationChecker */
    if (!$authorizationChecker->isGranted('ROLE_USER')) {
        throw new AccessDeniedException();
    }


    Если роль пользователя неважна (хотя хорошая практика все-таки всегда выдавать базовую роль)
    https://symfony.com/doc/current/security.html#chec...
    /** @var AuthorizationCheckerInterface $authorizationChecker */
    if (!$authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
        throw new AccessDeniedException();
    }
    Ответ написан
    4 комментария