mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-15 01:20:59 +00:00
[API] Registering shop users
This commit is contained in:
parent
0e30e3b002
commit
e5f7fc3e28
23 changed files with 855 additions and 11 deletions
7
.env
7
.env
|
|
@ -38,3 +38,10 @@ JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
|
|||
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
||||
JWT_PASSPHRASE=66d45daf91b2ed1031e62d81c81dba2e
|
||||
###< lexik/jwt-authentication-bundle ###
|
||||
|
||||
###> symfony/messenger ###
|
||||
# Choose one of the transports below
|
||||
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
|
||||
# MESSENGER_TRANSPORT_DSN=doctrine://default
|
||||
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
|
||||
###< symfony/messenger ###
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@
|
|||
"symfony/http-foundation": "^4.4",
|
||||
"symfony/http-kernel": "^4.4",
|
||||
"symfony/intl": "^4.4",
|
||||
"symfony/messenger": "^4.4",
|
||||
"symfony/monolog-bundle": "^3.0",
|
||||
"symfony/options-resolver": "^4.4",
|
||||
"symfony/polyfill-iconv": "^1.3",
|
||||
|
|
|
|||
20
config/packages/messenger.yaml
Normal file
20
config/packages/messenger.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
framework:
|
||||
messenger:
|
||||
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
|
||||
# failure_transport: failed
|
||||
|
||||
transports:
|
||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||
# async: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
# failed: 'doctrine://default?queue_name=failed'
|
||||
# sync: 'sync://'
|
||||
|
||||
routing:
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
||||
buses:
|
||||
messenger.bus.default:
|
||||
middleware:
|
||||
- 'validation'
|
||||
- 'doctrine_transaction'
|
||||
|
|
@ -7,7 +7,7 @@ Feature: Account registration
|
|||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Registering a new account with minimum information
|
||||
When I want to register a new account
|
||||
And I specify the first name as "Saul"
|
||||
|
|
@ -19,7 +19,7 @@ Feature: Account registration
|
|||
Then I should be notified that new account has been successfully created
|
||||
But I should not be logged in
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Registering a new account with minimum information when channel has disabled registration verification
|
||||
Given on this channel account verification is not required
|
||||
When I want to register a new account
|
||||
|
|
@ -32,7 +32,7 @@ Feature: Account registration
|
|||
Then I should be notified that new account has been successfully created
|
||||
And I should be logged in
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Registering a new account with all details
|
||||
When I want to register a new account
|
||||
And I specify the first name as "Saul"
|
||||
|
|
@ -45,7 +45,7 @@ Feature: Account registration
|
|||
Then I should be notified that new account has been successfully created
|
||||
But I should not be logged in
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Registering a guest account
|
||||
Given there is a customer "goodman@gmail.com" that placed an order "#001"
|
||||
When I want to register a new account
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Feature: Account registration
|
|||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to register a new account with email that has been already used
|
||||
Given there is a user "goodman@gmail.com" identified by "heisenberg"
|
||||
When I want to register a new account
|
||||
|
|
@ -16,7 +16,7 @@ Feature: Account registration
|
|||
Then I should be notified that the email is already used
|
||||
And I should not be logged in
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to register a new account without specifying first name
|
||||
When I want to register a new account
|
||||
And I do not specify the first name
|
||||
|
|
@ -27,7 +27,7 @@ Feature: Account registration
|
|||
Then I should be notified that the first name is required
|
||||
And I should not be logged in
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to register a new account without specifying last name
|
||||
When I want to register a new account
|
||||
And I do not specify the last name
|
||||
|
|
@ -39,7 +39,7 @@ Feature: Account registration
|
|||
Then I should be notified that the last name is required
|
||||
And I should not be logged in
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to register a new account without specifying password
|
||||
When I want to register a new account
|
||||
And I do not specify the password
|
||||
|
|
@ -62,7 +62,7 @@ Feature: Account registration
|
|||
Then I should be notified that the password do not match
|
||||
And I should not be logged in
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Trying to register a new account without specifying email
|
||||
When I want to register a new account
|
||||
And I do not specify the email
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Feature: Registering an account again after it has been deleted
|
|||
And there was account of "ted@example.com" with password "pswd"
|
||||
But its account was deleted
|
||||
|
||||
@ui
|
||||
@ui @api
|
||||
Scenario: Registering again after my account deletion
|
||||
When I want to again register a new account
|
||||
And I specify the first name as "Ted"
|
||||
|
|
|
|||
185
src/Sylius/Behat/Context/Api/Shop/RegistrationContext.php
Normal file
185
src/Sylius/Behat/Context/Api/Shop/RegistrationContext.php
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Behat\Context\Api\Shop;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class RegistrationContext implements Context
|
||||
{
|
||||
/** @var AbstractBrowser */
|
||||
private $client;
|
||||
|
||||
private $content = [];
|
||||
|
||||
public function __construct(AbstractBrowser $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I want to register a new account
|
||||
* @When I want to again register a new account
|
||||
*/
|
||||
public function iWantToRegisterNewAccount(): void
|
||||
{
|
||||
// Intentionally left empty
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify the first name as :firstName
|
||||
* @When I do not specify the first name
|
||||
*/
|
||||
public function iSpecifyTheFirstNameAs(string $firstName = ''): void
|
||||
{
|
||||
$this->content['firstName'] = $firstName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify the last name as :lastName
|
||||
* @When I do not specify the last name
|
||||
*/
|
||||
public function iSpecifyTheLastNameAs(string $lastName = ''): void
|
||||
{
|
||||
$this->content['lastName'] = $lastName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify the email as :email
|
||||
* @When I do not specify the email
|
||||
*/
|
||||
public function iSpecifyTheEmailAs(string $email = ''): void
|
||||
{
|
||||
$this->content['email'] = $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify the password as :password
|
||||
* @When I do not specify the password
|
||||
*/
|
||||
public function iSpecifyThePasswordAs(string $password = ''): void
|
||||
{
|
||||
$this->content['password'] = $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify the phone number as :phoneNumber
|
||||
*/
|
||||
public function iSpecifyThePhoneNumberAs(string $phoneNumber): void
|
||||
{
|
||||
$this->content['phoneNumber'] = $phoneNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I confirm this password
|
||||
*/
|
||||
public function iConfirmThisPassword(): void
|
||||
{
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I register this account
|
||||
* @When I try to register this account
|
||||
*/
|
||||
public function iRegisterThisAccount(): void
|
||||
{
|
||||
$this->client->request(
|
||||
'POST',
|
||||
'/new-api/shop/register',
|
||||
[],
|
||||
[],
|
||||
['HTTP_ACCEPT' => 'application/ld+json', 'CONTENT_TYPE' => 'application/ld+json'],
|
||||
json_encode($this->content, \JSON_THROW_ON_ERROR)
|
||||
);
|
||||
$this->content = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that new account has been successfully created
|
||||
*/
|
||||
public function iShouldBeNotifiedThatNewAccountHasBeenSuccessfullyCreated(): void
|
||||
{
|
||||
Assert::same($this->client->getResponse()->getStatusCode(), 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the first name is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheFirstNameIsRequired(): void
|
||||
{
|
||||
$this->assertFieldValidationMessage('firstName', 'Please enter your first name.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the last name is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheLastNameIsRequired(): void
|
||||
{
|
||||
$this->assertFieldValidationMessage('lastName', 'Please enter your last name.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the password is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatThePasswordIsRequired(): void
|
||||
{
|
||||
$this->assertFieldValidationMessage('password', 'Please enter your password.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the email is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheEmailIsRequired(): void
|
||||
{
|
||||
$this->assertFieldValidationMessage('email', 'Please enter your email.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that the email is already used
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheEmailIsAlreadyUsed(): void
|
||||
{
|
||||
$this->assertFieldValidationMessage('email', 'This email is already used.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should not be logged in
|
||||
*/
|
||||
public function iShouldNotBeLoggedIn(): void
|
||||
{
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be logged in
|
||||
*/
|
||||
public function iShouldBeLoggedIn(): void
|
||||
{
|
||||
// Intentionally left blank
|
||||
}
|
||||
|
||||
private function assertFieldValidationMessage(string $path, string $message): void
|
||||
{
|
||||
$decodedResponse = json_decode($this->client->getResponse()->getContent(), true, 512, \JSON_THROW_ON_ERROR);
|
||||
|
||||
Assert::keyExists($decodedResponse, 'violations');
|
||||
|
||||
Assert::oneOf(
|
||||
['propertyPath' => $path, 'message' => $message],
|
||||
$decodedResponse['violations']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,5 +28,9 @@
|
|||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.api.shop.registration" class="Sylius\Behat\Context\Api\Shop\RegistrationContext">
|
||||
<argument type="service" id="test.client" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# (c) Paweł Jędrzejewski
|
||||
|
||||
imports:
|
||||
- suites/api/account/customer_registration.yml
|
||||
- suites/api/account/login.yml
|
||||
- suites/api/admin/login.yml
|
||||
- suites/api/currency/managing_currencies.yml
|
||||
|
|
@ -36,9 +37,9 @@ imports:
|
|||
|
||||
- suites/ui/account/address_book.yml
|
||||
- suites/ui/account/customer.yml
|
||||
- suites/ui/account/customer_registration.yml
|
||||
- suites/ui/account/email_verification.yml
|
||||
- suites/ui/account/login.yml
|
||||
- suites/ui/account/registration.yml
|
||||
- suites/ui/addressing/managing_countries.yml
|
||||
- suites/ui/addressing/managing_zones.yml
|
||||
- suites/ui/admin/dashboard.yml
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Paweł Jędrzejewski
|
||||
|
||||
default:
|
||||
suites:
|
||||
api_customer_registration:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.transform.customer
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.customer
|
||||
- sylius.behat.context.setup.locale
|
||||
- sylius.behat.context.setup.order
|
||||
- sylius.behat.context.setup.shop_security
|
||||
- sylius.behat.context.setup.user
|
||||
|
||||
- sylius.behat.context.api.shop.registration
|
||||
|
||||
filters:
|
||||
tags: "@customer_registration && @api"
|
||||
49
src/Sylius/Bundle/ApiBundle/Command/RegisterShopUser.php
Normal file
49
src/Sylius/Bundle/ApiBundle/Command/RegisterShopUser.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Command;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
class RegisterShopUser
|
||||
{
|
||||
/** @var string */
|
||||
public $firstName;
|
||||
|
||||
/** @var string */
|
||||
public $lastName;
|
||||
|
||||
/** @var string */
|
||||
public $email;
|
||||
|
||||
/** @var string */
|
||||
public $password;
|
||||
|
||||
/** @var string|null */
|
||||
public $phoneNumber;
|
||||
|
||||
public function __construct(
|
||||
string $firstName,
|
||||
string $lastName,
|
||||
string $email,
|
||||
string $password,
|
||||
?string $phoneNumber = null
|
||||
) {
|
||||
$this->firstName = $firstName;
|
||||
$this->lastName = $lastName;
|
||||
$this->email = $email;
|
||||
$this->password = $password;
|
||||
$this->phoneNumber = $phoneNumber;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\CommandHandler;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Sylius\Bundle\ApiBundle\Command\RegisterShopUser;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Core\Repository\CustomerRepositoryInterface;
|
||||
use Sylius\Component\Resource\Factory\FactoryInterface;
|
||||
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
class RegisterShopUserHandler implements MessageHandlerInterface
|
||||
{
|
||||
/** @var CanonicalizerInterface */
|
||||
protected $canonicalizer;
|
||||
|
||||
/** @var FactoryInterface */
|
||||
protected $shopUserFactory;
|
||||
|
||||
/** @var FactoryInterface */
|
||||
protected $customerFactory;
|
||||
|
||||
/** @var CustomerRepositoryInterface */
|
||||
protected $customerRepository;
|
||||
|
||||
/** @var ObjectManager */
|
||||
protected $shopUserManager;
|
||||
|
||||
public function __construct(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
FactoryInterface $shopUserFactory,
|
||||
FactoryInterface $customerFactory,
|
||||
CustomerRepositoryInterface $customerRepository,
|
||||
ObjectManager $shopUserManager
|
||||
) {
|
||||
$this->canonicalizer = $canonicalizer;
|
||||
$this->shopUserFactory = $shopUserFactory;
|
||||
$this->customerFactory = $customerFactory;
|
||||
$this->customerRepository = $customerRepository;
|
||||
$this->shopUserManager = $shopUserManager;
|
||||
}
|
||||
|
||||
public function __invoke(RegisterShopUser $command): void
|
||||
{
|
||||
/** @var ShopUserInterface $user */
|
||||
$user = $this->shopUserFactory->createNew();
|
||||
$user->setPlainPassword($command->password);
|
||||
|
||||
$customer = $this->provideCustomer($command->email);
|
||||
$customer->setFirstName($command->firstName);
|
||||
$customer->setLastName($command->lastName);
|
||||
$customer->setEmail($command->email);
|
||||
$customer->setPhoneNumber($command->phoneNumber);
|
||||
$customer->setUser($user);
|
||||
|
||||
$this->shopUserManager->persist($user);
|
||||
}
|
||||
|
||||
protected function provideCustomer(string $email): CustomerInterface
|
||||
{
|
||||
$emailCanonical = $this->canonicalizer->canonicalize($email);
|
||||
|
||||
/** @var CustomerInterface|null $customer */
|
||||
$customer = $this->customerRepository->findOneBy(['emailCanonical' => $emailCanonical]);
|
||||
|
||||
if ($customer === null) {
|
||||
/** @var CustomerInterface $customer */
|
||||
$customer = $this->customerFactory->createNew();
|
||||
}
|
||||
|
||||
if ($customer->getUser() !== null) {
|
||||
throw new \DomainException(sprintf('User with email "%s" is already registered.', $emailCanonical));
|
||||
}
|
||||
|
||||
return $customer;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<resources xmlns="https://api-platform.com/schema/metadata"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.5.xsd"
|
||||
>
|
||||
<resource class="Sylius\Bundle\ApiBundle\Command\RegisterShopUser" shortName="RegisterShopUser">
|
||||
<attribute name="messenger">true</attribute>
|
||||
<attribute name="output">false</attribute>
|
||||
|
||||
<attribute name="route_prefix">shop</attribute>
|
||||
|
||||
<attribute name="validation_groups">sylius</attribute>
|
||||
|
||||
<collectionOperations>
|
||||
<collectionOperation name="post">
|
||||
<attribute name="path">/register</attribute>
|
||||
<attribute name="openapi_context">
|
||||
<attribute name="summary">Register a shop user</attribute>
|
||||
</attribute>
|
||||
<attribute name="validation_groups">
|
||||
<attribute>sylius</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
</collectionOperations>
|
||||
|
||||
<itemOperations />
|
||||
|
||||
<property name="firstName" required="true" />
|
||||
<property name="lastName" required="true" />
|
||||
<property name="email" required="true" />
|
||||
<property name="password" required="true" />
|
||||
</resource>
|
||||
</resources>
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
>
|
||||
<imports>
|
||||
<import resource="services/applicators.xml" />
|
||||
<import resource="services/command_handlers.xml" />
|
||||
<import resource="services/context_builders.xml" />
|
||||
<import resource="services/data_persisters.xml" />
|
||||
<import resource="services/data_providers.xml" />
|
||||
|
|
@ -58,5 +59,11 @@
|
|||
>
|
||||
<argument type="service" id="sylius.api.swagger_shop_authentication_documentation_normalizer.inner" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.validator.unique_shop_user_email" class="Sylius\Bundle\ApiBundle\Validator\Constraints\UniqueShopUserEmailValidator">
|
||||
<argument type="service" id="sylius.canonicalizer" />
|
||||
<argument type="service" id="sylius.repository.shop_user" />
|
||||
<tag name="validator.constraint_validator" alias="sylius.validator.unique_shop_user_email" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"
|
||||
>
|
||||
<services>
|
||||
<service id="Sylius\Bundle\ApiBundle\CommandHandler\RegisterShopUserHandler">
|
||||
<argument type="service" id="sylius.canonicalizer" />
|
||||
<argument type="service" id="sylius.factory.shop_user" />
|
||||
<argument type="service" id="sylius.factory.customer" />
|
||||
<argument type="service" id="sylius.repository.customer" />
|
||||
<argument type="service" id="sylius.manager.shop_user" />
|
||||
<tag name="messenger.message_handler" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
|
||||
<class name="Sylius\Bundle\ApiBundle\Command\RegisterShopUser">
|
||||
<property name="firstName">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.customer.first_name.not_blank</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="lastName">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.customer.last_name.not_blank</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="password">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.user.plainPassword.not_blank</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Length">
|
||||
<option name="min">4</option>
|
||||
<option name="minMessage">sylius.user.password.min</option>
|
||||
<option name="max">254</option>
|
||||
<option name="maxMessage">sylius.user.password.max</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="email">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.customer.email.not_blank</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Length">
|
||||
<option name="max">254</option>
|
||||
<option name="maxMessage">sylius.customer.email.max</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Email">
|
||||
<option name="message">sylius.customer.email.invalid</option>
|
||||
<option name="strict">true</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\UniqueShopUserEmail">
|
||||
<option name="message">sylius.user.email.unique</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
final class UniqueShopUserEmail extends Constraint
|
||||
{
|
||||
/** @var string */
|
||||
public $message = 'sylius.user.email.unique';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius.validator.unique_shop_user_email';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class UniqueShopUserEmailValidator extends ConstraintValidator
|
||||
{
|
||||
/** @var CanonicalizerInterface */
|
||||
private $canonicalizer;
|
||||
|
||||
/** @var UserRepositoryInterface */
|
||||
private $shopUserRepository;
|
||||
|
||||
public function __construct(CanonicalizerInterface $canonicalizer, UserRepositoryInterface $shopUserRepository)
|
||||
{
|
||||
$this->canonicalizer = $canonicalizer;
|
||||
$this->shopUserRepository = $shopUserRepository;
|
||||
}
|
||||
|
||||
public function validate($value, Constraint $constraint): void
|
||||
{
|
||||
if ($value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var UniqueShopUserEmail $constraint */
|
||||
Assert::isInstanceOf($constraint, UniqueShopUserEmail::class);
|
||||
|
||||
$emailCanonical = $this->canonicalizer->canonicalize($value);
|
||||
$shopUser = $this->shopUserRepository->findOneByEmail($emailCanonical);
|
||||
|
||||
if ($shopUser === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
"php": "^7.3",
|
||||
|
||||
"api-platform/core": "^2.5",
|
||||
"symfony/messenger": "^4.4",
|
||||
"sylius/core-bundle": "^1.7"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\CommandHandler;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Command\RegisterShopUser;
|
||||
use Sylius\Component\Core\Model\CustomerInterface;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\Core\Repository\CustomerRepositoryInterface;
|
||||
use Sylius\Component\Resource\Factory\FactoryInterface;
|
||||
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
final class RegisterShopUserHandlerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
FactoryInterface $shopUserFactory,
|
||||
FactoryInterface $customerFactory,
|
||||
CustomerRepositoryInterface $customerRepository,
|
||||
ObjectManager $shopUserManager
|
||||
): void {
|
||||
$this->beConstructedWith(
|
||||
$canonicalizer,
|
||||
$shopUserFactory,
|
||||
$customerFactory,
|
||||
$customerRepository,
|
||||
$shopUserManager
|
||||
);
|
||||
}
|
||||
|
||||
function it_is_a_message_handler(): void
|
||||
{
|
||||
$this->shouldImplement(MessageHandlerInterface::class);
|
||||
}
|
||||
|
||||
function it_creates_a_customer_and_user_with_given_data(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
FactoryInterface $shopUserFactory,
|
||||
FactoryInterface $customerFactory,
|
||||
CustomerRepositoryInterface $customerRepository,
|
||||
ObjectManager $shopUserManager,
|
||||
ShopUserInterface $shopUser,
|
||||
CustomerInterface $customer
|
||||
): void {
|
||||
$canonicalizer->canonicalize('WILL.SMITH@example.com')->willReturn('will.smith@example.com');
|
||||
$customerRepository->findOneBy(['emailCanonical' => 'will.smith@example.com'])->willReturn(null);
|
||||
|
||||
$shopUserFactory->createNew()->willReturn($shopUser);
|
||||
$customerFactory->createNew()->willReturn($customer);
|
||||
|
||||
$customer->getUser()->willReturn(null);
|
||||
|
||||
$shopUser->setPlainPassword('iamrobot')->shouldBeCalled();
|
||||
|
||||
$customer->setFirstName('Will')->shouldBeCalled();
|
||||
$customer->setLastName('Smith')->shouldBeCalled();
|
||||
$customer->setEmail('WILL.SMITH@example.com')->shouldBeCalled();
|
||||
$customer->setPhoneNumber('+13104322400')->shouldBeCalled();
|
||||
$customer->setUser($shopUser)->shouldBeCalled();
|
||||
|
||||
$shopUserManager->persist($shopUser)->shouldBeCalled();
|
||||
|
||||
$this(new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400'));
|
||||
}
|
||||
|
||||
function it_creates_only_a_user_if_customer_without_user_already_exists(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
FactoryInterface $shopUserFactory,
|
||||
FactoryInterface $customerFactory,
|
||||
CustomerRepositoryInterface $customerRepository,
|
||||
ObjectManager $shopUserManager,
|
||||
ShopUserInterface $shopUser,
|
||||
CustomerInterface $customer
|
||||
): void {
|
||||
$canonicalizer->canonicalize('WILL.SMITH@example.com')->willReturn('will.smith@example.com');
|
||||
$customerRepository->findOneBy(['emailCanonical' => 'will.smith@example.com'])->willReturn($customer);
|
||||
|
||||
$shopUserFactory->createNew()->willReturn($shopUser);
|
||||
$customerFactory->createNew()->shouldNotBeCalled();
|
||||
|
||||
$customer->getUser()->willReturn(null);
|
||||
|
||||
$shopUser->setPlainPassword('iamrobot')->shouldBeCalled();
|
||||
|
||||
$customer->setFirstName('Will')->shouldBeCalled();
|
||||
$customer->setLastName('Smith')->shouldBeCalled();
|
||||
$customer->setEmail('WILL.SMITH@example.com')->shouldBeCalled();
|
||||
$customer->setPhoneNumber('+13104322400')->shouldBeCalled();
|
||||
$customer->setUser($shopUser)->shouldBeCalled();
|
||||
|
||||
$shopUserManager->persist($shopUser)->shouldBeCalled();
|
||||
|
||||
$this(new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400'));
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_customer_with_user_already_exists(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
FactoryInterface $shopUserFactory,
|
||||
FactoryInterface $customerFactory,
|
||||
CustomerRepositoryInterface $customerRepository,
|
||||
ObjectManager $shopUserManager,
|
||||
ShopUserInterface $shopUser,
|
||||
CustomerInterface $customer,
|
||||
ShopUserInterface $existingShopUser
|
||||
): void {
|
||||
$canonicalizer->canonicalize('WILL.SMITH@example.com')->willReturn('will.smith@example.com');
|
||||
$customerRepository->findOneBy(['emailCanonical' => 'will.smith@example.com'])->willReturn($customer);
|
||||
|
||||
$customer->getUser()->willReturn($existingShopUser);
|
||||
|
||||
$shopUserFactory->createNew()->willReturn($shopUser);
|
||||
$customerFactory->createNew()->shouldNotBeCalled();
|
||||
|
||||
$shopUserManager->persist($shopUser)->shouldNotBeCalled();
|
||||
|
||||
$this->shouldThrow(\DomainException::class)->during('__invoke', [new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400')]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\UniqueShopUserEmail;
|
||||
use Sylius\Component\Core\Model\ShopUserInterface;
|
||||
use Sylius\Component\User\Canonicalizer\CanonicalizerInterface;
|
||||
use Sylius\Component\User\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
final class UniqueShopUserEmailValidatorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
UserRepositoryInterface $shopUserRepository,
|
||||
ExecutionContextInterface $executionContext
|
||||
): void {
|
||||
$this->beConstructedWith($canonicalizer, $shopUserRepository);
|
||||
$this->initialize($executionContext);
|
||||
}
|
||||
|
||||
function it_is_a_constraint_validator(): void
|
||||
{
|
||||
$this->shouldImplement(ConstraintValidatorInterface::class);
|
||||
}
|
||||
|
||||
function is_does_nothing_if_value_is_null(ExecutionContextInterface $executionContext): void
|
||||
{
|
||||
$executionContext->addViolation(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$this->validate(null, new UniqueShopUserEmail());
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_constraint_is_not_of_expected_type(): void
|
||||
{
|
||||
$this->shouldThrow(\InvalidArgumentException::class)->during('validate', ['', new class() extends Constraint {
|
||||
}]);
|
||||
}
|
||||
|
||||
function it_does_not_add_violation_if_a_user_with_given_email_is_not_found(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
UserRepositoryInterface $shopUserRepository,
|
||||
ExecutionContextInterface $executionContext
|
||||
): void {
|
||||
$canonicalizer->canonicalize('eMaIl@example.com')->willReturn('email@example.com');
|
||||
$shopUserRepository->findOneByEmail('email@example.com')->willReturn(null);
|
||||
|
||||
$executionContext->addViolation(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$this->validate('eMaIl@example.com', new UniqueShopUserEmail());
|
||||
}
|
||||
|
||||
function it_adds_violation_if_a_user_with_given_email_is_found(
|
||||
CanonicalizerInterface $canonicalizer,
|
||||
UserRepositoryInterface $shopUserRepository,
|
||||
ExecutionContextInterface $executionContext,
|
||||
ShopUserInterface $shopUser
|
||||
): void {
|
||||
$canonicalizer->canonicalize('eMaIl@example.com')->willReturn('email@example.com');
|
||||
$shopUserRepository->findOneByEmail('email@example.com')->willReturn($shopUser);
|
||||
|
||||
$executionContext->addViolation(Argument::cetera())->shouldBeCalled();
|
||||
|
||||
$this->validate('eMaIl@example.com', new UniqueShopUserEmail());
|
||||
}
|
||||
}
|
||||
12
symfony.lock
12
symfony.lock
|
|
@ -352,6 +352,18 @@
|
|||
"symfony/intl": {
|
||||
"version": "v4.1.3"
|
||||
},
|
||||
"symfony/messenger": {
|
||||
"version": "4.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "master",
|
||||
"version": "4.3",
|
||||
"ref": "8a2675c061737658bed85102e9241c752620e575"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/messenger.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/monolog-bridge": {
|
||||
"version": "v4.1.3"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue