api-platform.com

How inject service in __contstruct?


I want inject Symfony\Component\Security\Core\Security in __construct to get the current user. It need me for use @UniqueEnitity validator:

<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\Security;
//...


/**
 * @ApiResource()
 * @ORM\Entity(repositoryClass="App\Repository\SimpleEntityRepository")
 * @UniqueEntity(fields={"foo", "user"})
 */
class SimpleEntity
{

    //...

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User")
     * @ORM\JoinColumn(nullable=false)
     */
    private $user;

    public function __construct(Security $security)
    {
        $this->user = $security->getUser();
    }

    //...
}

But i get error: Cannot create an instance of App\Entity\SimpleEntity from serialized data because its constructor requires parameter "security" to be present.


Solution

  • Symfony can inject services into the constructors of other services, but not into those of Entities.

    Normally setting the current user on the entity would be done by decorating the built-in DataPersister of apip. However, persisting takes place allmost at the end of processing the request, while validation happens earlier. Therefore the validator would not see the user that would be set later by the persister. So you need to set the user earlier in the process, before validation takes place. This could be done by a decorating Denormalizer. This is a services so you can have the Security service injected:

    // api/src/Serializer/SimpleEntityDenormalizer
    
    namespace App\Serializer;
    
    use Symfony\Component\Security\Core\Security;
    use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
    use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
    use Symfony\Component\Serializer\SerializerAwareInterface;
    use Symfony\Component\Serializer\SerializerInterface;
    use App\Entity\SimpleEntity;
    
    final class SimpleEntityDenormalizer implements ContextAwareDenormalizerInterface, SerializerAwareInterface
    {
        private $decorated;
        private $security;
    
        public function __construct(DenormalizerInterface $decorated, Security $security)
        {
            $this->decorated = $decorated;
            $this->security = $security;
        }
    
        public function supportsDenormalization($data, string $type, string $format = null, array $context = [])
        {
            if ($type !== SimpleEntity::class) return false;
    
            return $this->decorated->supportsDenormalization($data, $type, $format, $context);
        }
    
        public function denormalize($data, string $type, string $format = null, array $context = [])
        {
            $object = $this->decorated->denormalize($data, $type, $format, $context);
    
            if (null === $object->getUser()) {
                $object->setUser($this->security->getUser());
            }
    
            return $object;
        }
    
        public function setSerializer(SerializerInterface $serializer)
        {
            if($this->decorated instanceof SerializerAwareInterface) {
                $this->decorated->setSerializer($serializer);
            }
        }
    }
    

    Add the following to api/config/services.yaml:

    'App\Serializer\SimpleEntityDenormalizer':
        arguments: 
        $decorated: '@api_platform.jsonld.normalizer.item' ]
    

    This does not replace the decorated service but adds a new one.