Answer the question
In order to leave comments, you need to log in
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
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 questionAsk a Question
731 491 924 answers to any question