src/Service/ValidatorService/EntityHydratorValidator.php line 402

Open in your IDE?
  1. <?php
  2. namespace App\Service\ValidatorService;
  3. use App\Controller\Api\Helpers\CustomInputBag;
  4. use App\Controller\Api\Service\HttpStatusInterface;
  5. use App\Entity\Unit;
  6. use Doctrine\Common\Util\ClassUtils;
  7. use Doctrine\ORM\EntityManagerInterface;
  8. use Doctrine\ORM\Mapping\MappingException;
  9. use phpDocumentor\Reflection\Types\Mixed_;
  10. use phpDocumentor\Reflection\Types\This;
  11. use SebastianBergmann\CodeUnit\ClassUnit;
  12. use Symfony\Component\HttpFoundation\InputBag;
  13. use Symfony\Component\Validator\ConstraintViolationList;
  14. use Symfony\Component\Validator\ConstraintViolationListInterface;
  15. use Symfony\Component\Validator\Validator\ValidatorInterface;
  16. /**
  17.  * @template T of object
  18.  */
  19. class EntityHydratorValidator
  20. {
  21.     private ValidatorInterface $validator;
  22.     /** @var T|null */
  23.     private $entity;
  24.     private ?InputBag $params;
  25.     private array $requires = [];
  26.     private ?\ReflectionClass $reflectedClass;
  27.     private ConstraintViolationListInterface $errors;
  28.     private EntityManagerInterface $entityManager;
  29.     private ?\Exception $exception null;
  30.     public function getException(): ?\Exception
  31.     {
  32.         return $this->exception;
  33.     }
  34.     public function setException(\Exception $exception): void
  35.     {
  36.         $this->exception $exception;
  37.     }
  38.     public function getErrors(): ConstraintViolationListInterface { return $this->errors; }
  39.     /**
  40.      * @template T of object
  41.      * @param class-string<T> $expected
  42.      * @param T $entity
  43.      * @return T
  44.      */
  45.     public function assertHydratedEntity(string $expectedobject $entity): object
  46.     {
  47.         if (!$entity instanceof $expected) {
  48.             throw new \Exception(sprintf(
  49.                 'Entity should be instance of %s, %s given',
  50.                 $expected,
  51.                 get_class($entity)
  52.             ));
  53.         }
  54.         return $entity;
  55.     }
  56.     /**
  57.      * @throws MappingException
  58.      */
  59.     private function isNullable(string $entityClassstring $field): bool
  60.     {
  61.         $metadata $this->entityManager->getClassMetadata($entityClass);
  62.         // Scalar field
  63.         if ($metadata->hasField($field)) {
  64.             return $metadata->isNullable($field);
  65.         }
  66.         // Association
  67.         if ($metadata->hasAssociation($field)) {
  68.             $assoc $metadata->getAssociationMapping($field);
  69.             return $assoc['joinColumns'][0]['nullable'] ?? true;
  70.         }
  71.         throw new \InvalidArgumentException("No mapping found for field '$field'");
  72.     }
  73.     private function hasValue($value): bool
  74.     {
  75.         if ($value === null) {
  76.             return false;
  77.         }
  78.         if (is_string($value)) {
  79.             return trim($value) !== '';
  80.         }
  81.         if (is_array($value)) {
  82.             return count($value) > 0;
  83.         }
  84.         if ($value instanceof \Doctrine\Common\Collections\Collection) {
  85.             return !$value->isEmpty();
  86.         }
  87.         return true// int, float, bool, object
  88.     }
  89.     public function __construct(ValidatorInterface $validatorEntityManagerInterface $entityManager)
  90.     {
  91.         $this->validator $validator;
  92.         $this->entityManager $entityManager;
  93.     }
  94.     public function reset(): self {
  95.         $this->entity null;
  96.         $this->params null;
  97.         $this->reflectedClass null;
  98.         $this->exception null;
  99.         return $this;
  100.     }
  101.     /**
  102.      * @param T $entity
  103.      * @throws \ReflectionException
  104.      */
  105.     public function setEntity(object $entity): self {
  106. //        if($this->entity && get_class($this->entity) !== get_class($entity)) {
  107. //            throw new \RuntimeException(sprintf("Previous Entity doesn't match with curr Entity, please reset this hydrator before to use. expected %s, %s given", get_class($entity), get_class($this->entity)));
  108. //        }
  109.         $this->entity $entity;
  110.         /**
  111.          * Proxy Entity yerine Gercek Entity
  112.          * $this->reflectedClass = new \ReflectionClass($this->entity);
  113.          * Proxy entity gonderdim: örnek -> Proxies\__CG__\App\Entity\Invoices
  114.          * Reflection proxy class’ı yansıtıyor →
  115.          * Proxy class’ta private property’ler yok →
  116.          *
  117.          * ClassUtils::getClass() şunu gördüm: App\Entity\Invoices no __CG__
  118.          * Proxy değil → gerçek entity class → property’ler görünür.
  119.          * Bu Güclü olmasina ragmen benim icin kötü oldu
  120.          * $this->reflectedClass = new \ReflectionClass($this->entity);
  121.          * Asagidaki yontemim sorunumu cözdü!!!
  122.         */
  123.         //
  124.         $this->reflectedClass = new \ReflectionClass(
  125.             ClassUtils::getClass($this->entity)
  126.         );
  127.         return $this;
  128.     }
  129.     /** @return T|null */
  130.     public function getEntity() { return $this->entity; }
  131.     public function setParams(InputBag $params): self $this->params $params; return $this; }
  132.     public function setRequires(array $requires): self $this->requires $requires; return $this; }
  133.     private function getProperties (): array {
  134.         return array_map( fn($item ) => $item->getName(), $this->reflectedClass->getProperties() );
  135.     }
  136.     private function isPropertyExitsstring $propName ): bool {
  137.         return in_array($propName$this->getProperties());
  138.     }
  139.     /**
  140.      * @param array $fields
  141.      * @param int $uniqueEntityMessageTemplate
  142.      * @param array<string, string> $entityValueGetters
  143.      * @return string|null
  144.      * @throws \Exception
  145.      */
  146.     public function validate(array $fieldsint $uniqueEntityMessageTemplate 0, array $entityValueGetters = [] ): ?string {
  147.         if(!isset($this->entity)){
  148.             throw new \Exception('Validation Entity required');
  149.         }
  150.         $requiredErrors = [];
  151.         // Loop through each field
  152.         foreach ($fields as $field) {
  153.             $fieldKey $field;
  154.             // Gelen Field Degeri related olmus bir filed
  155.             if(gettype($field) === "array" ){
  156.                 $fieldKey key($field);
  157.             }
  158.             #dump($fieldKey);
  159.             if(!$this->isPropertyExits($fieldKey)){
  160.                 $message sprintf(
  161.                     'Entity %s property %s not exits in class',
  162.                     $this->reflectedClass->getName(),
  163.                     $field
  164.                 );
  165.                 throw new \Exception($message json_encode($this->reflectedClass->getProperties()));
  166.             }
  167.             // Null Control
  168.             $value $this->params->get($fieldKey);
  169.             if ( (!$this->isNullable(ClassUtils::getClass($this->entity), $fieldKey) || in_array($fieldKey$this->requires)) && !$this->hasValue($value)) {
  170.                 $requiredErrors[] = sprintf(
  171.                     // '%s::%s null olamaz',
  172.                     // ClassUtils::getClass($this->entity),
  173.                     '%s can not be null',
  174.                     $fieldKey
  175.                 );
  176.                 continue;
  177.             }
  178.             // Build the setter method name in camelCase
  179.             $setter 'set' str_replace(' '''ucwords(str_replace('_'' '$fieldKey)));
  180.             // Get the value from params
  181.             $value $this->params->get($fieldKey);
  182.             // Gelen Field Degeri related olmus bir filed
  183.             if(gettype($field) === "array" ){
  184.                 $value $field[$fieldKey];
  185.             }
  186.             // If the field is a date field, create a DateTimeImmutable instance
  187.             if (str_ends_with($fieldKey'_at') && $value) {
  188.                 $value = (new \DateTimeImmutable($value));// ->format('Y-m-d');
  189.             }
  190.             // Call the setter method
  191.             $this->entity->{$setter}($value);
  192.         }
  193.         if(count($requiredErrors)){
  194.             return implode(", "$requiredErrors);
  195.         }
  196.         // Validate the entity
  197.         $this->errors $this->validator->validate($this->entity);
  198.         #dump($this->errors);
  199.         if (count($this->errors)) {
  200.             // Get the UniqueEntity validation message
  201.             $parsedMessage $this->errors[0]->getMessage();
  202.             // Split the message if it contains optional parts separated by '|'
  203.             $parsedMessage explode('|'$parsedMessage);
  204.             $explodedMessage $parsedMessage[$uniqueEntityMessageTemplate]; // The sprintf template
  205.             // Get the entity that caused the validation error
  206.             $entity $this->errors[0]->getCause()[0];
  207.             // Retrieve the field values and format dates
  208.             $values array_map(function($field) use ($entity$entityValueGetters) {
  209.                 $fieldKey $field;
  210.                 // Gelen Field Degeri related olmus bir filed
  211.                 if(gettype($field) === "array" ){
  212.                     $fieldKey key($field);
  213.                 }
  214.                 // Tam olarak pramr icin de bir entity varsa ve o entity den getter yaparak value olumak istersen
  215.                 // Tam olarak bunu yapiyor!
  216.                 if(array_key_exists($fieldKey$entityValueGetters)){
  217.                     $getterProp $entityValueGetters[$fieldKey];
  218.                     $entityGetterMethod 'get' str_replace(' '''ucwords(str_replace('_'' '$getterProp)));
  219.                     #dd($this->params->get($fieldKey));
  220.                     return $this->params->get($fieldKey)->{$entityGetterMethod}();
  221.                 }
  222.                 $getter 'get' str_replace(' '''ucwords(str_replace('_'' '$fieldKey)));
  223.                 $value $entity->{$getter}();
  224.                 if ($value instanceof \DateTimeInterface) {
  225.                     return $value->format('Y-m-d');
  226.                 }
  227.                 if(gettype($value) === "object"){
  228.                     try{
  229.                         if (method_exists($value'getName')) {
  230.                             $value $value->getName();
  231.                         } else {
  232.                             $value $value->getId();
  233.                         }
  234.                     } catch (\Exception $exception ){
  235.                         return $value->getId();
  236.                     }
  237.                 }
  238.                 return $value;
  239.             }, $fields);
  240.             #dd($explodedMessage, $values, sprintf($explodedMessage, ...$values));
  241.             // Format and return the exception message
  242.             return sprintf($explodedMessage, ...$values);
  243.         }
  244.         return null;
  245.     }
  246.     public function populateEntityFields(array $fields) {
  247.         if(!isset($this->entity)){
  248.             throw new \Exception('Entity required for fields!');
  249.         }
  250.         if(is_null($this->params)){
  251.             throw new \Exception('Params required for fields!');
  252.         }
  253.         $excludedFields = ['id''isNew'];
  254.         foreach ($fields as $field) {
  255.             if(in_array($field$excludedFields)){
  256.                 continue;
  257.             }
  258.             if(!$this->isPropertyExits($field)){
  259.                 $message sprintf(
  260.                     'Entity %s property %s not exists in class',
  261.                     $this->reflectedClass->getName(),
  262.                     $field
  263.                 );
  264.                 throw new \Exception($message);
  265.             }
  266.             $setter 'set' str_replace(' '''ucwords(str_replace('_'' '$field)));
  267.             // Safe
  268.             if(!$this->reflectedClass->hasMethod($setter)){
  269.                 throw new \Exception($setter " Method of Entity not found");
  270.             }
  271.             #$property = $this->reflectedClass->getProperty($field);
  272.             #$type = $property->getType()->getName(); // Bool & int & String ...
  273.             $value $this->params->get($field);
  274.             if(gettype($value) === "object" || is_array($value)){
  275.                 $this->entity->{$setter}($value);
  276.             }
  277.             else if ($value !== null && trim($value) !== '') {
  278.                 if (str_ends_with($field'_at')) {
  279.                     $value = new \DateTimeImmutable($value);
  280.                 }
  281.                 $this->entity->{$setter}($value);
  282.             }
  283.             else {
  284.                 $this->entity->{$setter}(null);
  285.             }
  286.             // boş değerler için setter çağrılmaz, entity mevcut değeri korur
  287.         }
  288.         return $this->entity;
  289.     }
  290.     private function isJson(string $string): bool
  291.     {
  292.         try {
  293.             json_decode($stringfalse512JSON_THROW_ON_ERROR);
  294.             return true;
  295.         } catch (\JsonException $e) {
  296.             return false;
  297.         }
  298.     }
  299.     /**
  300.      * @var array|string|null $data
  301.     */
  302.     public function createInputBag($dataRaw): ?InputBag {
  303.         if(is_null($dataRaw)) return null;
  304.         if(gettype($dataRaw) === "string"){
  305.             if($this->isJson($dataRaw)){
  306.                 // $data = json_decode($dataRaw, true);
  307.                 $data json_decode($dataRaw);
  308.             } else {
  309.                 $data[] = $dataRaw;
  310.             }
  311.         } else {
  312.             $data $dataRaw;
  313.         }
  314.         $ib = new InputBag();
  315.         try{
  316.             foreach ($data as $key => $value) {
  317.                 $ib->set($key$value);
  318.             }
  319.         } catch (\Exception $exception){
  320.             dd($dataRaw$data$exception);
  321.         }
  322.         return $ib;
  323.     }
  324.     final public function flush(): self {
  325.         try{
  326.             $this->entityManager->persist($this->entity);
  327.             $this->entityManager->flush();
  328.             $this->entityManager->refresh($this->entity);
  329.         } catch (\Exception $exception){
  330.             $this->setException($exception);
  331.         }
  332.         return $this;
  333.     }
  334. }