Задать вопрос
  • Как создать RouteCollection из папки контроллеров с аннотироваными методами?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    1. Вам надо создать AnnotationClassLoader, задачей которого преобразовывать аннотации в роуты. Абстрактный класс есть в компоненте, вам надо только определить метод configureRoute. За основу можно взять код из фреймворка
    Должно получится что-то вроде этого:

    namespace App\Router;
    
    use Symfony\Component\Routing\Loader\AnnotationClassLoader as BaseLoader;
    use Symfony\Component\Routing\Route;
    
    class AnnotationClassLoader extends BaseLoader
    {
        protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot)
        {
            if ('__invoke' === $method->getName()) {
                $route->setDefault('_controller', $class->getName());
            } else {
                $route->setDefault('_controller', $class->getName().'::'.$method->getName());
            }
        }
    }



    2. И сконфигурировать все это дело (придется использовать symfony/config):
    use секция, на всякий случай

    use App\Router\AnnotationClassLoader;
    use Doctrine\Common\Annotations\AnnotationReader;
    use Doctrine\Common\Annotations\AnnotationRegistry;
    use Symfony\Component\Config\FileLocator;
    use Symfony\Component\Config\Loader\DelegatingLoader;
    use Symfony\Component\Config\Loader\LoaderResolver;
    use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
    use Symfony\Component\Routing\Loader\AnnotationFileLoader;
    use Symfony\Component\Routing\Router;


    $autoloader = require_once __DIR__ . '/../vendor/autoload.php';
    
    AnnotationRegistry::registerLoader(array($autoloader, 'loadClass'));
    
    $reader = new AnnotationReader();
    $annotationClassLoader = new AnnotationClassLoader($reader);
    
    $fileLocator = new FileLocator([__DIR__]);
    
    $loaderResolver = new LoaderResolver([
        new AnnotationFileLoader($fileLocator, $annotationClassLoader),
        new AnnotationDirectoryLoader($fileLocator, $annotationClassLoader),
    ]);
    
    $loader = new DelegatingLoader($loaderResolver);
    
    $router = new Router($loader, __DIR__ . '/../src/Controller');
    
    dump($router->getRouteCollection());
    Ответ написан
    2 комментария
  • Symfony Form & FOSRESTBundle: Как загрузить файл?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Вы забыли добавить данные из files. Можно посмотреть как это сделано в HttpFoundationRequestHandler
    $params = $request->request->all();
    $files = $request->files->all();
    
    $data = array_replace_recursive($params, $files);
    
    $form->submit($data);


    Если вы хотите посылать json + файл, то файл придется передавать в base64: https://stackoverflow.com/questions/4083702/postin...
    либо немного подкорректировать структуру запроса (json передавать в одном поле, а файлы - в другом):
    https://stackoverflow.com/a/13076550/1247234

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

    Но все-таки лучше формы не использовать, а делать напрямую через serializer/validator: Как правильно фильтровать и мапить данные при реализации API на Symfony4? , т.к. основная фишка форм - это их вывод, который вы не используете
    Ответ написан
    1 комментарий
  • Как создать CSV используя symfony serializer?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    $data = [
       [
          'Name' => 'Alex',
          'Age' => '26',
       ],
       [
          'Name' => 'Alex2',
          'Age' => '20',
       ],
    ];
    Ответ написан
    Комментировать
  • Как с помощью Sypfony Validation 3.4 проверить ключ на тип массиву + его элементы, но не проверять элементы если не массив?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Судя по всему это "баг"версии 3.4: https://github.com/symfony/symfony/issues/26463
    Вот PR, который решает проблему, но он замерджен в 4.2: https://github.com/symfony/symfony/pull/27917

    В качестве решения:
    $rules = new Assert\Collection([
        "items"    => new Assert\Required([
            new Assert\NotBlank(),
            new Assert\Callback(function ($value, ExecutionContextInterface $context) {
                $validator = $context->getValidator();
    
                $violations = $validator->validate($value, new Assert\Type('array'));
    
                if ($violations->count() > 0) {
                    /** @var ConstraintViolationInterface $violation */
                    foreach ($violations as $violation) {
                        $context
                            ->buildViolation($violation->getMessage(), $violation->getParameters())
                            ->atPath('items')
                            ->addViolation()
                        ;
                    }
    
                    return;
                }
    
                $violations = $validator->validate($value, new Assert\All([
                    new Assert\Type('array'),
                    new Assert\Collection([
                        "id"        => new Assert\NotBlank(),
                        "quantity"  => new Assert\NotBlank(),
                    ])
                ]));
    
                /** @var ConstraintViolationInterface $violation */
                foreach ($violations as $violation) {
                    $context
                        ->buildViolation($violation->getMessage(), $violation->getParameters())
                        ->atPath('items'. $violation->getPropertyPath())
                        ->addViolation()
                    ;
                }
            }),
        ]),
    ]);
    
    $validator = Validation::createValidator();
    
    $data = [
        'items' => [
            [
                'id' => 'id',
                'quantity' => 'quantity',
            ],
        ],
    ];
    
    dump($validator->validate($data, $rules)); // Ok
    
    $data = [
        'items' => 1,
    ];
    
    dump($validator->validate($data, $rules)); // Ko
    
    $data = [
        'items' => [
            [
                'id' => 'id',
                'quantity' => 'quantity',
            ],
            [
                'id2' => 'id',
                'quantity2' => 'quantity',
            ]
        ],
    ];
    
    dump($validator->validate($data, $rules)); // Ko
    Ответ написан
  • Как работает drone?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Конфиг doctrine.dbal.connections.default.server_version должен быть инициализирован (для каждого соединения)
    Ответ написан
    Комментировать
  • Почему прилетает 403?

    BoShurik
    @BoShurik
    Symfony developer
    Меня немного смущает ошибка 403, вместо 400, но судя по всему ошибка в том, что вы файл неправильно передаете. Как минимум, это будет следующая ошибка, с которой вы столкнетесь :)
    $response = json_decode($make_request->send(
          'POST',
          $config['url'].'/files',
          [
            'Content-Type' => 'multipart/form-data',
            'Authorization' => $config['token'],
            'Accept' => 'application/vnd.api+json',
          ],
          ['file' => new \CURLFile($_FILES['file']['tmp_name'], null, $_FILES['file']['name']]),
        ));
    Ответ написан
  • Как десерилизовать массив, который приходит из формы Symfony 4?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Не понял, а почему так странно создаете сериалайзер?
    Во-первых,ObjectNormalizer имеет несколько больше зависимостей для полноценной его работы
    Во-вторых, правильней все-таки его получать через DI.
    public function submitAction(SerializerInterface $serializer, Request $request)
    {
        if ($request->isXmlHttpRequest()) {
            $data = $request->getContent();
            $result = $serializer->deserialize($data, SiteCreateDto::class, 'json');
            var_dump($result); exit;
        }
    }


    Если из коробки ObjectNormalizer не помог, то надо будет написать свой: https://symfony.com/doc/current/serializer/custom_...
    Ответ написан
  • Как улучшить архитектуру и убрать дублирование?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Я бы сделал так. Тем более, если там добавятся еще методы, то вы утоните в if-ах при вашем подходе

    <?php
    
    namespace App\Controller;
    
    use App\Entity\User;
    use App\Service\UserService;
    use Symfony\Component\DependencyInjection\ServiceLocator;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Security\Core\Exception\AccessDeniedException;
    use Symfony\Component\Security\Core\Security;
    use Twig\Environment;
    
    interface AdminControllerInterface
    {
        public function index(): Response;
    
        public function read(string $id): Response;
    }
    
    class UserController implements AdminControllerInterface
    {
        /**
         * Security - объект-helper, он содержит в себе AuthorizationCheckerInterface
         *
         * @var Security
         */
        private $security;
    
        /**
         * @var ServiceLocator
         */
        private $controllerLocators;
    
        public function __construct(Security $security, ServiceLocator $controllerLocators)
        {
            $this->security = $security;
            $this->controllerLocators = $controllerLocators;
        }
    
        public function index(): Response
        {
            $role = $this->getRole();
    
            if (!$this->controllerLocators->has($role)) {
                throw new AccessDeniedException();
            }
    
            /** @var AdminControllerInterface $controller */
            $controller = $this->controllerLocators->get($role);
    
            return $controller->index();
        }
    
        public function read(string $id): Response
        {
            $role = $this->getRole();
    
            if (!$this->controllerLocators->has($role)) {
                throw new AccessDeniedException();
            }
    
            /** @var AdminControllerInterface $controller */
            $controller = $this->controllerLocators->get($role);
    
            return $controller->read($id);
        }
    
        private function getRole(): string
        {
            // Для простоты представим, что роль у пользователя одна. Иначе искать среди ролей
            // ROLE_SUPER_ADMIN и ROLE_CUSTOMER_ADMIN и возвращаем с соответствующим приоритетом
            return current($this->security->getToken()->getRoles());
        }
    }
    
    class SuperAdminController implements AdminControllerInterface
    {
        private $userService;
    
        /**
         * @var Environment
         */
        private $twig;
    
        public function __construct(
            UserService $userService,
            Environment $twig
        ) {
            $this->userService = $userService;
            $this->twig = $twig;
        }
    
        public function index(): Response
        {
            $users = $this->userService->getAll();
    
            return new Response($this->twig->render('admin/users.html.twig', [
                'users' => $users
            ]));
        }
    
        public function read(string $id): Response
        {
            $user = $this->userService->getById($id);
    
            return new Response($this->twig->render('admin/user.html.twig', [
                'user' => $user
            ]));
        }
    }
    
    class CustomerAdminController implements AdminControllerInterface
    {
        private $userService;
    
        /**
         * @var Security
         */
        private $security;
    
        /**
         * @var Environment
         */
        private $twig;
    
        public function __construct(
            UserService $userService,
            Environment $twig
        ) {
            $this->userService = $userService;
            $this->twig = $twig;
        }
    
        public function index(): Response
        {
            /** @var User $user */
            $user = $this->security->getUser();
    
            $users = $this->userService->findByRoleAndWorkspaceId(
                User::ROLE_TEAM_MEMBER,
                $user->getWorkspace()->getId()
            );
    
            return new Response($this->twig->render('user/users.html.twig', [
                'users' => $users
            ]));
        }
    
        public function read(string $id): Response
        {
            $user = $this->userService->getByIdAndWorkspaceId($id);
    
            return new Response($this->twig->render('user/user.html.twig', [
                'user' => $user
            ]));
        }
    }


    app.admin_controller_locator:
        class: Symfony\Component\DependencyInjection\ServiceLocator
        arguments:
            -
                ROLE_SUPER_ADMIN: '@App\Controller\SuperAdminController'
                ROLE_CUSTOMER_ADMIN: '@App\Controller\CustomerAdminController'
        tags: ['container.service_locator']
    Ответ написан
  • Проверить сущности на уникальность по полю Symfony 4?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    UniqueEntity работает только с Entity. Если хотите валидировать таким образом DTO, то придется написать свой валидатор. За основу можно взять встроенный: https://github.com/symfony/symfony/blob/master/src...

    Я использую что-то вроде этого: https://gist.github.com/BoShurik/df63d1f51bc01465e...
    Ответ написан
    5 комментариев
  • Как создать сервис для класса и передать туда аргументы Symfony 4?

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

    services:
        _defaults:
            autowire: true
            autoconfigure: true
            bind:
                App\Repository\Repository $cachedRepository: '@App\Repository\User\CachedUserRepository'
    
        App\Repository\Repository: '@App\Repository\User\DoctrineUserRepository'
        App\Entity\UniqueEntityInterface: '@App\Entity\User'
    
        user_exists_checker:
            class: App\ExistsChecker
            bind:
                App\Repository\Repository: '@App\Repository\User\ApiUserRepository'


    Может пригодится, если несколько реализаций интерфейсов: https://symfony.com/blog/new-in-symfony-3-4-local-...

    Чтобы переопределить реализацию интерфейса для конкретного сервиса
    user_exists_checker:
        class: App\ExistsChecker
        bind:
            App\Repository\Repository: '@App\Repository\User\ApiUserRepository'


    Можно переопределить интерфейс для определенного названия переменной https://symfony.com/blog/new-in-symfony-4-2-autowi...
    _defaults:
            autowire: true
            autoconfigure: true
            bind:
                App\Repository\Repository $cachedRepository: '@App\Repository\User\CachedUserRepository'

    В примере выше, если в конструкторе вместо App\Repository\Repository $repository будете использовать App\Repository\Repository $cachedRepository, то вам придет App\Repository\User\CachedUserRepository
    Ответ написан
    2 комментария
  • Как побороть ошибку с namespace в symfony 4?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Кладите эти комманды в отельную папку (не src) и пропишите в composer.json для них соответствующий неймспейс в секции autoload. Но по факту в итоге у вас не получится интегрировать эту библиотеку с symfony (точнее получится, но это будут костыли), т.к. невозможно пробросить свои зависимости в конструктор
    "autoload": {
        "psr-4": { "Longman\\TelegramBot\\Commands\\": "commands/" }
    },
    Ответ написан
    Комментировать
  • Как с помощью Symfony Secure разрешить доступ только к одному id в рест-роуте?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Самое простое - сделать шорткат:
    вместо
    /users/123 и /users/123/всечтоугодно
    например
    /my и /my/всечтоугодно
    Проще и с точки зрения DX, и с UX.

    Если очень хочется оставить как есть, то надо использовать Voter и городить соответствующую проверку в контроллере. При желании можно эту проверку сбросить на Argument Resolver
    Ответ написан
  • Как задать приоритет маршрутов (маршрутизации)?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Надо перенести код из config/routes/annotations.yaml в config/routes.yml и отдельно прописать нужный контроллер в конце списка:
    config/routes.yml
    controllers:
        resource: ../src/Controller/
        type: annotation
    
    page_controller:
        resource: ../src/Controller/TextPageController.php
        type: annotation


    config/routes/dev/* - тут все стандартно

    -------------------------- -------- -------- ------ ----------------------------------- 
      Name                       Method   Scheme   Host   Path                               
     -------------------------- -------- -------- ------ ----------------------------------- 
      _twig_error_test           ANY      ANY      ANY    /_error/{code}.{_format}           
      _wdt                       ANY      ANY      ANY    /_wdt/{token}                      
      _profiler_home             ANY      ANY      ANY    /_profiler/                        
      _profiler_search           ANY      ANY      ANY    /_profiler/search                  
      _profiler_search_bar       ANY      ANY      ANY    /_profiler/search_bar              
      _profiler_phpinfo          ANY      ANY      ANY    /_profiler/phpinfo                 
      _profiler_search_results   ANY      ANY      ANY    /_profiler/{token}/search/results  
      _profiler_open_file        ANY      ANY      ANY    /_profiler/open                    
      _profiler                  ANY      ANY      ANY    /_profiler/{token}                 
      _profiler_router           ANY      ANY      ANY    /_profiler/{token}/router          
      _profiler_exception        ANY      ANY      ANY    /_profiler/{token}/exception       
      _profiler_exception_css    ANY      ANY      ANY    /_profiler/{token}/exception.css
    // Controllers                    
      index                      ANY      ANY      ANY    /       
    // Page Controller                           
      page_show                  ANY      ANY      ANY    /{pageSlug}                        
      page_item                  ANY      ANY      ANY    /{pageSlug}/{parameters}           
     -------------------------- -------- -------- ------ -----------------------------------


    В Symfony 5.1 появилась возможность выставлять приоритет у роутов, заданных через аннотацию
    Ответ написан
    2 комментария
  • Нерешимая проблема с Symfony - как решить?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Этот код будет работать только в Symfony
    class AccountRepository extends ServiceEntityRepository
    {
        public function __construct(RegistryInterface $registry)
        {
            parent::__construct($registry, Account::class);
        }
    }

    Вам надо наследоваться от стандартного EntityRepository
    class AccountRepository extends \Doctrine\ORM\EntityRepository
    {
    
    }


    Чтобы в этом случае получить репозиторий из контейнера надо его описать через фабрику:
    App\Repository\AccountRepository:
        arguments:
            - 'App\Entity\Account' # Entity class
        factory: ['@doctrine', 'getRepository']
    Ответ написан
  • Существование монитора безопасности в Symfony/Laravel?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Похоже на web application firewall
    https://modsecurity.org/

    Очень сомневаюсь, что такой продукт существует в качестве библиотеки
    Ответ написан
  • Чем отличается версия Symfony для traditional web application от версии microservice?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    If you are building a traditional web application:

    composer create-project symfony/website-skeleton my_project

    https://github.com/symfony/website-skeleton

    If you are building a microservice, console application or API

    composer create-project symfony/skeleton my_project

    https://github.com/symfony/skeleton

    В первом варианте уже предустановлены базовые вещи (ORM, swiftmailer, Twig, etc). По сути аналог https://github.com/symfony/symfony-standard для Symfony <= 3. Во втором - только то, что необходимо. Подразумевается, что вы доставите туда только то что нужно конкретно для вашего сервиса. Никто не мешает начинать с symfony/skeleton и для традиционного сайта.
    Ответ написан
    8 комментариев
  • Как валидировать внутренний массив в Symfony 4, который содержит объекты?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Если я правильно вас понял:
    $constraint = new Assert\Collection(
        [
            'fields' => [
                'cf_sub_products' => [
                    new Assert\NotBlank(),
                    new Assert\Type('array'),
                    new Assert\All([
                        new Assert\Collection([
                            'fields' => [
                                "size" => [
                                    new Assert\NotBlank(),
                                ],
                                "cf_product_id" => [
                                    new Assert\NotBlank(),
                                ],
                                "enabled" => [
                                    new Assert\NotBlank(),
                                ],
                                "price" => [
                                    new Assert\NotBlank(),
                                ],
                                "sku" => [
                                    new Assert\NotBlank(),
                                ],
                            ],
                        ])
                    ])
                ]
            ]
        ]
    );
    $violations = $validator->validate($body, $constraint);
    Ответ написан
    2 комментария
  • Как валидировать внутренний массив в Symfony 4?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    https://symfony.com/doc/current/reference/constrai...
    'toEmail' => [
        new Assert\NotBlank(),
        new Assert\Type('array'),
        new Assert\All([
            'constraints' => [
                new Assert\NotBlank(),
                new Assert\Email(),
            ],
        ])
    ]
    Ответ написан
    Комментировать
  • Как организовать роли в Symfony?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    security.yml
    security:
        access_control:
            - { path: ^/ }

    RequestVoter.php
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
    use Symfony\Component\Security\Core\Security;
    
    class RequestVoter implements VoterInterface
    {
        /**
         * @var Security
         */
        private $security;
    
        public function __construct(Security $security)
        {
            $this->security = $security;
        }
    
        /**
         * @inheritDoc
         */
        public function vote(TokenInterface $token, $subject, array $attributes)
        {
            if (!$subject instanceof Request) {
                return self::ACCESS_ABSTAIN;
            }
    
            $route = $subject->attributes->get('_route');
            $routeRole = $this->getRoleByRoute($route);
    
            if ($this->security->isGranted($routeRole)) {
                return self::ACCESS_GRANTED;
            }
    
            return self::ACCESS_DENIED;
        }
    
        protected function getRoleByRoute(string $route): string
        {
            // ...
        }
    }
    Ответ написан
    3 комментария