Connection.php
2 months ago
Edge.php
2 months ago
IdCursorFilter.php
2 months ago
PageInfo.php
2 months ago
PaginationParams.php
2 months ago
PaginationParams.php
119 lines
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Automattic\WooCommerce\Api\Pagination; |
| 6 | |
| 7 | use Automattic\WooCommerce\Api\Attributes\Description; |
| 8 | use Automattic\WooCommerce\Api\Attributes\Unroll; |
| 9 | |
| 10 | /** |
| 11 | * Standard pagination parameters for connection queries. |
| 12 | * |
| 13 | * Because this class carries #[Unroll], whenever it is used as an execute() |
| 14 | * parameter the builder expands its properties into individual GraphQL arguments. |
| 15 | */ |
| 16 | #[Unroll] |
| 17 | class PaginationParams { |
| 18 | /** |
| 19 | * Maximum number of items a client may request in a single page. |
| 20 | * |
| 21 | * Requests with `first` or `last` above this value are rejected with an |
| 22 | * INVALID_ARGUMENT error, matching the behavior of common GraphQL APIs |
| 23 | * (e.g. GitHub's 100-item cap). |
| 24 | */ |
| 25 | public const MAX_PAGE_SIZE = 100; |
| 26 | |
| 27 | /** |
| 28 | * Page size used when neither `first` nor `last` is provided. |
| 29 | */ |
| 30 | public const DEFAULT_PAGE_SIZE = 100; |
| 31 | |
| 32 | /** |
| 33 | * Constructor. |
| 34 | * |
| 35 | * @param ?int $first Return the first N results. |
| 36 | * @param ?int $last Return the last N results. |
| 37 | * @param ?string $after Return results after this cursor. |
| 38 | * @param ?string $before Return results before this cursor. |
| 39 | * |
| 40 | * @throws \InvalidArgumentException When `first` or `last` is negative or exceeds MAX_PAGE_SIZE. |
| 41 | */ |
| 42 | public function __construct( |
| 43 | #[Description( 'Return the first N results. Must be between 0 and ' . self::MAX_PAGE_SIZE . '.' )] |
| 44 | public readonly ?int $first = null, |
| 45 | #[Description( 'Return the last N results. Must be between 0 and ' . self::MAX_PAGE_SIZE . '.' )] |
| 46 | public readonly ?int $last = null, |
| 47 | #[Description( 'Return results after this cursor.' )] |
| 48 | public readonly ?string $after = null, |
| 49 | #[Description( 'Return results before this cursor.' )] |
| 50 | public readonly ?string $before = null, |
| 51 | ) { |
| 52 | self::validate_limit( 'first', $first ); |
| 53 | self::validate_limit( 'last', $last ); |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * The page size to use when no explicit `first` or `last` is provided. |
| 58 | * |
| 59 | * Exposed as a method (not just the constant) so the default can become |
| 60 | * configurable — e.g. via a filter or store option — without requiring |
| 61 | * call-site changes. |
| 62 | * |
| 63 | * @return int |
| 64 | */ |
| 65 | public static function get_default_page_size(): int { |
| 66 | return self::DEFAULT_PAGE_SIZE; |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Validate pagination limits on a raw args array without constructing a |
| 71 | * full PaginationParams instance. |
| 72 | * |
| 73 | * Intended for call sites that take raw GraphQL args (like nested |
| 74 | * connection resolvers) and forward them to Connection::slice(). The |
| 75 | * constructor already runs the same checks for root queries that build |
| 76 | * a PaginationParams via #[Unroll], so this keeps both paths in sync. |
| 77 | * |
| 78 | * @param array $args Raw args with optional `first` / `last` keys. |
| 79 | * |
| 80 | * @throws \InvalidArgumentException When either limit is negative or above MAX_PAGE_SIZE. |
| 81 | */ |
| 82 | public static function validate_args( array $args ): void { |
| 83 | self::validate_limit( 'first', $args['first'] ?? null ); |
| 84 | self::validate_limit( 'last', $args['last'] ?? null ); |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Validate a `first` / `last` argument. |
| 89 | * |
| 90 | * @param string $name The argument name, for the error message. |
| 91 | * @param ?int $value The value to validate. |
| 92 | * |
| 93 | * @throws \InvalidArgumentException When the value is out of range. |
| 94 | */ |
| 95 | private static function validate_limit( string $name, ?int $value ): void { |
| 96 | if ( null === $value ) { |
| 97 | return; |
| 98 | } |
| 99 | |
| 100 | // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Not HTML output; serialized as JSON in the GraphQL error response. |
| 101 | if ( $value < 0 ) { |
| 102 | throw new \InvalidArgumentException( |
| 103 | sprintf( 'Argument `%s` must be zero or greater.', $name ) |
| 104 | ); |
| 105 | } |
| 106 | |
| 107 | if ( $value > self::MAX_PAGE_SIZE ) { |
| 108 | throw new \InvalidArgumentException( |
| 109 | sprintf( |
| 110 | 'Argument `%s` exceeds the maximum page size of %d.', |
| 111 | $name, |
| 112 | self::MAX_PAGE_SIZE |
| 113 | ) |
| 114 | ); |
| 115 | } |
| 116 | // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped |
| 117 | } |
| 118 | } |
| 119 |