From c15d1f9475bb51768a8eccb9013d9307bdd42ef7 Mon Sep 17 00:00:00 2001 From: Fabian Wagner Date: Sun, 17 May 2026 12:48:44 +0200 Subject: [PATCH] A variable pagination --- src/Attributes/VariablePaginatable.php | 43 +++++++++++++++++++++ src/WorkkitServiceProvider.php | 52 ++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/Attributes/VariablePaginatable.php diff --git a/src/Attributes/VariablePaginatable.php b/src/Attributes/VariablePaginatable.php new file mode 100644 index 0000000..37065e4 --- /dev/null +++ b/src/Attributes/VariablePaginatable.php @@ -0,0 +1,43 @@ +paginate()`: + * + * #[VariablePaginatable] → 25, user can override (1..100) + * #[VariablePaginatable(50)] → 50, user can override (1..100) + * #[VariablePaginatable(10, allowUserOverride: false)] → fixed at 10, no `?per_page=` + * #[VariablePaginatable(50, max: 200)] → 50, user can override (1..200) + * + * Without the attribute, `Request::perPage()` falls back to its $fallback + * argument (15 by default — matches Eloquent's model default). + * + * Usage: + * + * #[VariablePaginatable(50)] + * public function index(Request $request): array + * { + * return ResponseService::apiPaginated( + * Book::query()->paginate($request->perPage()), + * BookResource::class, + * ); + * } + */ +#[Attribute(Attribute::TARGET_METHOD)] +final class VariablePaginatable +{ + public function __construct( + public int $default = 25, + public bool $allowUserOverride = true, + public int $max = 100, + ) { + } +} diff --git a/src/WorkkitServiceProvider.php b/src/WorkkitServiceProvider.php index 8565f06..7e2aaf1 100644 --- a/src/WorkkitServiceProvider.php +++ b/src/WorkkitServiceProvider.php @@ -2,10 +2,14 @@ namespace Blax\Workkit; +use Blax\Workkit\Attributes\VariablePaginatable; use Blax\Workkit\Commands\Database\BackupCommand; use Blax\Workkit\Commands\Database\PruneBackupsCommand; use Blax\Workkit\Commands\Database\RestoreCommand; use Blax\Workkit\Commands\PlugNPrayCommand; +use Illuminate\Http\Request; +use ReflectionException; +use ReflectionMethod; class WorkkitServiceProvider extends \Illuminate\Support\ServiceProvider { @@ -26,6 +30,8 @@ class WorkkitServiceProvider extends \Illuminate\Support\ServiceProvider */ public function boot() { + $this->registerPerPageMacro(); + if ($this->app->runningInConsole()) { $this->commands([ PlugNPrayCommand::class, @@ -41,4 +47,50 @@ class WorkkitServiceProvider extends \Illuminate\Support\ServiceProvider ], 'workkit-config'); } } + + /** + * Resolve the effective page size for the current route via the + * {@see VariablePaginatable} attribute on the controller method. + * + * Order of resolution: + * 1. No route / closure action / no attribute → $fallback (default 15) + * 2. Attribute present, allowUserOverride=true → clamp `?per_page=N` + * into `[1, max]`, defaulting to `default` when the query is missing. + * 3. Attribute present, allowUserOverride=false → `default` (ignores query). + */ + private function registerPerPageMacro(): void + { + Request::macro('perPage', function (int $fallback = 15): int { + /** @var Request $this */ + $route = $this->route(); + $controller = is_object($route?->getController()) ? $route->getController()::class : null; + $action = is_string($route?->getActionMethod()) ? $route->getActionMethod() : null; + + if (! $controller || ! $action) { + return $fallback; + } + + try { + $reflection = new ReflectionMethod($controller, $action); + } catch (ReflectionException) { + return $fallback; + } + + $attributes = $reflection->getAttributes(VariablePaginatable::class); + if ($attributes === []) { + return $fallback; + } + + /** @var VariablePaginatable $config */ + $config = $attributes[0]->newInstance(); + + if (! $config->allowUserOverride) { + return $config->default; + } + + $requested = (int) $this->query('per_page', (string) $config->default); + + return min(max($requested, 1), $config->max); + }); + } }