Skip to content

Commit 7a50bcd

Browse files
[Feature] Custom resource controller actions (#12)
Adds support for registering custom controller actions on resources, even though these are not defined in the JSON:API spec. Refer to PR for examples.
1 parent 71f3eff commit 7a50bcd

File tree

17 files changed

+1073
-31
lines changed

17 files changed

+1073
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ All notable changes to this project will be documented in this file. This projec
77

88
### Added
99

10+
- [#12](https://github.com/laravel-json-api/laravel/pull/12) Can now register routes for custom actions on a resource,
11+
using the `actions()` helper method when registering resources. See the PR for examples.
1012
- The `JsonApiController` now has the Laravel `AuthorizesRequests`, `DispatchesJobs` and `ValidatesRequests` traits
1113
applied.
1214

src/Routing/ActionProxy.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
/*
3+
* Copyright 2021 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace LaravelJsonApi\Laravel\Routing;
21+
22+
use Illuminate\Routing\Route as IlluminateRoute;
23+
use Illuminate\Support\Traits\ForwardsCalls;
24+
25+
/**
26+
* Class ActionProxy
27+
*
28+
* @mixin IlluminateRoute
29+
*/
30+
class ActionProxy
31+
{
32+
33+
use ForwardsCalls;
34+
35+
/**
36+
* @var IlluminateRoute
37+
*/
38+
private IlluminateRoute $route;
39+
40+
/**
41+
* @var string
42+
*/
43+
private string $controllerMethod;
44+
45+
/**
46+
* @var bool
47+
*/
48+
private bool $named = false;
49+
50+
/**
51+
* ActionProxy constructor.
52+
*
53+
* @param IlluminateRoute $route
54+
* @param string $controllerMethod
55+
*/
56+
public function __construct(IlluminateRoute $route, string $controllerMethod)
57+
{
58+
$this->route = $route;
59+
$this->controllerMethod = $controllerMethod;
60+
}
61+
62+
/**
63+
* @param $name
64+
* @param $arguments
65+
*/
66+
public function __call($name, $arguments)
67+
{
68+
$this->forwardCallTo($this->route, $name, $arguments);
69+
}
70+
71+
/**
72+
* @return void
73+
*/
74+
public function __destruct()
75+
{
76+
if (false === $this->named) {
77+
$this->route->name($this->controllerMethod);
78+
}
79+
}
80+
81+
/**
82+
* @param string $name
83+
* @return $this
84+
*/
85+
public function name(string $name): self
86+
{
87+
$this->route->name($name);
88+
$this->named = true;
89+
90+
return $this;
91+
}
92+
93+
}

src/Routing/ActionRegistrar.php

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<?php
2+
/*
3+
* Copyright 2021 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace LaravelJsonApi\Laravel\Routing;
21+
22+
use Illuminate\Contracts\Routing\Registrar as RegistrarContract;
23+
use Illuminate\Routing\Route as IlluminateRoute;
24+
use Illuminate\Routing\RouteCollection;
25+
use LaravelJsonApi\Core\Support\Str;
26+
27+
class ActionRegistrar
28+
{
29+
30+
/**
31+
* @var RegistrarContract
32+
*/
33+
private RegistrarContract $router;
34+
35+
/**
36+
* @var ResourceRegistrar
37+
*/
38+
private ResourceRegistrar $resource;
39+
40+
/**
41+
* @var RouteCollection
42+
*/
43+
private RouteCollection $routes;
44+
45+
/**
46+
* @var string
47+
*/
48+
private string $resourceType;
49+
50+
/**
51+
* @var array
52+
*/
53+
private array $options;
54+
55+
/**
56+
* @var string
57+
*/
58+
private string $controller;
59+
60+
/**
61+
* @var string|null
62+
*/
63+
private ?string $prefix;
64+
65+
/**
66+
* @var bool
67+
*/
68+
private bool $id = false;
69+
70+
/**
71+
* ActionRegistrar constructor.
72+
*
73+
* @param RegistrarContract $router
74+
* @param ResourceRegistrar $resource
75+
* @param RouteCollection $routes
76+
* @param string $resourceType
77+
* @param array $options
78+
* @param string $controller
79+
* @param string|null $prefix
80+
*/
81+
public function __construct(
82+
RegistrarContract $router,
83+
ResourceRegistrar $resource,
84+
RouteCollection $routes,
85+
string $resourceType,
86+
array $options,
87+
string $controller,
88+
string $prefix = null
89+
) {
90+
$this->router = $router;
91+
$this->resource = $resource;
92+
$this->routes = $routes;
93+
$this->resourceType = $resourceType;
94+
$this->options = $options;
95+
$this->controller = $controller;
96+
$this->prefix = $prefix;
97+
}
98+
99+
/**
100+
* @return $this
101+
*/
102+
public function withId(): self
103+
{
104+
$copy = clone $this;
105+
$copy->id = true;
106+
107+
return $copy;
108+
}
109+
110+
/**
111+
* Register a new GET route.
112+
*
113+
* @param string $uri
114+
* @param string|null $method
115+
* @return ActionProxy
116+
*/
117+
public function get(string $uri, string $method = null): ActionProxy
118+
{
119+
return $this->register('get', $uri, $method);
120+
}
121+
122+
/**
123+
* Register a new POST route.
124+
*
125+
* @param string $uri
126+
* @param string|null $method
127+
* @return ActionProxy
128+
*/
129+
public function post(string $uri, string $method = null): ActionProxy
130+
{
131+
return $this->register('post', $uri, $method);
132+
}
133+
134+
/**
135+
* Register a new PATCH route.
136+
*
137+
* @param string $uri
138+
* @param string|null $method
139+
* @return ActionProxy
140+
*/
141+
public function patch(string $uri, string $method = null): ActionProxy
142+
{
143+
return $this->register('patch', $uri, $method);
144+
}
145+
146+
/**
147+
* Register a new PUT route.
148+
*
149+
* @param string $uri
150+
* @param string|null $method
151+
* @return ActionProxy
152+
*/
153+
public function put(string $uri, string $method = null): ActionProxy
154+
{
155+
return $this->register('put', $uri, $method);
156+
}
157+
158+
/**
159+
* Register a new DELETE route.
160+
*
161+
* @param string $uri
162+
* @param string|null $method
163+
* @return ActionProxy
164+
*/
165+
public function delete(string $uri, string $method = null): ActionProxy
166+
{
167+
return $this->register('delete', $uri, $method);
168+
}
169+
170+
/**
171+
* Register a new OPTIONS route.
172+
*
173+
* @param string $uri
174+
* @param string|null $method
175+
* @return ActionProxy
176+
*/
177+
public function options(string $uri, string $method = null): ActionProxy
178+
{
179+
return $this->register('options', $uri, $method);
180+
}
181+
182+
/**
183+
* @param string $method
184+
* @param string $uri
185+
* @param string|null $action
186+
* @return ActionProxy
187+
*/
188+
public function register(string $method, string $uri, string $action = null): ActionProxy
189+
{
190+
$action = $action ?: $this->guessControllerAction($uri);
191+
$parameter = $this->getParameter();
192+
193+
$route = $this->router->{$method}(
194+
$this->uri($uri, $parameter),
195+
sprintf('%s@%s', $this->controller, $action)
196+
);
197+
198+
$this->route($route, $parameter);
199+
200+
return new ActionProxy($route, $action);
201+
}
202+
203+
/**
204+
* @return string|null
205+
*/
206+
private function getParameter(): ?string
207+
{
208+
if ($this->id) {
209+
return $this->resource->getResourceParameterName(
210+
$this->resourceType,
211+
$this->options
212+
);
213+
}
214+
215+
return null;
216+
}
217+
218+
/**
219+
* Configure the supplied route.
220+
*
221+
* @param IlluminateRoute $route
222+
* @param string|null $parameter
223+
*/
224+
private function route(IlluminateRoute $route, ?string $parameter): void
225+
{
226+
$route->where($this->resource->getWheres(
227+
$this->resourceType,
228+
$parameter,
229+
$this->options
230+
));
231+
232+
$route->defaults(Route::RESOURCE_TYPE, $this->resourceType);
233+
234+
if ($parameter) {
235+
$route->defaults(Route::RESOURCE_ID_NAME, $parameter);
236+
}
237+
238+
$this->routes->add($route);
239+
}
240+
241+
/**
242+
* Normalize the URI.
243+
*
244+
* @param string $uri
245+
* @param string|null $parameter
246+
* @return string
247+
*/
248+
private function uri(string $uri, ?string $parameter): string
249+
{
250+
$uri = ltrim($uri, '/');
251+
252+
if ($this->prefix) {
253+
$uri = sprintf('%s/%s', $this->prefix, $uri);
254+
}
255+
256+
if ($this->id) {
257+
return sprintf('{%s}/%s', $parameter, $uri);
258+
}
259+
260+
return $uri;
261+
}
262+
263+
/**
264+
* @param string $uri
265+
* @return string
266+
*/
267+
private function guessControllerAction(string $uri): string
268+
{
269+
return Str::camel($uri);
270+
}
271+
}

0 commit comments

Comments
 (0)