<?php
namespace App\EventSubscriber;
use App\Criteria\Interface\NormalizableDtoValueInterface;
use Artyum\RequestDtoMapperBundle\Event\PreDtoValidationEvent;
use DateTime;
use LogicException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\PropertyInfo\Type;
use Throwable;
class PreDtoValidationEventSubscriber implements EventSubscriberInterface
{
public function onPreDtoValidationEvent(PreDtoValidationEvent $event): void
{
$dto = $event->getSubject();
if (!($dto instanceof NormalizableDtoValueInterface)) {
return;
}
$phpDocExtractor = new PhpDocExtractor();
$reflectionExtractor = new ReflectionExtractor();
$listExtractors = [$reflectionExtractor];
$typeExtractors = [$phpDocExtractor, $reflectionExtractor];
$propertyInfo = new PropertyInfoExtractor($listExtractors, $typeExtractors);
$propertyAccessor = PropertyAccess::createPropertyAccessor();
foreach ($propertyInfo->getProperties($dto::class) as $property) {
$types = $propertyInfo->getTypes($dto::class, $property);
if (!$types) {
continue;
}
if (count($types) > 1) {
throw new LogicException('This subscriber only support properties with one type-hint.');
}
/** @var Type $expectedType */
$expectedType = current($types);
if (!$propertyAccessor->isWritable($dto, $property)) {
throw new LogicException(sprintf('The property "%s" is not writable.', $property));
}
$value = $propertyAccessor->getValue($dto, $property);
if ($value === null || $value === '') {
$propertyAccessor->setValue($dto, $property, null);
continue;
}
switch ($expectedType->getBuiltinType()) {
case 'bool':
if (in_array($value, ['1', 'true'], true)) {
$propertyAccessor->setValue($dto, $property, true);
}
if (in_array($value, ['0', 'false'], true)) {
$propertyAccessor->setValue($dto, $property, false);
}
break;
case 'int':
if (is_numeric($value)) {
$propertyAccessor->setValue($dto, $property, (int) $value);
}
break;
case 'object':
if ($expectedType->getClassName() === DateTime::class) {
try {
$propertyAccessor->setValue($dto, $property, new DateTime($value));
} catch (Throwable) {
break;
}
}
break;
}
}
}
public static function getSubscribedEvents(): array
{
return [
PreDtoValidationEvent::class => 'onPreDtoValidationEvent',
];
}
}