如何覆盖 sylius 商店 api 插件上的用户注册

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

我正在使用 Shop API plugin

设置 Sylius 1.8.6

需要做的是在用户注册中添加一些字段。我设法通过添加到 class namespace App\Entity\User\ShopUser

将它们添加到 Sylius 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;
    }
}

并通过创建一个 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'])
        ;
    }

}

和一个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();
    }
}

并将两者添加到我的 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 }

现在我要做的是确保在 /register 上调用 api 时添加它 我一直在关注 documentation 并创建了自定义 RequestHandlerCommand

#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.');
    }
}

我也将这些添加到我的 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%"

正如文档所说的关于覆盖处理程序

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.

我是这么想的:

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

会让请求通过我的 class 而不是默认的但是我不断收到错误:

{
    "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"
}

并且在使用 xdebug 或什至在这三个 class 中的任何一个中添加一些 dd() 时,代码永远不会通过它们。

我没有正确注册新服务吗?我找不到与此相关的任何信息。

感谢任何人给我指明正确的方向。

好的,我成功了。设置所有服务后,我仍然遇到错误。这是因为我的

use Sylius\ShopApiPlugin\Command\Customer\RegisterCustomer;

class UserRegistrationCommand extends RegisterCustomer

并且 ChannelBasedCommandProvider 会调用

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

而且我没有在 child class 中覆盖此功能:

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

所以它会 return parent class 而不是 class。