Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
Router
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 crud
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 shouldRegisterAction
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace DevToolbelt\LaravelFastCrud;
6
7use Illuminate\Support\Facades\Route;
8
9/**
10 * Route registration helper for CRUD controllers.
11 *
12 * Provides a convenient method to register all RESTful routes for a CRUD controller
13 * with automatic permission middleware assignment.
14 *
15 * @example
16 * ```php
17 * // Register all CRUD routes for products
18 * Router::crud('products', ProductController::class, 'products');
19 *
20 * // Exclude specific actions
21 * Router::crud('categories', CategoryController::class, 'categories', except: ['delete', 'exportCsv']);
22 *
23 * // Include only specific actions
24 * Router::crud('tags', TagController::class, 'tags', only: ['search', 'read']);
25 * ```
26 */
27final class Router extends Route
28{
29    /**
30     * Default CRUD action definitions.
31     *
32     * @var array<int, array{verb: string, path: string, method: string, permission: string}>
33     */
34    private const CRUD_ACTIONS = [
35        ['verb' => 'get', 'path' => '', 'method' => 'search', 'permission' => 'search'],
36        ['verb' => 'get', 'path' => '/options', 'method' => 'options', 'permission' => 'search'],
37        ['verb' => 'post', 'path' => '', 'method' => 'create', 'permission' => 'create'],
38        ['verb' => 'get', 'path' => '/export-csv', 'method' => 'exportCsv', 'permission' => 'exportCsv'],
39        ['verb' => 'get', 'path' => '/{id:uuid}', 'method' => 'read', 'permission' => 'view'],
40        ['verb' => 'put', 'path' => '/{id:uuid}', 'method' => 'update', 'permission' => 'update'],
41        ['verb' => 'patch', 'path' => '/{id:uuid}', 'method' => 'update', 'permission' => 'update'],
42        ['verb' => 'post', 'path' => '/{id:uuid}', 'method' => 'update', 'permission' => 'update'],
43        ['verb' => 'delete', 'path' => '/{id:uuid}', 'method' => 'delete', 'permission' => 'delete'],
44        ['verb' => 'delete', 'path' => '/{id:uuid}/soft', 'method' => 'softDelete', 'permission' => 'delete'],
45        ['verb' => 'patch', 'path' => '/{id:uuid}/restore', 'method' => 'restore', 'permission' => 'restore'],
46        ['verb' => 'put', 'path' => '/{id:uuid}/restore', 'method' => 'restore', 'permission' => 'restore'],
47    ];
48
49    /**
50     * Register CRUD routes for a controller with permission middleware.
51     *
52     * Generates the following routes:
53     * - GET /{uri} -> search() with {module}.access.search permission
54     * - GET /{uri}/options -> options() with {module}.access.search permission
55     * - POST /{uri} -> create() with {module}.access.create permission
56     * - GET /{uri}/export-csv -> exportCsv() with {module}.access.exportCsv permission
57     * - GET /{uri}/{id:uuid} -> read() with {module}.access.view permission
58     * - PUT|PATCH|POST /{uri}/{id:uuid} -> update() with {module}.access.update permission
59     * - DELETE /{uri}/{id:uuid} -> delete() with {module}.access.delete permission
60     * - DELETE /{uri}/{id:uuid}/soft -> softDelete() with {module}.access.delete permission
61     * - PUT|PATCH /{uri}/{id:uuid}/restore -> restore() with {module}.access.restore permission
62     *
63     * @param string $uri Base URI for the resource (e.g., 'products', 'api/v1/users')
64     * @param class-string $controllerName Fully qualified controller class name
65     * @param string|null $moduleName Module name for permission middleware (e.g., 'products').
66     *        If null, no permission middleware is applied
67     * @param array<int, string> $except Action methods to exclude (e.g., ['delete', 'exportCsv'])
68     * @param array<int, string> $only If provided, only these action methods will be registered
69     */
70    public static function crud(
71        string $uri,
72        string $controllerName,
73        ?string $moduleName = null,
74        array $except = [],
75        array $only = []
76    ): void {
77        foreach (self::CRUD_ACTIONS as $action) {
78            $verb = $action['verb'];
79            $path = $uri . $action['path'];
80            $method = $action['method'];
81            $permission = $action['permission'];
82
83            if (!method_exists($controllerName, $method)) {
84                continue;
85            }
86
87            if (!self::shouldRegisterAction($method, $only, $except)) {
88                continue;
89            }
90
91            $route = static::$verb($path, "{$controllerName}@{$method}");
92
93            if ($moduleName !== null) {
94                $route->middleware("can:{$moduleName}.access.{$permission}");
95            }
96        }
97    }
98
99    /**
100     * Determines if an action should be registered based on only/except filters.
101     *
102     * @param string $method The action method name
103     * @param array<int, string> $only Actions to include (if not empty, only these are registered)
104     * @param array<int, string> $except Actions to exclude
105     */
106    private static function shouldRegisterAction(string $method, array $only, array $except): bool
107    {
108        if (!empty($only)) {
109            return in_array($method, $only, true);
110        }
111
112        return !in_array($method, $except, true);
113    }
114}