B
B
BonBon Slick2020-11-24 20:15:15
symfony
BonBon Slick, 2020-11-24 20:15:15

ClassMetadataInterface service missing?

ClassMetadataInterface
ClassMetadata
Taken from DOKI , but there is no such service.
The files are there, but not registered in the container.

The question is, do I need to register myself?
Why then are the docks silent about this?

<service id="Symfony\Component\Validator\Mapping\ClassMetadataInterface"
                 alias="Symfony\Component\Validator\Mapping\ClassMetadata"/>


Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: 
You have requested a non-existent service "Symfony\Component\Validator\Mapping\ClassMetadata".


<service id="Symfony\Component\Validator\Mapping\ClassMetadata"/>

Symfony\Component\DependencyInjection\Exception\RuntimeException: Cannot autowire service 
"Symfony\Component\Validator\Mapping\ClassMetadata": argument "$class" of method "__construct()" is 
type-hinted "string", you should configure its value explicitly.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
B
BonBon Slick, 2020-11-24
@BonBonSlick

Xs how else to solve it, dynamically registering the service will not work, as well as passing it as a parameter.
Old approach Laravel API validation with Symfony
Loading Metadata class
Final code in comments.

<?php
declare(strict_types=1);

/*
 * Created by BonBonSlick
 */

namespace App\Domain\Core\Request;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use Symfony\Contracts\Translation\TranslatorInterface;


final class AuthRequest extends AbstractValidationRequest {
    private ?string               $email;
    private ?string               $password;

    public function __construct(Request $request, TranslatorInterface $translator) {
        parent::__construct($request, $translator);
        $this->email    = (string)$request->get('email');
        $this->password = (string)$request->get('password');
    }

    public static function loadValidatorMetadata(ClassMetadataInterface $classMetadata): void {
        $passMin  = 4;
        $passMax  = 32;
        $emailMin = 5;
        $emailMax = 32;
        $classMetadata->addPropertyConstraints(
            'email',
            [
                new NotBlank(
                    [
                        'message' => parent::simpleTrans('not.blank', 'email'),
                    ]
                ),
                new Length(
                    [
                        'min'        => $emailMin,
                        'max'        => $emailMax,
                        'minMessage' => parent::minMaxTrans('email', $emailMin, true),
                        'maxMessage' => parent::minMaxTrans('email', $emailMax),
                    ]
                ),
            ]
        )->addPropertyConstraints(
            'password',
            [
                new NotBlank(
                    [
                        'message' => parent::simpleTrans('not.blank', 'password'),
                    ]
                ),
                new Length(
                    [
                        'min'        => $passMin,
                        'max'        => $passMax,
                        'minMessage' => parent::minMaxTrans('password', $passMin, true),
                        'maxMessage' => parent::minMaxTrans('password', $passMax),
                    ]
                ),
            ]
        )
        ;
    }

    public function email(): string {
        return $this->email;
    }

    public function password(): string {
        return $this->password;
    }

}

<?php
declare(strict_types=1);

/*
 * Created by BonBonSlick
 */

namespace App\Domain\Core\Request;

use App\Infrastructure\ArgumentResolver\ValidationRequestInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Translation\Exception\InvalidArgumentException as TranslationInvalidArgumentException;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use Symfony\Contracts\Translation\TranslatorInterface;


abstract class AbstractValidationRequest implements ValidationRequestInterface {
    protected static TranslatorInterface             $translator;
    protected static string                          $messagesFileName   = 'validation';
    protected static string                          $inputNamesFileName = 'input_names';
    protected static string                          $attributesFileName = 'validation_attributes';
    protected static string                          $locale;
    protected Request                                $request;

    public function __construct(Request $request, TranslatorInterface $translator) {
        $this->request    = $request;
        self::$translator = $translator;
        // @todo - should be taken from DB or other settings?
        self::$locale = $request->getLocale() ?? $request->getDefaultLocale();
    }

    abstract protected static function loadValidatorMetadata(ClassMetadataInterface $metadata): void;

    /**
     * translates message with attribute
     *
     * @param string      $messageId          - messages eg "This field is required'
     * @param string      $inputNameId        - input field eg "password"
     * @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
     *
     */
    protected static function simpleTrans(
        string $messageId,
        string $inputNameId,
        ?string $attributeId = null,
        ?string $messagesFileName = null,
        ?string $inputNamesFileName = null,
        ?string $attributesFileName = null,
        string $localeCode = 'en' // TODO - should be returned from system settigns
    ): string {
        return self::$translator->trans(
            $messageId,
            [
                '{{ inputName }}' => self::$translator->trans(
                    $inputNameId,
                    [],
                    $inputNamesFileName ?? self::$inputNamesFileName,
                    $localeCode
                ),
                '%attribute%'     => self::$translator->trans(
                    $attributeId,
                    [],
                    $attributesFileName ?? self::$attributesFileName,
                    $localeCode
                ),
            ],
            $messagesFileName ?? self::$messagesFileName,
            $localeCode
        );
    }

    /**
     * @throws TranslationInvalidArgumentException
     */
    protected static function minMaxTrans(
        string $inputNameId,
        int $minMaxValue,
        ?bool $isMin = false,
        ?string $messagesFileName = null,
        ?string $inputNamesFileName = null,
        ?string $attributesFileName = null,
        string $localeCode = 'en' // TODO - should be returned from system settigns
    ): string {
        $id = $isMin ? 'min' : 'max';
        // https://symfony.com/doc/current/translation/message_format.html#pluralization
        return self::$translator->trans(
            $id,
            [
                '{{ inputName }}'      => self::$translator->trans(
                    $inputNameId,
                    [],
                    $inputNamesFileName ?? self::$inputNamesFileName,
                    $localeCode
                ),
                sprintf('%%%s%%', $id) => $minMaxValue,
                '%value%'              => self::$translator->trans(
                    'character',
                    ['%count%' => 2],
                    $attributesFileName ?? self::$attributesFileName,
                    $localeCode
                ),
            ],
            $messagesFileName ?? self::$messagesFileName,
            $localeCode
        );
    }
}

final class RequestDTOResolver implements ArgumentValueResolverInterface {
    private ValidatorInterface     $validator;
    private ClassMetadataInterface $classMetadata;
    private TranslatorInterface    $translator;

    public function __construct(
        ValidatorInterface $validator,
        TranslatorInterface $translator
    ) {
        $this->validator  = $validator;
        $this->translator = $translator;
    }

    /**
     * https://symfony.com/doc/current/reference/constraints.html
     *
     * @return \Generator|iterable
     */
    public function resolve(Request $request, ArgumentMetadata $argument) {
        // creating new instance of custom request DTO
        $class = $argument->getType();
        $dto   = new $class($request, $this->translator);
        // throw bad request exception in case of invalid request data
        $errors = $this->validator->validate($dto);
        if (0 < count($errors)) {
            throw new BadRequestHttpException((string)$errors);
        }
        yield $dto;
    }

    /**
     * @return bool|void
     * @throws \ReflectionException
     */
    public function supports(Request $request, ArgumentMetadata $argument) {
        $reflection = new ReflectionClass($argument->getType());
        if ($reflection->implementsInterface(RequestDTOInterface::class)) {
            return true;
        }

        return false;
    }
}

final class ValidationExceptionEventSubscriber implements EventSubscriberInterface {
    use ApiControllerTrait;

    public function __construct(SerializerInterface $serializer) {
        $this->serializer = $serializer;
    }

    public static function getSubscribedEvents(): array {
        return [
            KernelEvents::EXCEPTION => [
                ['processValidationException', 11],
            ],
        ];
    }

    public function processValidationException(ExceptionEvent $event): void {
        /** @var ValidationException $exception */
        $exception                 = $event->getThrowable();
        $isValidationFormException = \get_class($exception) === ValidationException::class;
        if (false === $isValidationFormException || false === $event->isMasterRequest()) {
            return;
        }
        $event->setResponse(
            $this->createResponse(
                $exception->getMessage(),
                [
                    'violations' => $exception->violations(),
                ],
                $this->statusError,
                null,
                Response::HTTP_NOT_ACCEPTABLE
            )
        );
    }
}

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question