• Воссоздание 3D модели человека по фото?

    BonBonSlick
    @BonBonSlick Автор вопроса
    depth info?
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik,
    1) App\Tests\Acceptance\Common\AuthenticationTest::authenticateUserToGetApiToken
    PHPUnit\Framework\Exception: array:2 [
      "email" => array:2 [
        0 => "The "Email" value should not be blank"
        1 => "The "Email" value should be at between 5 and 32, current 0 characters"
      ]
      "password" => array:2 [
        0 => "The "Password" value should not be blank"
        1 => "The "Password" value should be at between 4 and 32, current 0 characters"
      ]
    ]
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, с одной стороны потратил часов 4 на ето, с другой, приятно что есть люди которые лицом в говно тыкают это значит есть чему еще учиться)
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    <?xml version="1.0" ?>
    <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
        <file source-language="en" target-language="en" datatype="plaintext" original="file.ext">
            <body>
                <trans-unit id="not.found">
                    <source>not.found</source>
                    <target>Data was not found</target>
                </trans-unit>
                <trans-unit id="auth">
                    <source>auth</source>
                    <target>Wrong credentials</target>
                </trans-unit>
             
                <trans-unit id="not.blank">
                    <source>not.blank</source>
                    <target>The "{{ inputName }}" value should not be blank</target>
                </trans-unit>
                <trans-unit id="not.null">
                    <source>not.null</source>
                    <target>The "{{ inputName }}" value should  not be null</target>
                </trans-unit>
                <trans-unit id="string">
                    <source>string</source>
                    <target>The "{{ inputName }}" value should be string</target>
                </trans-unit>
            ...
                <trans-unit id="length">
                    <source>length</source>
                    <target>The "{{ inputName }}" value should be at between %min% and %max%, current %value% %attribute%</target>
                </trans-unit>
                <trans-unit id="max">
                    <source>max</source>
                    <target>The "{{ inputName }}" may not be greater than %max% %value%</target>
                </trans-unit>
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik,
    final class ValidationExceptionEventSubscriber implements EventSubscriberInterface {
        use ApiControllerTrait;
    
        private TranslatorInterface              $translator;
        private ExceptionEvent                   $event;
        private string                           $messagesFileName   = 'validation';
        private string                           $inputNamesFileName = 'input_names';
        private string                           $attributesFileName = 'validation_attributes';
    
        public function __construct(SerializerInterface $serializer, TranslatorInterface $translator) {
            $this->serializer = $serializer;
            $this->translator = $translator;
        }
    
        /**
         * {@inheritdoc}
         */
        public static function getSubscribedEvents(): array {
            return [
                KernelEvents::EXCEPTION => [
                    ['processValidationException', 11],
                ],
            ];
        }
    
        /**
         * @throws NullPointerException
         */
        public function processValidationException(ExceptionEvent $event): void {
            /** @var ValidationException $exception */
            $this->event               = $event;
            $exception                 = $event->getThrowable();
            $isValidationFormException = \get_class($exception) === ValidationException::class;
            if (false === $isValidationFormException || false === $event->isMasterRequest()) {
                return;
            }
            $event->setResponse(
                $this->createResponse(
                    $exception->getMessage(),
                    [
                        'violations' => $this->translateErrors($exception->violations()),
                    ],
                    $this->statusError,
                    null,
                    Response::HTTP_NOT_ACCEPTABLE
                )
            );
        }
    
        /**
         * @throws NullPointerException
         */
        private function translateErrors(ConstraintViolationListInterface $violationList): array {
            $errors = [];
            foreach ($violationList as $violation) {
                $errors[\trim($violation->getPropertyPath(), '[]')][] = $this->translateError($violation);
            }
            return $errors;
        }
    
        /**
         * @throws NullPointerException
         */
        private function translateError(ConstraintViolation $violation): string {
            $constraint = $violation->getConstraint();
            if (null === $constraint) {
                throw new NullPointerException(
                    sprintf(
                        'Violation constraint with template %s  for field %s is unknown',
                        $violation->getMessageTemplate(),
                        $violation->getPropertyPath()
                    )
                );
            }
            $className     = get_class($constraint);
            $messageId     = $violation->getMessageTemplate();
            $inputNameId   = $violation->getPropertyPath();
            $violatedValue = $violation->getInvalidValue();
            switch ($className) {
                case NotBlank::class:
                    $message = $this->simpleTrans($messageId, $inputNameId, $violatedValue);
                    break;
                case Length::class:
                    /** @var Length $constraint */
                    $message = $this->minMaxTrans($inputNameId, $constraint->min, $constraint->max, (int)$violatedValue);
                    break;
                default:
                    $message = $this->simpleTrans($messageId, $inputNameId, $violatedValue);
            }
            return $message;
        }
    
        /**
         * translates message with attribute
         *
         * @param string      $messageId          - messages eg "This field is required'
         * @param string      $inputNameId        - input field eg "password"
         * @param string|null $value              -  input value eg "42", "passwordDDD", "NarutoSearch", "Andrew'..
         * @param string|null $attributeId        - fields in messages eg "size", "resolution", "bytes"
         * @param string|null $messagesFileName   - file for messages
         * @param string|null $inputNamesFileName - file for input names
         * @param string|null $attributesFileName - file for attributes
         * @param string      $localeCode
         *
         * @throws TranslationInvalidArgumentException
         *
         * @return string
         *
         */
        private function simpleTrans(
            string $messageId,
            string $inputNameId,
            ?string $value = null,
            ?string $attributeId = null,
            ?string $messagesFileName = null,
            ?string $inputNamesFileName = null,
            ?string $attributesFileName = null,
            string $localeCode = null // TODO - should be returned from system settigns
        ): string {
            $localeCode ??= $this->event->getRequest()->getLocale();
            return $this->translator->trans(
                $messageId,
                [
                    '{{ inputName }}' => $this->translator->trans(
                        $inputNameId,
                        [],
                        $inputNamesFileName ?? $this->inputNamesFileName,
                        $localeCode
                    ),
                    '%attribute%'     => $this->translator->trans(
                        $attributeId,
                        [],
                        $attributesFileName ?? $this->attributesFileName,
                        $localeCode
                    ),
                    '{{ value }}'     => $value,
                ],
                $messagesFileName ?? $this->messagesFileName,
                $localeCode
            );
        }
    
        /**
         * @throws TranslationInvalidArgumentException
         */
        private function minMaxTrans(
            string $inputNameId,
            int $minLength,
            int $maxLength,
            int $currentLength,
            ?string $messagesFileName = null,
            ?string $inputNamesFileName = null,
            ?string $attributesFileName = null,
            string $localeCode = null // TODO - should be returned from system settigns
        ): string {
            $localeCode ??= $this->event->getRequest()->getLocale();
            // https://symfony.com/doc/current/translation/message_format.html#pluralization
            return $this->translator->trans(
                'length',
                [
                    '{{ inputName }}' => $this->translator->trans(
                        $inputNameId,
                        [],
                        $inputNamesFileName ?? $this->inputNamesFileName,
                        $localeCode
                    ),
                    '%min%'           => $minLength,
                    '%max%'           => $maxLength,
                    '%value%'         => $currentLength,
                    '%attribute%'     => $this->translator->trans(
                        'character',
                        ['%count%' => 2],
                        $attributesFileName ?? $this->attributesFileName,
                        $localeCode
                    ),
                ],
                $messagesFileName ?? $this->messagesFileName,
                $localeCode
            );
        }
    
        /**
         * return simple array of error messages
         *
         * @param ConstraintViolationListInterface $errorsCollection
         *
         * @return string[]
         */
        private function validationErrors(ConstraintViolationListInterface $errorsCollection): array {
            $errors = [];
            foreach ($errorsCollection as $error) {
                $errors[\trim($error->getPropertyPath(), '[]')] = $error->getMessage();
            }
    
            return $errors;
        }
    }

    final class ValidationException extends Exception {
        private ConstraintViolationListInterface $violations;
    
        public function __construct(
            string $message,
            ConstraintViolationListInterface $violations
        ) {
            parent::__construct($message);
            $this->violations = $violations;
        }
    
        public function violations(): ConstraintViolationListInterface {
            return $this->violations;
        }
    }
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, фуууух, переписал, опять))
    1 - перевод только если ошибка валидации
    2 - решена проблема с кешем т.к. теперь локаль в ексепшене из реквеста
    3 - метадата все просто для того что бы повесить констрейнты

    final class AuthRequest extends AbstractValidationRequest {
        private ?string               $email;
        private ?string               $password;
    
        public function __construct(Request $request) {
            parent::__construct($request);
            $this->email    = (string)$request->get('email');
            $this->password = (string)$request->get('password');
        }
    
        public static function loadValidatorMetadata(ClassMetadataInterface $classMetadata): void {
            $classMetadata->addPropertyConstraints(
                'email',
                [
                    new NotBlank(
                        [
                            'message' => 'not.blank',
                        ]
                    ),
                    new Length(
                        [
                            'min'        => 5,
                            'max'        => 32,
                        ]
                    ),
                ]
            )->addPropertyConstraints(
                'password',
                [
                    new NotBlank(
                        [
                            'message' => 'not.blank',
                        ]
                    ),
                    new Length(
                        [
                            'min'        => 4,
                            'max'        => 32,
                        ]
                    ),
                ]
            )
            ;
        }
    
        public function email(): string {
            return $this->email;
        }
    
        public function password(): string {
            return $this->password;
        }
    
    }

    abstract class AbstractValidationRequest implements ValidationRequestInterface {
        /**
         * Uploaded files ($_FILES).
         *
         * @var FileBag|UploadedFile[]
         */
        public FileBag $files;
        /**
         * Headers (taken from the $_SERVER).
         *
         * @var HeaderBag
         */
        public HeaderBag                                  $headers;
        private Request                                   $request;
    
        public function __construct(Request $request) {
            $this->request = $request;
        }
    
        abstract protected static function loadValidatorMetadata(ClassMetadataInterface $metadata): void;
    
    
        final  public function getLocale(): string {
            return $this->request->getLocale();
        }
    
        final public function get(string $fieldName) {
            return $this->request->get($fieldName);
        }
    }
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, я не вижу возможности перевода без оверхеда после того как вылетел ConstraintViolationListInterface даже имея все это и используя +- ваш подход
    $errors[$violation->getPropertyPath()] = $this->translator->trans($violation->getMessageTemplate(), array_merge($violation->getParameters(), [
        '%fieldNa
    вынуждает совршать кучу проверок т.к. одно дело когда всего лишь месседж перевести, а если еще аттрибут или несколько аттрибутов как с Length?
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, Тут довольно много бойлерплейта выйдет в каждом ДТО реквеста, может потом вынесу в абстрактный класс или трейты, посмотрим. Главное очень гибко и понятно, компактно и читабельно, можно поддерживать. Контроль локализации была основная причина всех этих трюков, а так бы использовал аннотации да и все, если проект простой или надо по фасту намутить, кинул аннотацию и все.
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, обновил ответ. Это мои личные, собственные наработки валидации API которые использую сейчас везде. Старый вариант тот который Ларавель стайл.
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, Да, ваш вариант тоже хорош. ($violation->getMessageTemplate()...
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, я знаю xD Удивлен что вы увидели, глаз алмаз, или топ знания что вы скомпилировали код в голове, ето были наработки, я уже давно поправил это, скинуть финальные образцы?
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, вывод ошибок говеный, там возвращаеться коллекция и форматировать надо по любому, в своем бандле вы делаете тоже что и я только немного иначе.
    - Новые правила валидации можно добавлять как отдельно, так и через @Assert/Callback

    Не понимаю зачем такие трюки с колбеками
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik,перевод не переводит поля. К примеру msg "not.blank" => "Field %fieldName% must not be blank", по дефолту аннотация не знает какой аттрибут лепить. То есть дефолтные переводы поддерживают такой формат и только "Field must not be blank" и не понятно какое поле.
    А если мы и могли бы лепить перевод аттрибутов и название полей то лепить переводы в аннотации не выйдет, там вроде принимает только строки вида "not.blank" и использует дефолтные файлы для переводов. А если передавать туда массив, то код поддерживать невозможно ибо отсутствует хайлайт и хинты.
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, кстати говоря, мой подход, поковыряв ваш бандл, очень схож с моим. Думаю рано или поздно все делают аналогичные наработки +- одинаковые. Я вроде видел уже похожее что то раньше в чатике по симфони в телеге
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, использую только пакет валидации, ассертов уже года 3-4 как. На некоторых проектах использовался компонент форм, но ето превратилось в полный писец. Потому что наследовать формы фалидации бред, еще больший бред использовать форму в нескольких запросах.
    В итоге получалась 1 форма с 10 детенышами, и эта 1 форма использовалась в 5-7 запросах. Ее размер вырос до 1 000+ строк кода и дофига if и switch кейсов что бы покрыть нужные клиенту фичи.
    Ваш ответ на другой вопрос хорош, но возникают вопросы такие как
    1 - перевод и локализация ошибок
    2 - валидация и обработка ошибок, которые вернул валидатор
    3 - распределение ответственности
    Аннотации решают пока валидации простые и не особо паритесь о локализации.

    У меня все под капотом происходит еще до того как запрос зайдет в метод контроллера.
    Валидация в запросе, если ошибка, обрабатываем ValidationException в слушателе и все, чистенько относительно. Не без греха, текущий подход заставляет штамповать кучу бойлерплейтный реквестов, но на практике когда 1 класс, ссылку на гист выложил в ответе, или одна форма которая хендлит все валидации приводит к неподдерживаемому коду. Лучше уже много классов и бойлерплейта с простым кодом чем один хрен пойми как работающий класс.
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, там в моем ответе обертка валидации API запросов которую придумал года 2 назад и с тех пор дорабатывал. Изначально там был ларавел стайл, но вышел сервис на 1400+ строк, нереально работать с ним. Поетому выбрал больше бойлерплейта, так код более простой и можно с ним работать. А тот сервис отвечал за валюдацию всех правил. Вот пример старого подхода
    final class CreateAnimeValidationRequest extends AbstractValidationRequest{
      /**
       * @inheritdoc
       */
      public function __invoke() : ValidateRequestInterface
    {
        return $this->handleConstraints(
          [
            'name' => 'required|filled|string|length:3,55|',
            'slug' => 'nullable|string|',
            'description' => 'nullable|string|',
          ]
        );
      }
    }
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, да, это то что надо. Оно под капотом вызывает статичный метод. Я как ковырял в поисках сервиса или как бы использовать метадату как-то упустил это, спасибо. Делайте ответом в дополнение к моему
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, о, вроде то что надо. Читал то что в вопросе
    https://symfony.com/doc/current/components/validat...
    Написано
  • ClassMetadataInterface service missing?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, зачем тогда интерфейс и почему его передают параметром в доках когда надо инициализировать на класс в котором метадата исполльзуется?
    Написано
  • Какой смысл в PasswordEncoderInterface когда есть и используется везде UserPasswordEncoderInterface?

    BonBonSlick
    @BonBonSlick Автор вопроса
    BoShurik, да, имел ввиду что метод надо все же обьявлять. Спасибо, можете вынести ваши ласт 3 комента в ответ.
    Написано