Ответы пользователя по тегу Symfony
  • Как валидировать данные входящие в объект при десериализации в Symfony?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Пример кода из моего древного проекта. Суть была такой, что если в api-эндпоинт для \DateTime и array передать некорректные типы, то денормалайзер кидал эксепшн Symfony\Component\Serializer\Exception\ExceptionInterface (не удалось создать дату из строки "blabla").
    Чтобы этого избежать, я передаю в денормалайзер кастомную константу Context::REQUEST_DENORMALIZATION и отлавливаю этот эксепшн в RequestArrayDenormalizer и RequestDateTimeNormalizer.Там смотрю есть ли эта константа, если есть, то значит у меня денормалазайзер был вызван для реквеста. Дальше я возвращаю это значение как есть, чтобы валидатор его смог провалидировать.

    namespace App\Application\Serializer\Denormalizer;
    
    use App\Application\Serializer\Normalizer\Context;
    use App\Application\Util\Helper\Arr;
    use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
    use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
    
    class RequestDtoDenormalizer implements DtoDenormalizerInterface
    {
        private DenormalizerInterface $denormalizer;
    
        public function __construct(DenormalizerInterface $denormalizer)
        {
            $this->denormalizer = $denormalizer;
        }
    
        public function denormalize(array $input, string $className): object
        {
            $input = Arr::mapRecursive($input, fn ($value) => is_string($value) ? trim($value) : $value);
    
            return $this->denormalizer->denormalize($input, $className, null, [
                Context::REQUEST_DENORMALIZATION => true,
                AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true,
            ]);
        }
    }

    namespace App\Application\Serializer\Normalizer\Http;
    
    use App\Application\Serializer\Normalizer\Traits\AvoidExceptionForRequestTrait;
    use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
    
    class RequestArrayDenormalizer extends ArrayDenormalizer
    {
        use AvoidExceptionForRequestTrait;
    }

    namespace App\Application\Serializer\Normalizer\Http;
    
    use App\Application\Serializer\Normalizer\Traits\AvoidExceptionForRequestTrait;
    use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
    
    class RequestDateTimeNormalizer extends DateTimeNormalizer
    {
        use AvoidExceptionForRequestTrait;
    }

    namespace App\Application\Serializer\Normalizer\Traits;
    
    use App\Application\Serializer\Normalizer\Context;
    use Symfony\Component\Serializer\Exception\ExceptionInterface;
    
    trait AvoidExceptionForRequestTrait
    {
        /**
         * {@inheritDoc}
         */
        public function denormalize($data, $type, $format = null, array $context = [])
        {
            try {
                return parent::denormalize($data, $type, $format, $context);
            } catch (ExceptionInterface $e) {
                if (!isset($context[Context::REQUEST_DENORMALIZATION])) {
                    throw $e;
                }
    
                return $this->transform($data);
            }
        }
    
        /**
         * @param mixed $data
         * @return mixed
         */
        protected function transform($data)
        {
            return $data;
        }
    }

    namespace App\Application\Serializer\Normalizer;
    
    abstract class Context
    {
        public const REQUEST_DENORMALIZATION = 'request_denormalization';
    }
    Ответ написан
  • Что может дать изучение Symfony разработчику Laravel?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Symfony не загоняет в жесткие рамки разработчика и на нем точно также можно писать лапшу, как и на Laravel.

    Мне 3 недели назад достался довольно успешный европейский стартап, написанный на Symfony. Открыл код и ужаснулся:
    • Нет никакого SOLID, вся логика находится в контроллерах вместе с валидацией. И валидация делается не через компонент Validator, а ручками через empty(), isset() и тд.
    • Какие-то костыльные и кастомные события и тд.
    • Нет брокеров сообщений и из-за этого задержка в эндпоинтах доходит до 5-ти секунд.
    • Очень медленные тесты, 4709 assertions проходили за 54 минуты на локальной машине с i7 10-го поколения. За пару дней мне удалось сократить это время до 70 секунд. Теперь CI/CD очень быстрый и CTO вне себя от счастья.

    И на Laravel я видел крутые с точки зрения кода проекты, в которых было приятно участвовать. Там есть тот же DI, ивенты/события, можно интегрировать Doctrine если сильно хочется. Не хочешь фасады, юзай контракты. Вот даже пример есть Laravel-проекта с DDD.
    Ответ написан
  • Как автоматически запустить сервис?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    В Symfony аутентификация делается через Custom Authentication System with Guard или Custom User Provider

    Рендеры глобальных блоков типа менюшек и тд делается с помощью Embedding Controllers
    <div id="sidebar">
        {# if you don't want to expose the controller with a public URL,
           use the controller() function to define the controller to execute #}
        {{ render(controller(
            'App\\Controller\\BlogController::recentArticles', {max: 3}
        )) }}
    </div>

    P.S. Не юзайте глобальные переменные типа $_GET, $_POST и тд, не тащите свой багаж знаний из эпохи динозавров. Уже давно придумали Request объект. Есть документация по фреймворку и Symfonycasts, можно выделить выходные и пройти основной курс по Symfony.
    Ответ написан
  • Как сделать груповую проверку форму в Symfony 4.4.8?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Из доки по Symfony

    As with most of the other constraints, null and empty strings are considered valid values. This is to allow them to be optional values. If the value is mandatory, a common solution is to combine this constraint with NotBlank.

    Т.е валидация не сработает, когда прилетит null или пустая строка - это дефолтное поведение.

    https://symfony.com/doc/current/reference/constrai...
    Ответ написан
  • Где можно посмотреть толковые реализации API на Symfony?

    tommy-vercetti
    @tommy-vercetti Автор вопроса
    Symfony/Golang
    Опишу тут к чему мы в итоге пришли, вдруг кому-нибудь пригодится :)

    - Используем RDM (Rich Domain Model)
    - Ивенты бросаем внутри сущности. Для этого мы используем интерфейс RaiseEventsInterface и трейт RaiseEventsTrait.

    interface RaiseEventsInterface
    {
        public function releaseAndResetEvents(): array;
    }
    
    trait RaiseEventsTrait
    {
        protected array $events = [];
    
        public function releaseAndResetEvents(): array
        {
            $events = $this->events;
    
            $this->events = [];
    
            return $events;
        }
    
        protected function raise(AbstractDomainEvent $event): void
        {
            $event->initTime();
    
            $this->events[] = $event;
        }
    }

    В DoctrineDomainEventPublisher мы пробегаемся по всем новым и измененным сущностям и забираем ивенты. Далее сортируем эти ивенты по времени и диспатчим в Event Bus (компонент symfony/messenger) .

    - Не используем бандлы: FosRestBundle, FosUserBundle

    - Для валидации используем DTO через action argument resolver. В основном валидация через DTO делает простые проверки: на тип, дату, период времени и тд. Проверки на принадлежность к какой-то сущности проводятся в самой сущности. В случае невалидного состояния из сущности бросается эксепшн, например UncancellableMissionException, MissionOfferSlotCannotBeAcceptedException и тд.

    - В контроллере нет бизнес-логики. Сообщение диспатчится в Command или Query Bus.

    - Для репозиториев используем интерфейсы с общими методами: get, getOneByCriteria, add и тд. Например, метод add делает внутри $em->persist().

    - Стараемся избегать entity manager в коде типа: $em->persist(), $em->flush(). Благо компонент symfony/messenger предоставляет doctrine_transaction middleware. Конечно, не везде удается обходиться без entity manager, например, в ситуации с блокировками транзакций.

    - Для генерации доки используем API Blueprint - пакет https://github.com/bukalapak/snowboard

    - Для респонсов используем Fractal https://fractal.thephpleague.com/transformers/

    - Для тестов API - Behat
    Ответ написан
  • Как запустить чистый sql в консольной команде Symfony?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    namespace App\Command;
    
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;
    
    final class TestCommand extends Command
    {
        protected static $defaultName = 'app:test-command';
    
        /**
         * @var EntityManagerInterface
         */
        private $entityManager;
    
    
        public function __construct(EntityManagerInterface $entityManager)
        {
            $this->entityManager = $entityManager;
    
            parent::__construct();
        }
    
        /**
         * @param InputInterface $input
         * @param OutputInterface $output
         * @return int|void|null
         * @throws \Doctrine\DBAL\DBALException
         */
        protected function execute(InputInterface $input, OutputInterface $output)
        {
            $conn = $this->entityManager->getConnection();
    
            $sql = 'SELECT * FROM invoice WHERE status = :status';
    
            $stmt = $conn->prepare($sql);
            $stmt->execute(['status' => 'transaction_failed']);
            $invoices = $stmt->fetchAll();
    
            dd($invoices);
        }
    }

    Подробнее https://symfonycasts.com/screencast/doctrine-queri...
    Ответ написан
  • Symfony многоядерность?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    name параметр используется для генерации урлов.
    Например, у нас есть детальная страница товара @Route("/products/{id}", name="product_detail").
    На странице списка товаров для того, чтобы сгенерить ссылку на товар в twig-шаблоне генерим урл {{ path('product_detail', {'id': product.id}) }}. При смене урла код шаблонов менять не придется.

    Как быть с поддоменами в доке есть инфа https://symfony.com/doc/current/routing.html#sub-d...
    Ответ написан
  • На каком уровне в Symfony перехватывать ошибки?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Создаем слушатель событий и подписываемся на KernelEvents::EXCEPTION. Далее в методе onKernelException подготавливаем и сеттим респонс.

    namespace App\EventListener;
    
    use Symfony\Component\HttpKernel\KernelEvents;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    use Symfony\Component\HttpKernel\Event\ExceptionEvent;
    
    class ExceptionListener implements EventSubscriberInterface
    {
        public static function getSubscribedEvents(): array
        {
            return [
                KernelEvents::EXCEPTION => ['onKernelException'],
            ];
        }
    
        public function onKernelException(ExceptionEvent $event): void
        {
            // You get the exception object from the received event
            $exception = $event->getException();
            $message = sprintf(
                'My Error says: %s with code: %s',
                $exception->getMessage(),
                $exception->getCode()
            );
    
            // Customize your response object to display the exception details
            $response = new Response();
            $response->setContent($message);
    
            // HttpExceptionInterface is a special type of exception that
            // holds status code and header details
            if ($exception instanceof HttpExceptionInterface) {
                $response->setStatusCode($exception->getStatusCode());
                $response->headers->replace($exception->getHeaders());
            } else {
                $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
            }
    
            // sends the modified response object to the event
            $event->setResponse($response);
        }
    }

    Подробнее https://symfony.com/doc/current/event_dispatcher.html
    Ответ написан
  • Проблема с обновлением данных в Symfony и очередью сообщений, как решить?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Нужно почистить entity manager и разорвать соединение с базой в Consumer'е после сохранения данных:

    // Сохранили в базу
    $this->em->flush();
    
    $this->em->clear();
    $this->em->getConnection()->close();
    Ответ написан
  • Есть ли хорошие курсы по Symfony?

    tommy-vercetti
    @tommy-vercetti
    Symfony/Golang
    Есть один трекер (думаю все его знают), там вводишь Symfony в поиске и вылетит раздача в 27 гигов.

    P.S. Раньше SymfonyCasts назывался KnpUniversity. Правда я брал подписку у этих ребят, т.к туториалы у них очень крутые и денег они своих стоят.
    Ответ написан