Search code examples
symfonyfixturesapi-platform.comalice

API Platform + alice UserDataPersister not working with fixtures


I have the following UserDataPersister (taken straight from the tutorial) configured:

Information for Service "App\DataPersister\UserDataPersister"
============================================================= 
  Service ID       App\DataPersister\UserDataPersister            
  Class            App\DataPersister\UserDataPersister            
  Tags             api_platform.data_persister (priority: -1000)  
  Public           no                                             
  Shared           yes                                            
  Abstract         no                                             
  Autowired        yes                                            
  Autoconfigured   yes                                            

and the following User fixture:

App\Entity\User:
    user_{1..10}:
        email: "usermail_<current()>\\@email.org"
        plainPassword: "plainPassword_<current()>"
        __calls:
          - initUuid: []

But I get errors when loading this fixture:

  An exception occurred while executing 'INSERT INTO "user" (id, uuid, roles, password, email) VALUES (?, ?, ?, ?, ?)' with params [281, "16ac40d3-53af-45dc-853f-e26f188d  
  1818", "[]", null, "[email protected]"]:                                                                                                                                
                                                                                                                                                                            
  SQLSTATE[23502]: Not null violation: 7 ERROR:  null value in column "password" of relation "user" violates not-null constraint                                            
  DETAIL:  Failing row contains (281, 16ac40d3-53af-45dc-853f-e26f188d1818, [], null, [email protected]).                                                                 

My implementation of UserDataPersister is identical with this.


Solution

  • Quote from Article at the end

    If we stopped now... yay! We haven't... really... done anything: we added this new plainPassword property... but nothing is using it! So, the request would ultimately explode in the database because our $password field will be null.

    Next, we need to hook into the request-handling process: we need to run some code after deserialization but before persisting. We'll do that with a data persister.

    Since unit test would POST the request, the data persistor is called by api-platform and it will pick up encoding logic by event. In case of fixtures, direct doctrine batch insert is done, this will bypass all persistence logic and would result in null password.

    There is a way to solve this as mentioned by @rishta Use Processor to implement hash to your data fixtures as referenced in Documentation

    <?php
    namespace App\DataFixtures\Processor;
    
    use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
    use Fidry\AliceDataFixtures\ProcessorInterface;
    use App\Entity\User;
    
    final class UserProcessor implements ProcessorInterface
    {
        private $userPasswordEncoder;
    
        public function __construct(EntityManagerInterface $entityManager, UserPasswordEncoderInterface $userPasswordEncoder) {
            $this->userPasswordEncoder = $userPasswordEncoder;
        }
    
        /**
         * @inheritdoc
         */
        public function preProcess(string $fixtureId, $object): void {
            if (false === $object instanceof User) {
                return;
            }
    
            $object = $this->userPasswordEncoder(
                    $object,
                    $object->getPlainPassword()
            );
        }
    
        /**
         * @inheritdoc
         */
        public function postProcess(string $fixtureId, $object): void
        {
            // do nothing
        }
    }
    

    Register service :

    # app/config/services.yml
    
    services:
        _defaults:
            autoconfigure: true
    
        App\DataFixtures\Processor\UserProcessor: ~
            #add tag in case autoconfigure is disabled, no need for auto config
            #tags: [ { name: fidry_alice_data_fixtures.processor } ]
    

    One of the better ways to do input masking in API Platform is to use DTO Pattern as oppose to suggested by article, in which you are allowed to :

    • Create separate input & output data objects
    • Transform Underlying date to and from the objects
    • Choose Different IO objects for each operation whenever needed

    More on DTO in documentation