From a959124e87e4b56f31b10d9e56c3780f95d8f0ed Mon Sep 17 00:00:00 2001 From: Matthieu Rolland Date: Tue, 2 Jul 2024 11:08:35 +0200 Subject: [PATCH] implement php attribute for lazy array --- src/Adapter/Presenter/AbstractLazyArray.php | 87 ++++++++++++++++---- src/Adapter/Presenter/Cart/CartLazyArray.php | 58 ++++--------- src/Adapter/Presenter/LazyArrayAttribute.php | 40 +++++++++ 3 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 src/Adapter/Presenter/LazyArrayAttribute.php diff --git a/src/Adapter/Presenter/AbstractLazyArray.php b/src/Adapter/Presenter/AbstractLazyArray.php index 77463d8e57fee..67370ea985587 100644 --- a/src/Adapter/Presenter/AbstractLazyArray.php +++ b/src/Adapter/Presenter/AbstractLazyArray.php @@ -68,8 +68,6 @@ */ abstract class AbstractLazyArray implements Iterator, ArrayAccess, Countable, JsonSerializable { - private const INDEX_NAME_PATTERN = '/@indexName\s+"([^"]+)"/'; - /** * @var ArrayObject */ @@ -96,18 +94,14 @@ public function __construct() $reflectionClass = new ReflectionClass(static::class); $methods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { - $methodDoc = $method->getDocComment(); - if (str_contains($methodDoc, '@arrayAccess')) { - if (preg_match(self::INDEX_NAME_PATTERN, $methodDoc, $matches)) { - $indexName = $matches[1]; - } else { - $indexName = $this->convertMethodNameToIndex($method->getName()); - } + $attributeInstance = $this->getAttributeInstanceFromMethod($method); + if ($this->isArrayAccessMethod($attributeInstance, $method)) { $this->arrayAccessList->offsetSet( - $indexName, + $this->getIndexNameFromMethod($attributeInstance, $method), [ 'type' => 'method', 'value' => $method->getName(), + 'isRewritable' => $this->isResultRewritable($reflectionClass, $attributeInstance), ] ); } @@ -400,12 +394,20 @@ public function intersectKey($array) */ public function offsetSet($offset, $value, $force = false): void { - if (!$force && $this->arrayAccessList->offsetExists($offset)) { - $result = $this->arrayAccessList->offsetGet($offset); - if ($result['type'] !== 'variable') { - throw new RuntimeException('Trying to set the index ' . print_r($offset, true) . ' of the LazyArray ' . static::class . ' already defined by a method is not allowed'); + // verify if the offset exists and is not rewritable, unless forced + if ($this->arrayAccessList->offsetExists($offset)) { + $offsetData = $this->arrayAccessList->offsetGet($offset); + + if (!$force && !$offsetData['isRewritable'] && $offsetData['type'] !== 'variable') { + $errorMessage = sprintf( + 'Trying to set the index %s of the LazyArray %s already defined by a method is not allowed.', + print_r($offset, true), + static::class + ); + throw new RuntimeException($errorMessage); } } + $this->arrayAccessList->offsetSet($offset, [ 'type' => 'variable', 'value' => $value, @@ -440,4 +442,61 @@ private function convertMethodNameToIndex($methodName) return Inflector::getInflector()->tableize($strippedMethodName); } + + private function isResultRewritable(ReflectionClass $reflexionClass, ?LazyArrayAttribute $methodAttributeInstance): bool + { + if (!is_null($methodAttributeInstance) && !is_null($methodAttributeInstance->isRewritable)) { + return $methodAttributeInstance->isRewritable; + } + + // no attribute found at method level, let's check at class level + $classAttributeInstance = null; + $classAttributes = $reflexionClass->getAttributes(); + + if (!empty($classAttributes)) { + $classAttributeInstance = $classAttributes[0]->newInstance(); + if (isset($classAttributeInstance->isRewritable)) { + return $classAttributeInstance->isRewritable; + } + } + + return false; + } + + private function getAttributeInstanceFromMethod(ReflectionMethod $method): ?LazyArrayAttribute + { + $attributeInstance = null; + $methodAttributes = $method->getAttributes(LazyArrayAttribute::class); + + if (!empty($methodAttributes)) { + $attributeInstance = $methodAttributes[0]->newInstance(); + } + + return $attributeInstance; + } + + private function getIndexNameFromMethod(?LazyArrayAttribute $attributeInstance, ReflectionMethod $method): string + { + if (!is_null($attributeInstance) && !empty($attributeInstance->indexName)) { + return $attributeInstance->indexName; + } + + return $this->convertMethodNameToIndex($method->getName()); + } + + private function isArrayAccessMethod($attributeInstance, $method): bool + { + if (!is_null($attributeInstance)) { + return $attributeInstance->arrayAccess; + } + + @trigger_error( + 'Configuring a method as arrayAccess through annotations is deprecated since version 9.0.0, use php attributes instead, using the LazyArrayAttribute class.', + E_USER_DEPRECATED + ); + + $methodDoc = $method->getDocComment(); + + return str_contains($methodDoc, '@arrayAccess'); + } } diff --git a/src/Adapter/Presenter/Cart/CartLazyArray.php b/src/Adapter/Presenter/Cart/CartLazyArray.php index 2828eacf53ee0..812aa8b6db11d 100644 --- a/src/Adapter/Presenter/Cart/CartLazyArray.php +++ b/src/Adapter/Presenter/Cart/CartLazyArray.php @@ -35,6 +35,7 @@ use Link; use PrestaShop\PrestaShop\Adapter\Image\ImageRetriever; use PrestaShop\PrestaShop\Adapter\Presenter\AbstractLazyArray; +use PrestaShop\PrestaShop\Adapter\Presenter\LazyArrayAttribute; use PrestaShop\PrestaShop\Adapter\Presenter\Product\ProductLazyArray; use PrestaShop\PrestaShop\Adapter\Presenter\Product\ProductListingLazyArray; use PrestaShop\PrestaShop\Adapter\Presenter\Product\ProductListingPresenter; @@ -43,6 +44,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; use Tools; +#[LazyArrayAttribute(isRewritable: true)] class CartLazyArray extends AbstractLazyArray { private bool $shouldSeparateGifts; @@ -98,9 +100,7 @@ public function __construct(Cart $cart, CartPresenter $cartPresenter, bool $shou parent::__construct(); } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getProducts(): array { if ($this->shouldSeparateGifts) { @@ -115,9 +115,7 @@ public function getProducts(): array return $this->products; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getTotals(): array { $total_excluding_tax = $this->cart->getOrderTotal(false); @@ -149,9 +147,7 @@ public function getTotals(): array return $this->totals; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getSubtotals(): array { $subtotals = []; @@ -218,9 +214,7 @@ public function getSubtotals(): array return $this->subTotals; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getProductsCount(): int { // If product list is already available, no need to execute a new sql query @@ -238,9 +232,7 @@ public function getProductsCount(): int return $this->productsCount; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getSummaryString(): string { $productsCount = $this->getProductsCount(); @@ -252,9 +244,7 @@ public function getSummaryString(): string return $this->summaryString; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getLabels(): array { $this->labels = [ @@ -269,9 +259,7 @@ public function getLabels(): array return $this->labels; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getIdAddressDelivery(): ?int { $this->idAddressDelivery = $this->cart->id_address_delivery; @@ -279,9 +267,7 @@ public function getIdAddressDelivery(): ?int return $this->idAddressDelivery; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getIdAddressInvoice(): ?int { $this->idAddressInvoice = $this->cart->id_address_invoice; @@ -289,9 +275,7 @@ public function getIdAddressInvoice(): ?int return $this->idAddressInvoice; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getIsVirtual(): bool { $this->isVirtual = $this->cart->isVirtualCart(); @@ -299,9 +283,7 @@ public function getIsVirtual(): bool return $this->isVirtual; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getVouchers(): array { $this->vouchers = $this->getTemplateVarVouchers(); @@ -309,9 +291,7 @@ public function getVouchers(): array return $this->vouchers; } - /** - * @arrayAccess - */ + #[LazyArrayAttribute(arrayAccess: true)] public function getDiscounts(): array { $vouchers = $this->getVouchers(); @@ -340,11 +320,7 @@ function ($voucher) { return $this->discounts; } - /** - * @arrayAccess - * - * @indexName "minimalPurchase" - */ + #[LazyArrayAttribute(arrayAccess: true, indexName: 'minimalPurchase')] public function getMinimalPurchase(): float { $minimalPurchase = $this->priceFormatter->convertAmount((float) Configuration::get('PS_PURCHASE_MINIMUM')); @@ -357,11 +333,7 @@ public function getMinimalPurchase(): float return $this->minimalPurchase; } - /** - * @arrayAccess - * - * @indexName "minimalPurchaseRequired" - */ + #[LazyArrayAttribute(arrayAccess: true, indexName: 'minimalPurchaseRequired')] public function getMinimalPurchaseRequired(): string { $minimalPurchase = $this->getMinimalPurchase(); diff --git a/src/Adapter/Presenter/LazyArrayAttribute.php b/src/Adapter/Presenter/LazyArrayAttribute.php new file mode 100644 index 0000000000000..9a9acf2d1ea81 --- /dev/null +++ b/src/Adapter/Presenter/LazyArrayAttribute.php @@ -0,0 +1,40 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +namespace PrestaShop\PrestaShop\Adapter\Presenter; + +use Attribute; + +#[Attribute] +class LazyArrayAttribute +{ + public function __construct( + public bool $arrayAccess = false, + public ?string $indexName = null, + public ?bool $isRewritable = null + ) { + } +}