Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,32 +167,37 @@ class UnsetSessionMiddleware
```
## RESTful Resources

A single line registers all standard CRUD routes:
Registers standard CRUD routes with explicit plural and singular URL patterns.
No magic pluralization — you define exactly what the URLs look like.

```php
$router->resource('api/users', UserController::class);
$router->resource('api/users', 'api/user', UserController::class);
```

This creates the following routes:

| Method | URL | Controller Method |
|--------|-----------|-------------------|
| GET | api/users | read |
| POST | api/users | create |
| PUT | api/users | update |
| PATCH | api/users | update |
| DELETE | api/users | delete |

| Method | URL | Controller Method | Description |
|--------|-----|-------------------|-------------|
| GET | api/users | index | List all users |
| GET | api/user/:id | read | Get single user |
| POST | api/users | create | Create new user |
| PUT | api/user/:id | update | Full update user |
| PATCH | api/user/:id | update | Partial update user |
| DELETE | api/user/:id | delete | Delete user |
>The default action names are [index, read, create, update, delete].
### Custom Method Names
You can override the default action names by passing a custom array of 5 methods:

```php
$router->resource('api/posts', PostController::class, [
'actionIndex',
'actionAdd',
'actionUpdate',
'actionDrop'
$router->resource('api/posts', 'api/post', PostController::class, [
'actionIndex', // GET api/posts — list all posts
'actionView', // GET api/post/:id — get single post
'actionAdd', // POST api/posts — create new post
'actionUpdate', // PUT/PATCH api/post/:id — update post
'actionDrop' // DELETE api/post/:id — delete post
]);
```
>The array order is fixed: [index, read, create, update, delete].
## The set() Method — Extended Syntax

Allows defining a route with multiple HTTP methods via `|`:
Expand Down
108 changes: 64 additions & 44 deletions src/Traits/RouterRequestMethodTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,53 +65,73 @@ public function any(string $pattern, array|callable $target, array $middleware =
}

/**
* Registers a resource route, mapping standard actions to controller methods.
* Registers RESTful resource routes with explicit plural and singular URL patterns.
*
* Supports common CRUD operations by default:
* - GET => read
* - POST => create
* - PUT => update
* - DELETE => delete
* No magic pluralization — you define exactly what the URLs look like.
*
* Can be customized with an optional $actions array.
* Creates the following routes:
* - GET {plural} => actions[0] (index — list all)
* - GET {singular}/:id => actions[1] (read — get single)
* - POST {plural} => actions[2] (create)
* - PUT {singular}/:id => actions[3] (full update)
* - PATCH {singular}/:id => actions[3] (partial update)
* - DELETE {singular}/:id => actions[4] (delete)
*/
public function resource(string $pattern, string $controller, array $actions = ['read', 'create', 'update', 'delete']): void
{
$request = $this->rudra->request();
$server = $request->server();
$post = $request->post();

$requestMethod = $server->get('REQUEST_METHOD');
$httpMethod = $requestMethod === 'POST' && $post->has('_method')
? strtoupper($post->get('_method'))
: $requestMethod;

switch ($httpMethod) {
case 'GET':
$route['method'] = 'GET';
$route['action'] = $actions[0]; // read
break;
case 'POST':
$route['method'] = 'POST';
$route['action'] = $actions[1]; // create
break;
case 'PUT':
case 'PATCH':
$route['method'] = $httpMethod;
$route['action'] = $actions[2]; // update
break;
case 'DELETE':
$route['method'] = 'DELETE';
$route['action'] = $actions[3]; // delete
break;
default:
return; // Unknown method — ignore
}

$route['url'] = $pattern;
$route['controller'] = $controller;

$this->set($route);
public function resource(string $plural, string $singular, string $controller,
array $actions = ['index', 'read', 'create', 'update', 'delete']
): void {
[$index, $read, $create, $update, $delete] = $actions;

$plural = ltrim($plural, '/');
$singular = ltrim($singular, '/');

// GET api/users => index
$this->set([
'method' => 'GET',
'url' => $plural,
'controller' => $controller,
'action' => $index,
]);

// GET api/user/:id => read
$this->set([
'method' => 'GET',
'url' => $singular . '/:id',
'controller' => $controller,
'action' => $read,
]);

// POST api/users => create
$this->set([
'method' => 'POST',
'url' => $plural,
'controller' => $controller,
'action' => $create,
]);

// PUT api/user/:id => update
$this->set([
'method' => 'PUT',
'url' => $singular . '/:id',
'controller' => $controller,
'action' => $update,
]);

// PATCH api/user/:id => update
$this->set([
'method' => 'PATCH',
'url' => $singular . '/:id',
'controller' => $controller,
'action' => $update,
]);

// DELETE api/user/:id => delete
$this->set([
'method' => 'DELETE',
'url' => $singular . '/:id',
'controller' => $controller,
'action' => $delete,
]);
}

/**
Expand Down
85 changes: 35 additions & 50 deletions tests/RouterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,75 +84,60 @@ public function testAny(): void
$this->assertEquals("ANY", Rudra::config()->get("actionAny"));
}

protected function setRouteResourceEnvironment(string $requestMethod, string $action): void
{
$_SERVER["REQUEST_URI"] = "api/123";
protected function setRouteResourceEnvironment(
string $requestMethod,
string $action,
string $requestUri
): void {
$_SERVER["REQUEST_URI"] = $requestUri;
$_SERVER["REQUEST_METHOD"] = $requestMethod;
$this->setContainer();
Router::resource("api/:id", MainController::class);
$this->assertEquals($action, Rudra::config()->get($action));
}

public function testResource(): void
{
$this->setRouteResourceEnvironment("GET", "read");
$this->setRouteResourceEnvironment("POST", "create");
$this->setRouteResourceEnvironment("PUT", "update");
$this->setRouteResourceEnvironment("DELETE", "delete");
}
Router::resource(
"api/users",
"api/user",
MainController::class,
['index', 'read', 'create', 'update', 'delete']
);

protected function setRouteResourcePostEnvironment(string $requestMethod, string $action): void
{
$_SERVER["REQUEST_URI"] = "api/123";
$_SERVER["REQUEST_METHOD"] = "POST";
$_POST["_method"] = $requestMethod;
$this->setContainer();
Router::resource("api/:id", MainController::class);
$this->assertEquals($action, Rudra::config()->get($action));
}

public function testResourcePost(): void
public function testResource(): void
{
$this->setRouteResourcePostEnvironment("DELETE", "delete");
$this->setRouteResourcePostEnvironment("PUT", "update");
$this->setRouteResourcePostEnvironment("PATCH", "update");
// Collection routes (plural URL, no :id)
$this->setRouteResourceEnvironment("GET", "index", "api/users");
$this->setRouteResourceEnvironment("POST", "create", "api/users");

// Single-item routes (singular URL + /:id)
$this->setRouteResourceEnvironment("GET", "read", "api/user/123");
$this->setRouteResourceEnvironment("PUT", "update", "api/user/123");
$this->setRouteResourceEnvironment("PATCH", "update", "api/user/123");
$this->setRouteResourceEnvironment("DELETE", "delete", "api/user/123");
}

protected function setRoutePostEnvironment(string $requestMethod, string $action): void
protected function setRouteResourcePostEnvironment(string $spoofedMethod, string $action): void
{
$_SERVER["REQUEST_URI"] = "api/123";
$_SERVER["REQUEST_URI"] = "api/user/123";
$_SERVER["REQUEST_METHOD"] = "POST";
$_POST["_method"] = $requestMethod;
$_POST["_method"] = $spoofedMethod;
$this->setContainer();

$method = strtolower($requestMethod);
Router::resource(
"api/users",
"api/user",
MainController::class,
['index', 'read', 'create', 'update', 'delete']
);

Router::resource("api/:id", MainController::class);
$this->assertEquals($action, Rudra::config()->get($action));
}

public function testPostMethods(): void
{
$this->setRoutePostEnvironment("DELETE", "delete");
$this->setRoutePostEnvironment("PUT", "update");
$this->setRoutePostEnvironment("PATCH", "update");
}

public function testMiddleware()
public function testResourcePost(): void
{
$_SERVER["REQUEST_URI"] = "123/456";
$_SERVER["REQUEST_METHOD"] = "GET";
$this->setContainer();

Router::get("123/:id", [MainController::class,'read'],
[
"before" => [[Middleware::class]],
"after" => [function () { Rudra::config()->set(["after" => "after"]); }]
]
);

$this->assertEquals(Middleware::class, Rudra::config()->get("middleware"));
$this->assertEquals("after", Rudra::config()->get("after"));
$this->setRouteResourcePostEnvironment("PUT", "update");
$this->setRouteResourcePostEnvironment("PATCH", "update");
$this->setRouteResourcePostEnvironment("DELETE", "delete");
}

public function testClosure()
Expand Down
6 changes: 6 additions & 0 deletions tests/Stub/Controllers/MainController.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ public function actionAny()
Rudra::config()->set(["actionAny" => "ANY"]);
}

public function index()
{
Rudra::config()->set(["index" => "index"]);
}


public function read($params = null)
{
Rudra::config()->set(["read" => "read"]);
Expand Down
Loading