This repository was archived by the owner on Dec 19, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 149
GraphQL-57: Manage Address Book #194
Merged
magento-engcom-team
merged 19 commits into
magento:2.3-develop
from
pfantini:57-manage-address-book
Jan 2, 2019
Merged
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
09c9e6b
GraphQL-57: Manager Address Book
pfantini fbefe29
GraphQL-57: Add functional test
pfantini 7aaba83
Refactor fillAddress and add missing doc
pfantini 47adffd
GraphQL-57: Refactor address resolve to check address attributes requ…
pfantini 9041bdb
GraphQL: Add missing module depenency and add input checker on addres…
pfantini cfc82be
GraphQL-57: Refactor Address resolve and change functional test
pfantini 6c6fdbc
GraphQL-57: Improve doc
pfantini 6436bcb
GraphQL-57: Remove unused dependencies
pfantini bfd4fc1
GraphQL-57: Refactor address update and data provider
pfantini 71d242c
GraphQL-57: Fix cyclomatic complexity in addressDataProvider
pfantini 468b290
GraphQL-57: Curate address data on customerDataProvider
pfantini c6de6a1
Merge remote-tracking branch 'origin/2.3-develop' into 57-manage-addr…
af50831
GraphQL-57: Manage Address Book
cbe1ea3
GraphQL-57: Manage Address Book
ec4c985
GraphQL-57: Manage Address Book
b0ab652
GraphQL-57: Manage Address Book
f41405d
Merge remote-tracking branch 'origin/2.3-develop' into 57-manage-addr…
2894aaa
GraphQL-57: Manage Address Book
b41627b
GraphQL-57: Manage Address Book
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
278 changes: 278 additions & 0 deletions
278
app/code/Magento/CustomerGraphQl/Model/Resolver/Address.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,278 @@ | ||
| <?php | ||
| /** | ||
| * Copyright © Magento, Inc. All rights reserved. | ||
| * See COPYING.txt for license details. | ||
| */ | ||
| declare(strict_types=1); | ||
|
|
||
| namespace Magento\CustomerGraphQl\Model\Resolver; | ||
|
|
||
| use Magento\Authorization\Model\UserContextInterface; | ||
| use Magento\Customer\Api\CustomerRepositoryInterface; | ||
| use Magento\Customer\Api\AddressRepositoryInterface; | ||
| use Magento\Customer\Api\AddressMetadataManagementInterface; | ||
| use Magento\Customer\Api\Data\AddressInterfaceFactory; | ||
| use Magento\Customer\Api\Data\AddressInterface; | ||
| use Magento\Framework\Api\DataObjectHelper; | ||
| use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; | ||
| use Magento\Framework\GraphQl\Config\Element\Field; | ||
| use Magento\Framework\GraphQl\Query\ResolverInterface; | ||
| use Magento\CustomerGraphQl\Model\Resolver\Address\AddressDataProvider; | ||
| use Magento\Eav\Model\Config; | ||
| use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; | ||
| use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; | ||
| use Magento\Framework\GraphQl\Exception\GraphQlInputException; | ||
| use Magento\Framework\Exception\NoSuchEntityException; | ||
|
|
||
| /** | ||
| * Customers Address, used for GraphQL request processing. | ||
| */ | ||
| class Address implements ResolverInterface | ||
| { | ||
| /** | ||
| * Mutation Address type | ||
| */ | ||
| const MUTATION_ADDRESS_CREATE = 'customerAddressCreate'; | ||
| const MUTATION_ADDRESS_UPDATE = 'customerAddressUpdate'; | ||
| const MUTATION_ADDRESS_DELETE = 'customerAddressDelete'; | ||
|
|
||
| /** | ||
| * @var AddressRepositoryInterface | ||
| */ | ||
| private $addressRepositoryInterface; | ||
|
|
||
| /** | ||
| * @var AddressInterfaceFactory | ||
| */ | ||
| private $addressInterfaceFactory; | ||
|
|
||
| /** | ||
| * @var Config | ||
| */ | ||
| private $eavConfig; | ||
|
|
||
| /** | ||
| * @var AddressDataProvider | ||
| */ | ||
| private $addressDataProvider; | ||
|
|
||
| /** | ||
| * @var DataObjectHelper | ||
| */ | ||
| private $dataObjectHelper; | ||
|
|
||
| /** | ||
| * @var array | ||
| */ | ||
| private $addressAttributes; | ||
|
|
||
| /** | ||
| * @param AddressRepositoryInterface $addressRepositoryInterface | ||
| * @param AddressInterfaceFactory $addressInterfaceFactory | ||
| * @param Config $eavConfig | ||
| * @param AddressDataProvider $addressDataProvider | ||
| * @param DataObjectHelper $dataObjectHelper | ||
| */ | ||
| public function __construct( | ||
| AddressRepositoryInterface $addressRepositoryInterface, | ||
| AddressInterfaceFactory $addressInterfaceFactory, | ||
| Config $eavConfig, | ||
| AddressDataProvider $addressDataProvider, | ||
| DataObjectHelper $dataObjectHelper | ||
| ) { | ||
| $this->addressRepositoryInterface = $addressRepositoryInterface; | ||
| $this->addressInterfaceFactory = $addressInterfaceFactory; | ||
| $this->eavConfig = $eavConfig; | ||
| $this->addressDataProvider = $addressDataProvider; | ||
| $this->dataObjectHelper = $dataObjectHelper; | ||
| $this->addressAttributes = $this->eavConfig->getEntityAttributes( | ||
| AddressMetadataManagementInterface::ENTITY_TYPE_ADDRESS | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @inheritdoc | ||
| */ | ||
| public function resolve( | ||
| Field $field, | ||
| $context, | ||
| ResolveInfo $info, | ||
| array $value = null, | ||
| array $args = null | ||
| ) { | ||
| /** @var \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $context */ | ||
| if ((!$context->getUserId()) || $context->getUserType() == UserContextInterface::USER_TYPE_GUEST) { | ||
| throw new GraphQlAuthorizationException( | ||
| __( | ||
| 'Current customer does not have access to the resource "%1"', | ||
| [AddressMetadataManagementInterface::ENTITY_TYPE_ADDRESS] | ||
| ) | ||
| ); | ||
| } | ||
| $customerId = $context->getUserId(); | ||
| switch ($field->getName()) { | ||
| case self::MUTATION_ADDRESS_CREATE: | ||
| return $this->addressDataProvider->processCustomerAddress( | ||
| $this->processCustomerAddressCreate($customerId, $args['input']) | ||
| ); | ||
| case self::MUTATION_ADDRESS_UPDATE: | ||
| return $this->addressDataProvider->processCustomerAddress( | ||
| $this->processCustomerAddressUpdate($customerId, $args['id'], $args['input']) | ||
| ); | ||
| case self::MUTATION_ADDRESS_DELETE: | ||
| return $this->processCustomerAddressDelete($customerId, $args['id']); | ||
| default: | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Get new address attribute input errors | ||
| * | ||
| * @param array $addressInput | ||
| * @return bool|string | ||
| */ | ||
| private function getNewAddressInputError(array $addressInput) | ||
| { | ||
| foreach ($this->addressAttributes as $attributeName => $attributeInfo) { | ||
| if ($attributeInfo->getIsRequired() | ||
| && (!isset($addressInput[$attributeName]) || empty($addressInput[$attributeName]))) { | ||
| return $attributeName; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Get update address attribute input errors | ||
| * | ||
| * @param array $addressInput | ||
| * @return bool|string | ||
| */ | ||
| private function getUpdateAddressInputError(array $addressInput) | ||
| { | ||
| foreach ($this->addressAttributes as $attributeName => $attributeInfo) { | ||
| if ($attributeInfo->getIsRequired() | ||
| && (isset($addressInput[$attributeName]) && empty($addressInput[$attributeName]))) { | ||
| return $attributeName; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Add $addressInput array information to a $address object | ||
| * | ||
| * @param AddressInterface $address | ||
| * @param array $addressInput | ||
| * @return AddressInterface | ||
| */ | ||
| private function fillAddress(AddressInterface $address, array $addressInput) : AddressInterface | ||
| { | ||
| $this->dataObjectHelper->populateWithArray( | ||
| $address, | ||
| $addressInput, | ||
| AddressInterface::class | ||
| ); | ||
| return $address; | ||
| } | ||
|
|
||
| /** | ||
| * Process customer address create | ||
| * | ||
| * @param int $customerId | ||
| * @param array $addressInput | ||
| * @return AddressInterface | ||
| * @throws GraphQlInputException | ||
| */ | ||
| private function processCustomerAddressCreate($customerId, array $addressInput) : AddressInterface | ||
| { | ||
| $errorInput = $this->getNewAddressInputError($addressInput); | ||
| if ($errorInput) { | ||
| throw new GraphQlInputException( | ||
| __('Required parameter %1 is missing', [$errorInput]) | ||
| ); | ||
| } | ||
| /** @var AddressInterface $newAddress */ | ||
| $newAddress = $this->fillAddress( | ||
| $this->addressInterfaceFactory->create(), | ||
| $addressInput | ||
| ); | ||
| $newAddress->setCustomerId($customerId); | ||
| return $this->addressRepositoryInterface->save($newAddress); | ||
| } | ||
|
|
||
| /** | ||
| * Process customer address update | ||
| * | ||
| * @param int $customerId | ||
| * @param int $addressId | ||
| * @param array $addressInput | ||
| * @return AddressInterface | ||
| * @throws GraphQlAuthorizationException | ||
| * @throws GraphQlNoSuchEntityException | ||
| * @throws GraphQlInputException | ||
| */ | ||
| private function processCustomerAddressUpdate($customerId, $addressId, array $addressInput) | ||
| { | ||
| try { | ||
| /** @var AddressInterface $address */ | ||
| $address = $this->addressRepositoryInterface->getById($addressId); | ||
| } catch (NoSuchEntityException $exception) { | ||
| throw new GraphQlNoSuchEntityException( | ||
| __('Address id %1 does not exist.', [$addressId]) | ||
| ); | ||
| } | ||
| if ($address->getCustomerId() != $customerId) { | ||
| throw new GraphQlAuthorizationException( | ||
| __('Current customer does not have permission to update address id %1', [$addressId]) | ||
| ); | ||
| } | ||
| $errorInput = $this->getUpdateAddressInputError($addressInput); | ||
| if ($errorInput) { | ||
| throw new GraphQlInputException( | ||
| __('Required parameter %1 is missing', [$errorInput]) | ||
| ); | ||
| } | ||
| return $this->addressRepositoryInterface->save( | ||
| $this->fillAddress($address, $addressInput) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Process customer address delete | ||
| * | ||
| * @param int $customerId | ||
| * @param int $addressId | ||
| * @return bool | ||
| * @throws GraphQlAuthorizationException | ||
| * @throws GraphQlNoSuchEntityException | ||
| */ | ||
| private function processCustomerAddressDelete($customerId, $addressId) | ||
| { | ||
| try { | ||
| /** @var AddressInterface $address */ | ||
| $address = $this->addressRepositoryInterface->getById($addressId); | ||
| } catch (NoSuchEntityException $exception) { | ||
| throw new GraphQlNoSuchEntityException( | ||
| __('Address id %1 does not exist.', [$addressId]) | ||
| ); | ||
| } | ||
| if ($customerId != $address->getCustomerId()) { | ||
| throw new GraphQlAuthorizationException( | ||
| __('Current customer does not have permission to delete address id %1', [$addressId]) | ||
| ); | ||
| } | ||
| if ($address->isDefaultBilling()) { | ||
| throw new GraphQlAuthorizationException( | ||
| __('Customer Address %1 is set as default billing address and can not be deleted', [$addressId]) | ||
| ); | ||
| } | ||
| if ($address->isDefaultShipping()) { | ||
| throw new GraphQlAuthorizationException( | ||
| __('Customer Address %1 is set as default shipping address and can not be deleted', [$addressId]) | ||
| ); | ||
| } | ||
| return $this->addressRepositoryInterface->delete($address); | ||
| } | ||
| } | ||
85 changes: 85 additions & 0 deletions
85
app/code/Magento/CustomerGraphQl/Model/Resolver/Address/AddressDataProvider.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| <?php | ||
| /** | ||
| * Copyright © Magento, Inc. All rights reserved. | ||
| * See COPYING.txt for license details. | ||
| */ | ||
| declare(strict_types=1); | ||
|
|
||
| namespace Magento\CustomerGraphQl\Model\Resolver\Address; | ||
|
|
||
| use Magento\Customer\Api\Data\AddressInterface; | ||
| use Magento\Customer\Api\AddressRepositoryInterface; | ||
| use Magento\Framework\Webapi\ServiceOutputProcessor; | ||
| use Magento\Framework\Serialize\SerializerInterface; | ||
|
|
||
| /** | ||
| * Customer Address field data provider, used for GraphQL request processing. | ||
| */ | ||
| class AddressDataProvider | ||
| { | ||
| /** | ||
| * @var ServiceOutputProcessor | ||
| */ | ||
| private $serviceOutputProcessor; | ||
|
|
||
| /** | ||
| * @var SerializerInterface | ||
| */ | ||
| private $jsonSerializer; | ||
|
|
||
| /** | ||
| * @param ServiceOutputProcessor $serviceOutputProcessor | ||
| * @param SerializerInterface $jsonSerializer | ||
| */ | ||
| public function __construct( | ||
| ServiceOutputProcessor $serviceOutputProcessor, | ||
| SerializerInterface $jsonSerializer | ||
| ) { | ||
| $this->serviceOutputProcessor = $serviceOutputProcessor; | ||
| $this->jsonSerializer = $jsonSerializer; | ||
| } | ||
|
|
||
| /** | ||
| * Transform single customer address data from object to in array format | ||
| * | ||
| * @param AddressInterface $addressObject | ||
| * @return array | ||
| */ | ||
| public function processCustomerAddress(AddressInterface $addressObject) : array | ||
| { | ||
| $address = $this->serviceOutputProcessor->process( | ||
| $addressObject, | ||
| AddressRepositoryInterface::class, | ||
| 'getById' | ||
| ); | ||
| if (isset($address['extension_attributes'])) { | ||
| $address = array_merge($address, $address['extension_attributes']); | ||
| } | ||
| $customAttributes = []; | ||
| if (isset($address['custom_attributes'])) { | ||
| foreach ($address['custom_attributes'] as $attribute) { | ||
| $isArray = false; | ||
| if (is_array($attribute['value'])) { | ||
| $isArray = true; | ||
| foreach ($attribute['value'] as $attributeValue) { | ||
| if (is_array($attributeValue)) { | ||
| $customAttributes[$attribute['attribute_code']] = $this->jsonSerializer->serialize( | ||
| $attribute['value'] | ||
| ); | ||
| continue; | ||
| } | ||
| $customAttributes[$attribute['attribute_code']] = implode(',', $attribute['value']); | ||
| continue; | ||
| } | ||
| } | ||
| if ($isArray) { | ||
| continue; | ||
| } | ||
| $customAttributes[$attribute['attribute_code']] = $attribute['value']; | ||
| } | ||
| } | ||
| $address = array_merge($address, $customAttributes); | ||
|
|
||
| return $address; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.