Ответы пользователя по тегу Symfony
  • Как сделать нативный 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 комментария
  • JsonResponse убирает индексы массива из результата, если они начинаются с нуля?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    JsonResponse не при чем. Дело в json_encode. Вам нужна опция JSON_FORCE_OBJECT

    use Symfony\Component\HttpFoundation\JsonResponse;
    
    $data = [
        'List' => [
            0 => [
                'id' => 'id0',
            ],
            1 => [
                'id' => 'id1',
            ],
        ],
    ];
    dump(
        new JsonResponse(
            json_encode($data, JsonResponse::DEFAULT_ENCODING_OPTIONS | JSON_FORCE_OBJECT),
            200,
            [],
            true
        )
    );
    // or
    $response = new JsonResponse($data);
    $response->setEncodingOptions(JsonResponse::DEFAULT_ENCODING_OPTIONS | JSON_FORCE_OBJECT);
    dump($response->getContent());


    Либо, если это нужно регулярно и много где, можно создать свой JsonResponse, который наследуется от стандартного, в котором изменить encodingOptions на нужные
    Ответ написан
    Комментировать
  • Как вы получаете количество оставшихся записей для выборки подмножеств?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    https://github.com/BabDev/Pagerfanta

    use Pagerfanta\Pagerfanta;
    
    class PaginatedCollection
    {
        private int $page;
        private int $size;
        private int $total;
        private int $pages;
        private array $items;
    
        public function __construct(Pagerfanta $pagerfanta)
        {
            $this->page = $pagerfanta->getCurrentPage();
            $this->size = $pagerfanta->getMaxPerPage();
            $this->total = $pagerfanta->getNbResults();
            $this->pages = $pagerfanta->getNbPages();
            $this->items = iterator_to_array($pagerfanta);
        }
    
        public function getPage(): int
        {
            return $this->page;
        }
    
        public function getSize(): int
        {
            return $this->size;
        }
    
        public function getTotal(): int
        {
            return $this->total;
        }
    
        public function getPages(): int
        {
            return $this->pages;
        }
    
        public function getItems(): array
        {
            return $this->items;
        }
    }


    // Repository
    public function findPaginatedBySupplierId(int $supplier_id)
    {
        return new Pagerfanta(DoctrineORMAdapter($this->getSupplierIdQueryBuilder($supplier_id)));
    }
    
    private function getSupplierIdQueryBuilder(int $supplier_id)
    {
        $qb = $this->createQueryBuilder('sj');
        $qb = $qb
            ->select("sj")
            ->orderBy('sj.datetime', 'ASC')
            ->andWhere("sj.supplier = :supplier_id")
            ->setParameter("supplier_id", $supplier_id);
    
        return $qb;
    }


    // Controller
    public function action()
    {
        // ...
        return $this->json($this->paginatedCollection($repository->findPaginatedBySupplierId($id), $request));
    }
    
    // AbstractController
    protected function paginatedCollection(Pagerfanta $pagination, Request $request, int $size = 20): PaginatedCollection
    {
        $pagination = $this->paginate($pagination, $request, $size);
    
        return new PaginatedCollection($pagination);
    }
    
    protected function paginate(Pagerfanta $pagination, Request $request, int $size = 20): Pagerfanta
    {
        $pagination->setMaxPerPage($size);
        $pagination->setCurrentPage($request->query->getInt('page', 1));
    
        return $pagination;
    }


    {
        "page": 1,
        "size": 20,
        "total": 76,
        "pages": 4,
        "items": [...]
    }
    Ответ написан
  • Как десериализовать массив json-ов c отношением OneToMany?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Symfony serializer как десериализовать массив с объектами?

    Плюс, вам надо прописать типы в phpDoc
    /**
     * @var \App\Entity\IpContract[]
     *
     * @ORM\OneToMany(targetEntity="App\Entity\IpContract", mappedBy="users", orphanRemoval=true)
     */
    private $ipContracts;
    Ответ написан