Ответы пользователя по тегу Symfony
  • Как в symfony создать сервис, у которого зависимость определяется окружением?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Если зависимость от APP_ENV, то проще так
    # config/services.yaml
    services:
        DependencyInterface: '@RealDependency'

    # config/services_test.yaml
    services:
        DependencyInterface: '@FakeDependency'

    Если нужна зависимость от какой-то другой переменной окружения, то надо использовать вариант Максим Федоров , т.к. контейнер компилируемый и на этапе компиляция значение из переменных окружения неизвестны
    Ответ написан
    2 комментария
  • Symfony 4: как отключить кеширование классов и всего такого в var/cache?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Отключать не стоит, т.к. контейнер и роутинг компилируемые by design, т.о. время ответа будет очень долгим (4000ms vs 250ms на моем приложении)
    Если очень хочется, то можно сделать так:
    // \App\Kernel
    protected function initializeContainer()
    {
        if ($_ENV['APP_ENV'] === 'dev') {
            $container = $this->buildContainer();
            $container->setParameter('container.build_id', 'id');
            $container->setParameter('container.build_hash', 'hash');
            $container->set('kernel', $this);
            $container->compile(true);
    
            $this->container = $container;
        } else {
            parent::initializeContainer();
        }
    }

    Но чтоб это заработало, мне пришлось отключить smart-core/accelerator-cache-bundle, возможно другие бандлы тоже несовместимы с таким режимом.

    Так что в вашем случае я бы все-таки рекомендовал как-то законфигурировать докер.
    Ответ написан
    3 комментария
  • Как в symfony 3.4 вывести логин (или какие-либо данные) в базовый шаблон?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    1. Рендер контроллера внутри шаблона
    https://symfony.com/doc/3.4/templating/embedding_c...
    {{ render(controller(
        AppBundle:Article:recentArticles',
        { 'max': 3 }
    )) }}

    2. Кастомный Twig Extension, который добавляет функцию, реализующую нужную логику
    https://symfony.com/doc/3.4/templating/twig_extens...
    {{ articles(3) }}
    3. Если надо выводить что-то простое, к примеру, версию приложения, то можно использовать глобальные переменные
    https://symfony.com/doc/3.4/templating/global_vari...
    {{ app_version }}
    4. Какие-то вещи доступны в глобальной переменнойapp, к примеру, тот же инстанс залогиненого пользователя
    {{ app.user.username }}

    Это справедливо для всех версий Symfony
    Ответ написан
    Комментировать
  • Как в symfony 3.4 вывести и отправить форму для сущности в списке сущностей?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    $form = $this->container->get('form.factory')
        ->createNamed(
            'account_'. $account->getId(), 
            AccountType::class, 
            $account
        )
    ;


    По поводу кода
    public function indexAction(Request $request)
    {
        $accounts = $this
            ->getDoctrine()
            ->getRepository('AppBundle:Account')
            ->findActive();
    
        $forms = [];
        foreach ($accounts as $key => $account){
    
            $form = $this->container->get('form.factory')->createNamed('account_'. $account->getId(), AccountType::class, $account);
    
            // Не надо добавлять кнопки сабмита, их рекомендуется добавлять непосредственно в шаблоне обычным html
            // $form->add('submit', SubmitType::class);
    
            $form->handleRequest($request);
    
            // Не надо инжектить форму в сущность, легче просто передать формы в шаблон
            // $account->setForm($form);
            // $account->setFormView($formView);
    
            if($form->isSubmitted() && $form->isValid()){
    
                // Объекты передаются по ссылке, нет смысла в получение $account повторно
                // $data = $form->getData();
    
                $this->addFlash('success', 'Saved');
    
                $em = $this->getDoctrine()->getManager();
                // Это актуально только для создания сущности, при редактировании - это не нужно
                // $em->persist($data);
                $em->flush();
    
                return $this->redirectToRoute('account_list');
            } else {
                $forms[$account->getId()] = $form->createView();
            }
        }
    
        return [
            'forms' => $forms,
            'accounts' => $accounts,
        ];
    }


    {# {{ form_row(account.formView) }} #}
    {{ form_start(forms[account.id]) }}
        {{ form_widget(forms[account.id]) }}
        <button>Submit</button>
    {{ form_end(forms[account.id]) }}


    P.S. В идеале бы разделить обновление и список сущностей по разным экшенам
    Ответ написан
    2 комментария
  • Как правильно использовать роут symfony component?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    index:
      path: /
      defaults:
        _controller: 'App\Controllers\CommentController::get'
      methods: ['GET']
    
    set:
      path: /
      defaults:
        _controller: 'App\Controllers\CommentController::set'
      methods: ['POST']
    Ответ написан
    6 комментариев
  • Как использовать RabbitMqBundle в Symfony4?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    Документация
    class TaskController
    {
        /**
         * @var ProducerInterface
         */
        private $producer;
    
        public function __construct(ProducerInterface $producer)
        {
            $this->producer = $producer;
        }
    
        public function indexAction($name)
        {
            $this->producer->publish('test');
    
            return new Response();
        }
    }

    # services.yaml
    services:
        App\Controller\TaskController:
            arguments:
                - '@old_sound_rabbit_mq.task_producer'

    либо (если producer только одни)
    # services.yaml
    services:
        OldSound\RabbitMqBundle\RabbitMq\ProducerInterface: '@old_sound_rabbit_mq.task_producer'

    и понадеяться на autowire
    Еще может пригодится: Local service binding
    Ответ написан
    8 комментариев
  • Symfony. Вывод списка статей с захватом имени автора статьи с помощью один-ко-многим. Как не захватывать весь объект Автора, а только его имя?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    https://www.doctrine-project.org/projects/doctrine...

    $query = $em->createQuery(
    'SELECT NEW ArticleDTO(ar.title, ar.imagePath, ar.publishedAt, CONCAT(au.firstName, ' ', au.lastName)) 
    FROM Article ar JOIN ar.author au');
    $articles = $query->getResult(); // array of ArticleDTO


    Это можно разместить где-нибудь в репозитории в отдельном методе

    Так же может быть интересен Partial Object, но тут рискуете забыть про то, что объект загружен не полностью и получить кучу сложно отлавливаемых багов
    Ответ написан
    3 комментария
  • Как создать 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 должен быть инициализирован (для каждого соединения)
    Ответ написан
    Комментировать
  • Как десерилизовать массив, который приходит из формы 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 комментария