From b425c67dcbbb85b1c0cbeec0899d5e4c16894bfc Mon Sep 17 00:00:00 2001 From: Eduardo Pittol Date: Sat, 11 Jul 2026 17:31:53 -0300 Subject: [PATCH] Add @phpstan-type shapes for high-arity WooCommerce builder overrides/criteria Converts the loosely-typed array overrides/criteria params on ProductMethods::haveProductInDatabase/haveManyProductsInDatabase, CouponMethods::haveCouponInDatabase and its discount-type wrappers, and OrderMethods::haveOrderInDatabase/haveManyOrdersInDatabase/haveOrderAddressInDatabase/ seeOrderAddressInDatabase/haveOrderItemInDatabase into unsealed @phpstan-type array shapes with enum literals for post/order status, stock status, tax status, and discount-type values. At PHPStan level max over src and tests, these shapes now double as the machine-readable allowed-keys map for agents and as assertions checked against every shipped Cest on CI. Closes #37 --- CHANGELOG.md | 4 ++ src/WooCommerce/Method/CouponMethods.php | 72 +++++++++++++++-------- src/WooCommerce/Method/OrderMethods.php | 62 +++++++++++++++---- src/WooCommerce/Method/ProductMethods.php | 39 ++++++++++-- 4 files changed, 137 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39d8164..325f414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `@phpstan-type` shape aliases (unsealed, with enum literals for status/stock/discount values) for the high-arity `overrides`/`criteria` params on `ProductMethods::haveProductInDatabase`/`haveManyProductsInDatabase`, `CouponMethods::haveCouponInDatabase` and its percentage/fixed-cart/fixed-product/free-shipping wrappers, and `OrderMethods::haveOrderInDatabase`/`haveManyOrdersInDatabase`/`haveOrderAddressInDatabase`/`seeOrderAddressInDatabase`/`haveOrderItemInDatabase` — so PHPStan (already at level max over `src` and `tests`) flags hallucinated keys/values in any Cest ([#37](https://github.com/aztecweb/aztecweb-wp-browser/issues/37)). + ## [0.1.0] - 2026-06-09 ### Added diff --git a/src/WooCommerce/Method/CouponMethods.php b/src/WooCommerce/Method/CouponMethods.php index d730622..22cd88a 100644 --- a/src/WooCommerce/Method/CouponMethods.php +++ b/src/WooCommerce/Method/CouponMethods.php @@ -6,6 +6,36 @@ use lucatume\WPBrowser\Module\WPDb; +/** + * @phpstan-type CouponStatus 'publish'|'draft'|'pending'|'private'|'trash' + * @phpstan-type CouponDiscountType 'percent'|'fixed_cart'|'fixed_product' + * @phpstan-type CouponYesNo 'yes'|'no' + * @phpstan-type CouponMeta array{ + * discount_type?: CouponDiscountType, + * coupon_amount?: string|float, + * free_shipping?: CouponYesNo, + * minimum_amount?: string, + * usage_limit?: string|int, + * usage_limit_per_user?: string|int, + * limit_usage_to_x_items?: string|int, + * product_ids?: string, + * exclude_product_ids?: string, + * product_categories?: string, + * exclude_product_categories?: string, + * individual_use?: CouponYesNo, + * usage_count?: string|int, + * ... + * } + * @phpstan-type CouponOverrides array{ + * code?: string, + * post_type?: string, + * post_status?: CouponStatus, + * post_title?: string, + * post_name?: string, + * meta?: CouponMeta, + * ... + * } + */ trait CouponMethods { abstract protected function wpDb(): WPDb; @@ -24,13 +54,13 @@ abstract protected function wpDb(): WPDb; * ]); * ``` * - * @param array $overrides Post and meta data overrides. Meta is passed under the 'meta' key. + * @param CouponOverrides $overrides Post and meta data overrides. Meta is passed under the 'meta' key. * * @return int The coupon post ID. */ public function haveCouponInDatabase(array $overrides = []): int { - $meta = isset($overrides['meta']) && is_array($overrides['meta']) ? $overrides['meta'] : []; + $meta = $overrides['meta'] ?? []; unset($overrides['meta']); $couponData = array_merge([ @@ -79,18 +109,16 @@ public function haveCouponInDatabase(array $overrides = []): int * ]); * ``` * - * @param string $code The coupon code. - * @param float $percentage The discount percentage (0-100). - * @param array $overrides Post and meta data overrides. + * @param string $code The coupon code. + * @param float $percentage The discount percentage (0-100). + * @param CouponOverrides $overrides Post and meta data overrides. * * @return int The coupon post ID. */ public function havePercentageCouponInDatabase(string $code, float $percentage, array $overrides = []): int { $overrides['code'] = $code; - if (!isset($overrides['meta']) || !is_array($overrides['meta'])) { - $overrides['meta'] = []; - } + $overrides['meta'] ??= []; $overrides['meta']['discount_type'] = 'percent'; $overrides['meta']['coupon_amount'] = $percentage; @@ -105,18 +133,16 @@ public function havePercentageCouponInDatabase(string $code, float $percentage, * $couponId = $I->haveFixedCartCouponInDatabase('FIXED5', 5.00); * ``` * - * @param string $code The coupon code. - * @param float $amount The discount amount in shop currency. - * @param array $overrides Post and meta data overrides. + * @param string $code The coupon code. + * @param float $amount The discount amount in shop currency. + * @param CouponOverrides $overrides Post and meta data overrides. * * @return int The coupon post ID. */ public function haveFixedCartCouponInDatabase(string $code, float $amount, array $overrides = []): int { $overrides['code'] = $code; - if (!isset($overrides['meta']) || !is_array($overrides['meta'])) { - $overrides['meta'] = []; - } + $overrides['meta'] ??= []; $overrides['meta']['discount_type'] = 'fixed_cart'; $overrides['meta']['coupon_amount'] = $amount; @@ -131,18 +157,16 @@ public function haveFixedCartCouponInDatabase(string $code, float $amount, array * $couponId = $I->haveFixedProductCouponInDatabase('PROD10', 10.00); * ``` * - * @param string $code The coupon code. - * @param float $amount The discount amount in shop currency. - * @param array $overrides Post and meta data overrides. + * @param string $code The coupon code. + * @param float $amount The discount amount in shop currency. + * @param CouponOverrides $overrides Post and meta data overrides. * * @return int The coupon post ID. */ public function haveFixedProductCouponInDatabase(string $code, float $amount, array $overrides = []): int { $overrides['code'] = $code; - if (!isset($overrides['meta']) || !is_array($overrides['meta'])) { - $overrides['meta'] = []; - } + $overrides['meta'] ??= []; $overrides['meta']['discount_type'] = 'fixed_product'; $overrides['meta']['coupon_amount'] = $amount; @@ -157,17 +181,15 @@ public function haveFixedProductCouponInDatabase(string $code, float $amount, ar * $couponId = $I->haveFreeShippingCouponInDatabase('FREESHIPPING'); * ``` * - * @param string $code The coupon code. - * @param array $overrides Post and meta data overrides. + * @param string $code The coupon code. + * @param CouponOverrides $overrides Post and meta data overrides. * * @return int The coupon post ID. */ public function haveFreeShippingCouponInDatabase(string $code, array $overrides = []): int { $overrides['code'] = $code; - if (!isset($overrides['meta']) || !is_array($overrides['meta'])) { - $overrides['meta'] = []; - } + $overrides['meta'] ??= []; $overrides['meta']['discount_type'] = 'fixed_cart'; $overrides['meta']['free_shipping'] = 'yes'; diff --git a/src/WooCommerce/Method/OrderMethods.php b/src/WooCommerce/Method/OrderMethods.php index aa6e1ce..16fdf31 100644 --- a/src/WooCommerce/Method/OrderMethods.php +++ b/src/WooCommerce/Method/OrderMethods.php @@ -7,6 +7,48 @@ use Aztec\WPBrowser\WooCommerce\OrderStorage\OrderStorageInterface; use lucatume\WPBrowser\Module\WPDb; +/** + * @phpstan-type OrderStatus 'wc-pending'|'wc-processing'|'wc-on-hold'|'wc-completed'|'wc-cancelled'|'wc-refunded'|'wc-failed'|'wc-checkout-draft' + * @phpstan-type OrderAddressType 'billing'|'shipping' + * @phpstan-type OrderAddress array{ + * first_name?: string, + * last_name?: string, + * company?: string, + * address_1?: string, + * address_2?: string, + * city?: string, + * state?: string, + * postcode?: string, + * country?: string, + * email?: string, + * phone?: string, + * ... + * } + * @phpstan-type OrderItemType 'line_item'|'fee'|'shipping'|'coupon'|'tax' + * @phpstan-type OrderItemOverrides array{ + * order_item_name?: string, + * order_item_type?: OrderItemType, + * meta?: array, + * ... + * } + * @phpstan-type OrderOverrides array{ + * status?: OrderStatus, + * customer_id?: int, + * currency?: string, + * total_amount?: string, + * tax_amount?: string, + * billing_email?: string, + * payment_method?: string, + * payment_method_title?: string, + * transaction_id?: string, + * customer_note?: string, + * parent_order_id?: int, + * address?: array{billing?: OrderAddress, shipping?: OrderAddress}, + * items?: list, + * meta?: array, + * ... + * } + */ trait OrderMethods { abstract protected function wpDb(): WPDb; @@ -24,7 +66,7 @@ abstract protected function orderStorage(): OrderStorageInterface; * $I->seeOrderInDatabase(['id' => $orderId, 'status' => 'processing']); * ``` * - * @param array $overrides Order data overrides (status, customer_id, etc.). Behavior depends on storage mode (HPOS or Legacy) + * @param OrderOverrides $overrides Order data overrides (status, customer_id, etc.). Behavior depends on storage mode (HPOS or Legacy) * * @return int The created order ID */ @@ -149,9 +191,9 @@ public function haveOrderStatus(int $orderId, string $newStatus): void * $I->assertGreaterThan(0, $addressId); * ``` * - * @param int $orderId Order ID - * @param string $addressType Address type ('billing' or 'shipping') - * @param array $overrides Address fields (first_name, last_name, company, address_1, address_2, city, state, postcode, country, email, phone) + * @param int $orderId Order ID + * @param OrderAddressType $addressType Address type ('billing' or 'shipping') + * @param OrderAddress $overrides Address fields (first_name, last_name, company, address_1, address_2, city, state, postcode, country, email, phone) * * @return int Address ID */ @@ -174,8 +216,8 @@ public function haveOrderAddressInDatabase(int $orderId, string $addressType, ar * $I->assertGreaterThan(0, $itemId); * ``` * - * @param int $orderId Order ID - * @param array $overrides Line item data (order_item_name, order_item_type, meta array for item meta) + * @param int $orderId Order ID + * @param OrderItemOverrides $overrides Line item data (order_item_name, order_item_type, meta array for item meta) * * @return int Order item ID */ @@ -404,8 +446,8 @@ public function dontSeeOrderItemMetaInDatabase(array $criteria): void * $I->seeOrderAddressInDatabase('billing', ['first_name' => 'John']); * ``` * - * @param string $type Address type ('billing' or 'shipping') - * @param array $criteria Database query criteria for address fields + * @param OrderAddressType $type Address type ('billing' or 'shipping') + * @param OrderAddress $criteria Database query criteria for address fields * * @return void */ @@ -442,8 +484,8 @@ public function grabOrderItemsTableName(): string * } * ``` * - * @param int $count Number of orders to create - * @param array $overrides Order data overrides applied to each order + * @param int $count Number of orders to create + * @param OrderOverrides $overrides Order data overrides applied to each order * * @return array Array of created order IDs */ diff --git a/src/WooCommerce/Method/ProductMethods.php b/src/WooCommerce/Method/ProductMethods.php index 3219854..fcce081 100644 --- a/src/WooCommerce/Method/ProductMethods.php +++ b/src/WooCommerce/Method/ProductMethods.php @@ -6,6 +6,36 @@ use lucatume\WPBrowser\Module\WPDb; +/** + * @phpstan-type ProductStatus 'publish'|'draft'|'pending'|'private'|'trash' + * @phpstan-type ProductStockStatus 'instock'|'outofstock'|'onbackorder' + * @phpstan-type ProductTaxStatus 'taxable'|'shipping'|'none' + * @phpstan-type ProductYesNo 'yes'|'no' + * @phpstan-type ProductBackorders 'no'|'notify'|'yes' + * @phpstan-type ProductMeta array{ + * _price?: string, + * _regular_price?: string, + * _sale_price?: string, + * _sku?: string, + * _stock_status?: ProductStockStatus, + * _tax_status?: ProductTaxStatus, + * _tax_class?: string, + * _manage_stock?: ProductYesNo, + * _backorders?: ProductBackorders, + * _sold_individually?: ProductYesNo, + * _virtual?: ProductYesNo, + * _downloadable?: ProductYesNo, + * ... + * } + * @phpstan-type ProductOverrides array{ + * post_type?: string, + * post_status?: ProductStatus, + * post_title?: string, + * post_name?: string, + * meta?: ProductMeta, + * ... + * } + */ trait ProductMethods { abstract protected function wpDb(): WPDb; @@ -78,7 +108,7 @@ public function haveProductCategoryRelationshipInDatabase(int $productId, int $c * ]); * ``` * - * @param array $overrides Post and meta data overrides. Meta is passed under the 'meta' key. + * @param ProductOverrides $overrides Post and meta data overrides. Meta is passed under the 'meta' key. * * @return int The created product post ID. */ @@ -387,16 +417,15 @@ public function dontSeeProductMetaInDatabase(array $criteria): void * ]); * ``` * - * @param int $count The number of products to create. - * @param array $overrides Post and meta data overrides. + * @param int $count The number of products to create. + * @param ProductOverrides $overrides Post and meta data overrides. * * @return array Array of created product post IDs. */ public function haveManyProductsInDatabase(int $count, array $overrides = []): array { $createdIds = []; - $baseTitleValue = $overrides['post_title'] ?? 'Product'; - $baseTitle = is_string($baseTitleValue) ? $baseTitleValue : 'Product'; + $baseTitle = $overrides['post_title'] ?? 'Product'; for ($i = 1; $i <= $count; $i++) { $productData = array_merge($overrides, [