Skip to content

Commit 182fdaa

Browse files
authored
Merge pull request #10155 from magento-lynx/graphql-api-enhancements
2 parents 297b774 + 7854577 commit 182fdaa

27 files changed

+2885
-1196
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Customer\Address;
9+
10+
use Magento\Customer\Api\AddressRepositoryInterface;
11+
use Magento\Customer\Api\Data\AddressInterface;
12+
use Magento\Framework\Exception\LocalizedException;
13+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
14+
15+
class DeleteCustomerAddressV2
16+
{
17+
/**
18+
* DeleteCustomerAddressV2 Constructor
19+
*
20+
* @param AddressRepositoryInterface $addressRepository
21+
*/
22+
public function __construct(
23+
private readonly AddressRepositoryInterface $addressRepository
24+
) {
25+
}
26+
27+
/**
28+
* Delete customer address
29+
*
30+
* @param AddressInterface $address
31+
* @return void
32+
* @throws GraphQlInputException
33+
*/
34+
public function execute(AddressInterface $address): void
35+
{
36+
if ($address->isDefaultBilling()) {
37+
throw new GraphQlInputException(
38+
__('Customer Address with the specified ID is set as default billing address and can not be deleted')
39+
);
40+
}
41+
42+
if ($address->isDefaultShipping()) {
43+
throw new GraphQlInputException(
44+
__('Customer Address with the specified ID is set as default shipping address and can not be deleted')
45+
);
46+
}
47+
48+
try {
49+
$this->addressRepository->delete($address);
50+
} catch (LocalizedException $e) {
51+
throw new GraphQlInputException(__($e->getMessage()), $e);
52+
}
53+
}
54+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Customer\Address;
9+
10+
use Magento\Customer\Api\AddressRepositoryInterface;
11+
use Magento\Customer\Api\Data\AddressInterface;
12+
use Magento\Framework\Exception\LocalizedException;
13+
use Magento\Framework\Exception\NoSuchEntityException;
14+
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
15+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
16+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
17+
18+
class GetCustomerAddressV2
19+
{
20+
/**
21+
* GetCustomerAddressV2 Constructor
22+
*
23+
* @param AddressRepositoryInterface $addressRepository
24+
*/
25+
public function __construct(
26+
private readonly AddressRepositoryInterface $addressRepository
27+
) {
28+
}
29+
30+
/**
31+
* Get customer address
32+
*
33+
* @param int $addressId
34+
* @param int $customerId
35+
* @return AddressInterface
36+
* @throws GraphQlInputException
37+
* @throws GraphQlNoSuchEntityException
38+
* @throws GraphQlAuthorizationException
39+
*/
40+
public function execute(int $addressId, int $customerId): AddressInterface
41+
{
42+
try {
43+
$customerAddress = $this->addressRepository->getById($addressId);
44+
} catch (NoSuchEntityException $e) {
45+
throw new GraphQlNoSuchEntityException(
46+
__('Could not find an address with the specified ID')
47+
);
48+
} catch (LocalizedException $e) {
49+
throw new GraphQlInputException(__($e->getMessage()), $e);
50+
}
51+
52+
if ((int)$customerAddress->getCustomerId() !== $customerId) {
53+
throw new GraphQlAuthorizationException(
54+
__('Current customer does not have permission to get address with the specified ID')
55+
);
56+
}
57+
58+
return $customerAddress;
59+
}
60+
}

app/code/Magento/CustomerGraphQl/Model/Customer/ExtractCustomerData.php

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Magento\Customer\Api\Data\CustomerInterface;
1313
use Magento\EavGraphQl\Model\GetAttributeValueComposite;
1414
use Magento\Framework\Exception\LocalizedException;
15+
use Magento\Framework\GraphQl\Query\Uid;
1516
use Magento\Framework\Webapi\ServiceOutputProcessor;
1617

1718
/**
@@ -20,25 +21,17 @@
2021
class ExtractCustomerData
2122
{
2223
/**
23-
* @var ServiceOutputProcessor
24-
*/
25-
private $serviceOutputProcessor;
26-
27-
/**
28-
* @var GetAttributeValueComposite
29-
*/
30-
private GetAttributeValueComposite $getAttributeValueComposite;
31-
32-
/**
24+
* ExtractCustomerData Constructor.
25+
*
3326
* @param ServiceOutputProcessor $serviceOutputProcessor
3427
* @param GetAttributeValueComposite $getAttributeValueComposite
28+
* @param Uid $idEncoder
3529
*/
3630
public function __construct(
37-
ServiceOutputProcessor $serviceOutputProcessor,
38-
GetAttributeValueComposite $getAttributeValueComposite
31+
private readonly ServiceOutputProcessor $serviceOutputProcessor,
32+
private readonly GetAttributeValueComposite $getAttributeValueComposite,
33+
private readonly Uid $idEncoder
3934
) {
40-
$this->serviceOutputProcessor = $serviceOutputProcessor;
41-
$this->getAttributeValueComposite = $getAttributeValueComposite;
4235
}
4336

4437
/**
@@ -57,6 +50,7 @@ private function curateAddressData(array $arrayAddress): array
5750
$arrayAddress[$key]['default_billing'] = false;
5851
}
5952
}
53+
6054
return $arrayAddress;
6155
}
6256

@@ -98,14 +92,15 @@ function (array $customAttribute) {
9892
}
9993
//Fields are deprecated and should not be exposed on storefront.
10094
$customerData['group_id'] = null;
101-
$customerData['id'] = null;
102-
10395
$customerData['model'] = $customer;
10496

10597
//'dob' is deprecated, 'date_of_birth' is used instead.
10698
if (!empty($customerData['dob'])) {
10799
$customerData['date_of_birth'] = $customerData['dob'];
108100
}
101+
102+
$customerData['id'] = $this->idEncoder->encode((string) $customerData['id']);
103+
109104
return $customerData;
110105
}
111106
}

app/code/Magento/CustomerGraphQl/Model/Resolver/CustomerAddressUid.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ public function resolve(
3434
ResolveInfo $info,
3535
?array $value = null,
3636
?array $args = null
37-
): string {
38-
if (!isset($value['id'])) {
39-
throw new LocalizedException(__('Missing required address ID.'));
40-
}
41-
42-
return $this->idEncoder->encode((string) $value['id']);
37+
): ?string {
38+
return isset($value['id']) ? $this->idEncoder->encode((string) $value['id']): null;
4339
}
4440
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Resolver;
9+
10+
use Magento\CustomerGraphQl\Model\Customer\Address\DeleteCustomerAddressV2 as DeleteAddress;
11+
use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddressV2;
12+
use Magento\Framework\GraphQl\Config\Element\Field;
13+
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
14+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
15+
use Magento\Framework\GraphQl\Query\ResolverInterface;
16+
use Magento\Framework\GraphQl\Query\Uid;
17+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
18+
use Magento\GraphQl\Model\Query\ContextInterface;
19+
20+
class DeleteCustomerAddressV2 implements ResolverInterface
21+
{
22+
/**
23+
* DeleteCustomerAddressV2 Constructor
24+
*
25+
* @param GetCustomerAddressV2 $getCustomerAddress
26+
* @param DeleteAddress $deleteCustomerAddress
27+
* @param Uid $uidEncoder
28+
*/
29+
public function __construct(
30+
private readonly GetCustomerAddressV2 $getCustomerAddress,
31+
private readonly DeleteAddress $deleteCustomerAddress,
32+
private readonly Uid $uidEncoder
33+
) {
34+
}
35+
36+
/**
37+
* @inheritdoc
38+
*/
39+
public function resolve(
40+
Field $field,
41+
$context,
42+
ResolveInfo $info,
43+
?array $value = null,
44+
?array $args = null
45+
): bool {
46+
/** @var ContextInterface $context */
47+
if (false === $context->getExtensionAttributes()->getIsCustomer()) {
48+
throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.'));
49+
}
50+
51+
if (empty($args['uid'])) {
52+
throw new GraphQlInputException(__('Address "uid" value must be specified'));
53+
}
54+
55+
$address = $this->getCustomerAddress->execute(
56+
(int) $this->uidEncoder->decode((string) $args['uid']),
57+
$context->getUserId()
58+
);
59+
60+
$this->deleteCustomerAddress->execute($address);
61+
62+
return true;
63+
}
64+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Resolver;
9+
10+
use Magento\CustomerGraphQl\Model\Customer\Address\ExtractCustomerAddressData;
11+
use Magento\CustomerGraphQl\Model\Customer\Address\UpdateCustomerAddress;
12+
use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddressV2;
13+
use Magento\Framework\GraphQl\Config\Element\Field;
14+
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
15+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
16+
use Magento\Framework\GraphQl\Query\ResolverInterface;
17+
use Magento\Framework\GraphQl\Query\Uid;
18+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
19+
use Magento\GraphQl\Model\Query\ContextInterface;
20+
21+
class UpdateCustomerAddressV2 implements ResolverInterface
22+
{
23+
/**
24+
* UpdateCustomerAddressV2 Constructor
25+
*
26+
* @param GetCustomerAddressV2 $getCustomerAddress
27+
* @param UpdateCustomerAddress $updateCustomerAddress
28+
* @param ExtractCustomerAddressData $extractCustomerAddressData
29+
* @param Uid $uidEncoder
30+
*/
31+
public function __construct(
32+
private readonly GetCustomerAddressV2 $getCustomerAddress,
33+
private readonly UpdateCustomerAddress $updateCustomerAddress,
34+
private readonly ExtractCustomerAddressData $extractCustomerAddressData,
35+
private readonly Uid $uidEncoder
36+
) {
37+
}
38+
39+
/**
40+
* @inheritdoc
41+
*/
42+
public function resolve(
43+
Field $field,
44+
$context,
45+
ResolveInfo $info,
46+
?array $value = null,
47+
?array $args = null
48+
): array {
49+
/** @var ContextInterface $context */
50+
if (false === $context->getExtensionAttributes()->getIsCustomer()) {
51+
throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.'));
52+
}
53+
54+
if (empty($args['uid'])) {
55+
throw new GraphQlInputException(__('Address "uid" value must be specified'));
56+
}
57+
58+
if (empty($args['input']) || !is_array($args['input'])) {
59+
throw new GraphQlInputException(__('"input" value must be specified'));
60+
}
61+
62+
$address = $this->getCustomerAddress->execute(
63+
(int) $this->uidEncoder->decode((string) $args['uid']),
64+
$context->getUserId()
65+
);
66+
67+
$this->updateCustomerAddress->execute($address, $args['input']);
68+
69+
return $this->extractCustomerAddressData->execute($address);
70+
}
71+
}

app/code/Magento/CustomerGraphQl/etc/schema.graphqls

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ type Mutation {
2727
deleteCustomer: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomer") @doc(description:"Delete customer account")
2828
revokeCustomerToken: RevokeCustomerTokenOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke the customer token.")
2929
createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create a billing or shipping address for a customer or guest.")
30-
updateCustomerAddress(id: Int! @doc(description: "The ID assigned to the customer address."), input: CustomerAddressInput @doc(description: "An input object that contains changes to the customer address.")): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update the billing or shipping address of a customer or guest.")
31-
deleteCustomerAddress(id: Int! @doc(description: "The ID of the customer address to be deleted.")): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddress") @doc(description: "Delete the billing or shipping address of a customer.")
30+
updateCustomerAddress(id: Int! @doc(description: "The ID assigned to the customer address."), input: CustomerAddressInput @doc(description: "An input object that contains changes to the customer address.")): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update the billing or shipping address of a customer or guest.") @deprecated(reason:"Use `updateCustomerAddressV2` instead.")
31+
deleteCustomerAddress(id: Int! @doc(description: "The ID of the customer address to be deleted.")): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddress") @doc(description: "Delete the billing or shipping address of a customer.") @deprecated(reason:"Use `deleteCustomerAddressV2` instead.")
3232
requestPasswordResetEmail(email: String! @doc(description: "The customer's email address.")): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RequestPasswordResetEmail") @doc(description: "Request an email with a reset password token for the registered customer identified by the specified email.")
3333
resetPassword(email: String! @doc(description: "The customer's email address."), resetPasswordToken: String! @doc(description: "A runtime token generated by the `requestPasswordResetEmail` mutation."), newPassword: String! @doc(description: "The customer's new password.")): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResetPassword") @doc(description: "Reset a customer's password using the reset password token that the customer received in an email after requesting it using `requestPasswordResetEmail`.")
3434
updateCustomerEmail(email: String! @doc(description: "The customer's email address."), password: String! @doc(description: "The customer's password.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerEmail") @doc(description: "Change the email address for the logged-in customer.")
3535
confirmEmail(input: ConfirmEmailInput! @doc(description: "An input object to identify the customer to confirm the email.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ConfirmEmail") @doc(description: "Confirms the email address for a customer.")
3636
resendConfirmationEmail(email: String! @doc(description: "The email address to send the confirmation email to.")): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResendConfirmationEmail") @doc(description: "Resends the confirmation email to a customer.")
37+
updateCustomerAddressV2(uid: ID! @doc(description: "The unique ID of the customer address.") input: CustomerAddressInput @doc(description: "An input object that contains changes to the customer address.")): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddressV2") @doc(description: "Update the billing or shipping address of a customer or guest.")
38+
deleteCustomerAddressV2(uid: ID! @doc(description: "The unique ID of the customer address to be deleted.")): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddressV2") @doc(description: "Delete the billing or shipping address of a customer.")
3739
}
3840

3941
input ConfirmEmailInput @doc(description: "Contains details about a customer email address to confirm.") {
@@ -145,7 +147,7 @@ type Customer @doc(description: "Defines the customer name, addresses, and other
145147
dob: String @doc(description: "The customer's date of birth.") @deprecated(reason: "Use `date_of_birth` instead.")
146148
date_of_birth: String @doc(description: "The customer's date of birth.")
147149
taxvat: String @doc(description: "The customer's Value-added tax (VAT) number (for corporate customers).")
148-
id: Int @doc(description: "The ID assigned to the customer.") @deprecated(reason: "`id` is not needed as part of `Customer`, because on the server side, it can be identified based on the customer token used for authentication. There is no need to know customer ID on the client side.")
150+
id: ID! @doc(description: "The unique ID assigned to the customer.")
149151
is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter.") @resolver(class: "\\Magento\\NewsletterGraphQl\\Model\\Resolver\\IsSubscribed")
150152
addresses: [CustomerAddress] @doc(description: "An array containing the customer's shipping and billing addresses.") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CustomerAddresses")
151153
addressesV2(
@@ -165,8 +167,8 @@ type CustomerAddresses {
165167
}
166168

167169
type CustomerAddress @doc(description: "Contains detailed information about a customer's billing or shipping address."){
168-
id: Int @doc(description: "The ID of a `CustomerAddress` object.")
169-
uid: ID! @doc(description: "The unique ID for a `CustomerAddress` object.") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CustomerAddressUid")
170+
id: Int @doc(description: "The ID of a `CustomerAddress` object.") @deprecated(reason: "Use `uid` instead.")
171+
uid: ID @doc(description: "The unique ID for a `CustomerAddress` object.") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CustomerAddressUid")
170172
customer_id: Int @doc(description: "The customer ID") @deprecated(reason: "`customer_id` is not needed as part of `CustomerAddress`. The `id` is a unique identifier for the addresses.")
171173
region: CustomerAddressRegion @doc(description: "An object containing the region name, region code, and region ID.")
172174
region_id: Int @doc(description: "The unique ID for a pre-defined region.")

0 commit comments

Comments
 (0)