Sylius / ShopApiPlugin

Shop API for Sylius.

Home Page:https://sylius.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to overide the user registration on the sylius shop api plugin

DennisdeBest opened this issue · comments

Hello, I am setting up a Sylius 1.8.6 with the Shop API plugin

What needs to be done is adding some fields on the user registration. I have managed to add them to the Sylius ShopUser entity by adding to the class namespace App\Entity\User\ShopUser

namespace App\Entity\User;

use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\ShopUser as BaseShopUser;

/**
 * @ORM\Entity
 * @ORM\Table(name="sylius_shop_user")
 */
class ShopUser extends BaseShopUser
{
    /**
     * @var string
     * @ORM\Column(type="string", nullable=false)
     */
    private string $permit;

    public function getPermit(): string
    {
        return $this->permit;
    }

    public function setPermit(string $permit): void
    {
        $this->permit = $permit;
    }
}

And to the fixtures by creating a ShopUserFactory

<?php


namespace App\Fixtures;

use App\Entity\User\ShopUser;
use Sylius\Bundle\CoreBundle\Fixture\Factory\ShopUserExampleFactory;
use Sylius\Component\Core\Model\ShopUserInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ShopUserFactory extends ShopUserExampleFactory
{
    public function create(array $options = []): ShopUserInterface
    {
        /** @var ShopUser $user */
        $user = parent::create($options);

        if (isset($options['permit'])) {
            $user->setPermit($options['permit']);
        }

        return $user;
    }

    protected function configureOptions(OptionsResolver $resolver): void
    {
        parent::configureOptions($resolver);

        $resolver
            ->setDefault('permit', 'default_permit')
            ->setAllowedTypes('permit', ['string'])
        ;
    }

}

and a ShopUserFixture

<?php


namespace App\Fixtures;


use Sylius\Bundle\CoreBundle\Fixture\ShopUserFixture as ShopUserFixtureBase;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

final class ShopUserFixture extends ShopUserFixtureBase
{
    protected function configureResourceNode(ArrayNodeDefinition $resourceNode): void
    {
        parent::configureResourceNode($resourceNode);

        $resourceNode
            ->children()
            ->scalarNode('permit')->end();
    }
}

And adding the two to my services.yaml

  sylius.fixture.example_factory.shop_user:
    class: App\Fixtures\ShopUserFactory
    arguments:
      - "@sylius.factory.shop_user"
    public: true

  sylius.fixture.shop_user:
    class: App\Fixtures\ShopUserFixture
    arguments:
      - "@sylius.manager.shop_user"
      - "@sylius.fixture.example_factory.shop_user"
    tags:
      - { name: sylius_fixtures.fixture }

Now what I want to do is make sure this is added when the api is called on /register I have been following the documentation and created a custom Request, Handler and Command

#Request 
<?php


namespace App\Controller\ShopAPI\Requests;


use App\Controller\ShopAPI\Commands\UserRegistrationCommand;
use Sylius\ShopApiPlugin\Command\CommandInterface;
use Sylius\ShopApiPlugin\Command\Customer\RegisterCustomer;
use Sylius\ShopApiPlugin\Request\Customer\RegisterCustomerRequest;
use Symfony\Component\HttpFoundation\RequestStack;

final class UserRegistration extends RegisterCustomerRequest
{
    private $address;
    private $city;
    private $postcode;
    private $permit;

    public function __construct(RequestStack $requestStack, string $channelCode)
    {

        $request = $requestStack->getCurrentRequest();

        parent::__construct($request, $channelCode);

        $this->address = $request->request->get('address');
        $this->postcode = $request->request->get('postcode');
        $this->city = $request->request->get('city');
        $this->permit = $request->request->get('permit');
        var_dump($request->request->get('permit'));die;
    }

    public function getCommand(): CommandInterface
    {
        return new UserRegistrationCommand(
            $this->email,
            $this->plainPassword,
            $this->firstName,
            $this->lastName,
            $this->channelCode,
            $this->subscribedToNewsletter,
            $this->phoneNumber,
            $this->address,
            $this->postcode,
            $this->city,
            $this->permit
        );
    }
}

#Command
<?php


namespace App\Controller\ShopAPI\Commands;


use Sylius\ShopApiPlugin\Command\Customer\RegisterCustomer;

class UserRegistrationCommand extends RegisterCustomer
{
    protected string $address;
    protected string $city;
    protected string $postcode;
    protected string $permit;

    public function __construct(
        string $email,
        string $plainPassword,
        string $firstName,
        string $lastName,
        string $channelCode,
        ?bool $subscribedToNewsletter,
        ?string $phoneNumber,
        string $address,
        string $city,
        string $postcode,
        string $permit
    )
    {
        parent::__construct(
            $email,
            $plainPassword,
            $firstName,
            $lastName,
            $channelCode,
            $subscribedToNewsletter,
            $phoneNumber
        );
        $this->address = $address;
        $this->city = $city;
        $this->postcode = $postcode;
        $this->permit = $permit;
    }

    public function address(): string
    {
        return $this->address;
    }

    public function city(): string
    {
        return $this->city;
    }

    public function postcode(): string
    {
        return $this->postcode;
    }

    public function permit(): string
    {
        return $this->permit;
    }
}

#Handler
<?php

declare(strict_types=1);

namespace App\Controller\ShopAPI\Handlers;

use App\Controller\ShopAPI\Commands\UserRegistrationCommand;
use App\Entity\User\ShopUser;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\AddressInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\Core\Repository\AddressRepositoryInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Sylius\ShopApiPlugin\Event\CustomerRegistered;
use Sylius\ShopApiPlugin\Provider\CustomerProviderInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Webmozart\Assert\Assert;

final class UserRegistrationHandler
{
    /** @var UserRepositoryInterface */
    private $userRepository;

    /** @var ChannelRepositoryInterface */
    private $channelRepository;

    /** @var FactoryInterface */
    private $userFactory;

    /** @var EventDispatcherInterface */
    private $eventDispatcher;

    /** @var CustomerProviderInterface */
    private $customerProvider;

    /** @var FactoryInterface */
    private FactoryInterface $addressFactory;

    /** @var AddressRepositoryInterface */
    private AddressRepositoryInterface $addressRepository;

    public function __construct(
        UserRepositoryInterface $userRepository,
        AddressRepositoryInterface $addressRepository,
        ChannelRepositoryInterface $channelRepository,
        FactoryInterface $userFactory,
        FactoryInterface $addressFactory,
        EventDispatcherInterface $eventDispatcher,
        CustomerProviderInterface $customerProvider
    )
    {
        $this->userRepository = $userRepository;
        $this->channelRepository = $channelRepository;
        $this->userFactory = $userFactory;
        $this->eventDispatcher = $eventDispatcher;
        $this->customerProvider = $customerProvider;
        $this->addressFactory = $addressFactory;
        $this->addressRepository = $addressRepository;
    }

    public function __invoke(UserRegistrationCommand $command): void
    {
        $this->assertEmailIsNotTaken($command->email());
        $this->assertChannelExists($command->channelCode());

        $customer = $this->customerProvider->provide($command->email());

        $customer->setFirstName($command->firstName());
        $customer->setLastName($command->lastName());
        $customer->setEmail($command->email());
        $customer->setSubscribedToNewsletter($command->subscribedToNewsletter());
        $customer->setPhoneNumber($command->phoneNumber());

        /** @var ShopUser $user */
        $user = $this->userFactory->createNew();
        $user->setPlainPassword($command->plainPassword());
        $user->setUsername($command->email());
        $user->setPermit($command->permit());
        $user->setCustomer($customer);

        $this->userRepository->add($user);

        /** @var AddressInterface $address */
        $address = $this->addressFactory->createNew();
        $address->setPostcode($command->postcode());
        $address->setPhoneNumber($command->phoneNumber());
        $address->setCity($command->city());
        $address->setCustomer($user);

        $this->addressRepository->add($address);

        $customer->setDefaultAddress($address);


        $this->eventDispatcher->dispatch('sylius.customer.post_api_registered', new CustomerRegistered(
            $command->email(),
            $command->firstName(),
            $command->lastName(),
            $command->channelCode(),
            $command->subscribedToNewsletter(),
            $command->phoneNumber()
        ));
    }

    private function assertEmailIsNotTaken(string $email): void
    {
        Assert::null($this->userRepository->findOneByEmail($email), 'User with given email already exists.');
    }

    private function assertChannelExists(string $channelCode): void
    {
        Assert::notNull($this->channelRepository->findOneByCode($channelCode), 'Channel does not exist.');
    }
}

And I have also added these to my services.yaml

  App\Controller\ShopAPI\Commands\UserRegistrationCommand:
    arguments:
      $email: "%email%"

  App\Controller\ShopAPI\Handlers\UserRegistrationHandler:
    public: true
    arguments:
      $userRepository: '@sylius.repository.shop_user'
      $userFactory: '@sylius.factory.shop_user'
      $customerProvider: '@sylius.shop_api_plugin.provider.customer_provider'
    
  Sylius\ShopApiPlugin\Command\Customer\RegisterCustomer:
    class: App\Controller\ShopAPI\Handlers\UserRegistrationHandler

  App\Controller\ShopAPI\Requests\UserRegistration:
    arguments:
      $channelCode: "%channelCode%"

As the documentation says about overriding handlers

The main way to extend a handler is to decorate it. This makes adding functionality before and after the handler easy. However, if you want to change the logic in the handler, you need to overwrite it. This can be done by registering the new handler with the same service id.

I thought this bit :

Sylius\ShopApiPlugin\Command\Customer\RegisterCustomer:
    class: App\Controller\ShopAPI\Handlers\UserRegistrationHandler

would make the request go through my class instead of the default however I keep getting the error :

{
    "code": 500,
    "message": "An exception occurred while executing 'INSERT INTO sylius_shop_user (username, username_canonical, enabled, salt, password, encoder_name, last_login, password_reset_token, password_requested_at, email_verification_token, verified_at, locked, expires_at, credentials_expire_at, roles, email, email_canonical, created_at, updated_at, permit, customer_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' with params [\"test3@example.com\", \"test3@example.com\", 0, \"ketyu603mrk0ksg0s0ssc0wkcw44k8g\", \"$argon2i$v=19$m=65536,t=4,p=1$Z09IeWlJR05nSW40cVYuYg$JIUtgpsZRVnKJoJJZvfN+kX5XRF+U69t8SQzRdZTVOs\", \"argon2i\", null, null, null, null, null, 0, null, null, \"a:1:{i:0;s:9:\\\"ROLE_USER\\\";}\", null, null, \"2020-12-22 08:41:08\", \"2020-12-22 08:41:08\", null, 102]:\n\nSQLSTATE[23000]: Integrity constraint violation: 1048 Column 'permit' cannot be null"
}

And while using xdebug or even adding some dd()'s inside any of those three classes the code never goes through them.

Am I not registering the new service correctly ? I can't find anything regarding this.

Thanks for any one pointing me in the right direction.

EDIT

I was overiding the wronk class, but when I override namespace Sylius\ShopApiPlugin\Handler\Customer\RegisterCustomerHandler I get the following error about messenger :

{
    "code": 500,
    "message": "No handler for message \"Sylius\\ShopApiPlugin\\Command\\Customer\\RegisterCustomer\"."
}         

And I still have no clue on how to fix this ...

When decorating a service you need to copy over the tags of the service you are decorating. Those are not getting copied. So while the Sylius Handler was tagged as a messenger.message_handler the new service needs to be tagged as well. That looks like the only thing you are missing.

Thanks a lot for your reply, I will try it out and let you know !

Ok I got it working. After setting up all the services I still had the error. This was because of my

use Sylius\ShopApiPlugin\Command\Customer\RegisterCustomer;

class UserRegistrationCommand extends RegisterCustomer

and the ChannelBasedCommandProvider would call

 $this->requestClass::fromHttpRequestAndChannel($httpRequest, $channel);

And I did not override this function in my child class :

    public static function fromHttpRequestAndChannel(Request $request, ChannelInterface $channel): ChannelBasedRequestInterface
   {
       return new self($request, $channel->getCode());
   }

So it would return the parent class instead and there is no more handler for that class.

Thanks !