Skip to content

Commit 7d9abfd

Browse files
authored
API Resources (#20710)
Simple way to transform models into JSON.
1 parent 60860f3 commit 7d9abfd

File tree

18 files changed

+1406
-9
lines changed

18 files changed

+1406
-9
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Illuminate\Contracts\Database;
4+
5+
interface CastsToResource
6+
{
7+
/**
8+
* Cast the given model into a resource.
9+
*
10+
* @param \Illuminate\Http\Request $request
11+
* @param mixed $model
12+
* @return mixed
13+
*/
14+
public static function castToResource($request, $model);
15+
16+
/**
17+
* Cast the given paginator or collection into a resource.
18+
*
19+
* @param \Illuminate\Http\Request $request
20+
* @param mixed $collection
21+
* @return mixed
22+
*/
23+
public static function castCollectionToResource($request, $collection);
24+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Eloquent;
4+
5+
use Exception;
6+
7+
trait DetectsResource
8+
{
9+
/**
10+
* Cast the given model into a resource.
11+
*
12+
* @param \Illuminate\Http\Request $request
13+
* @param mixed $model
14+
* @return mixed
15+
*/
16+
public static function castToResource($request, $model)
17+
{
18+
$class = $model->detectResourceName();
19+
20+
return new $class($model);
21+
}
22+
23+
/**
24+
* Cast the given paginator or collection into a resource.
25+
*
26+
* @param \Illuminate\Http\Request $request
27+
* @param \Illuminate\Support\Collection $collection
28+
* @return mixed
29+
*/
30+
public static function castCollectionToResource($request, $collection)
31+
{
32+
$class = $collection->first()->detectResourceName('Collection');
33+
34+
return new $class($collection);
35+
}
36+
37+
/**
38+
* Detect the resource name for the model.
39+
*
40+
* @param string $suffix
41+
* @return string
42+
*/
43+
public function detectResourceName($suffix = '')
44+
{
45+
$segments = explode('\\', get_class($this));
46+
47+
$base = array_pop($segments);
48+
49+
if (class_exists($class = implode('\\', $segments).'\\Http\\Resources\\'.$base.$suffix)) {
50+
return $class;
51+
}
52+
53+
throw new Exception(
54+
"Unable to detect the resource for the [".get_class($this)."] model."
55+
);
56+
}
57+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
namespace Illuminate\Foundation\Console;
4+
5+
use Illuminate\Support\Str;
6+
use Illuminate\Console\GeneratorCommand;
7+
use Symfony\Component\Console\Input\InputOption;
8+
9+
class ResourceMakeCommand extends GeneratorCommand
10+
{
11+
/**
12+
* The console command name.
13+
*
14+
* @var string
15+
*/
16+
protected $name = 'make:resource';
17+
18+
/**
19+
* The console command description.
20+
*
21+
* @var string
22+
*/
23+
protected $description = 'Create a new resource';
24+
25+
/**
26+
* The type of class being generated.
27+
*
28+
* @var string
29+
*/
30+
protected $type = 'Resource';
31+
32+
/**
33+
* Execute the console command.
34+
*
35+
* @return bool|null
36+
*/
37+
public function handle()
38+
{
39+
if ($this->collection()) {
40+
$this->type = 'Resource collection';
41+
}
42+
43+
parent::handle();
44+
}
45+
46+
/**
47+
* Get the stub file for the generator.
48+
*
49+
* @return string
50+
*/
51+
protected function getStub()
52+
{
53+
return $this->collection()
54+
? __DIR__.'/stubs/resource-collection.stub'
55+
: __DIR__.'/stubs/resource.stub';
56+
}
57+
58+
/**
59+
* Determine if the command is generating a resource collection.
60+
*
61+
* @return bool
62+
*/
63+
protected function collection()
64+
{
65+
return $this->option('collection') ||
66+
Str::endsWith($this->argument('name'), 'Collection');
67+
}
68+
69+
/**
70+
* Get the default namespace for the class.
71+
*
72+
* @param string $rootNamespace
73+
* @return string
74+
*/
75+
protected function getDefaultNamespace($rootNamespace)
76+
{
77+
return $rootNamespace.'\Http\Resources';
78+
}
79+
80+
/**
81+
* Get the console command options.
82+
*
83+
* @return array
84+
*/
85+
protected function getOptions()
86+
{
87+
return [
88+
['collection', 'c', InputOption::VALUE_NONE, 'Create a resource collection.'],
89+
];
90+
}
91+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace DummyNamespace;
4+
5+
use Illuminate\Http\Resources\Json\ResourceCollection;
6+
7+
class DummyClass extends ResourceCollection
8+
{
9+
/**
10+
* Transform the resource collection into an array.
11+
*
12+
* @param \Illuminate\Http\Request
13+
* @return array
14+
*/
15+
public function toArray($request)
16+
{
17+
return parent::toArray($request);
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace DummyNamespace;
4+
5+
use Illuminate\Http\Resources\Json\Resource;
6+
7+
class DummyClass extends Resource
8+
{
9+
/**
10+
* Transform the resource into an array.
11+
*
12+
* @param \Illuminate\Http\Request
13+
* @return array
14+
*/
15+
public function toArray($request)
16+
{
17+
return parent::toArray($request);
18+
}
19+
}

src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use Illuminate\Foundation\Console\StorageLinkCommand;
3838
use Illuminate\Routing\Console\ControllerMakeCommand;
3939
use Illuminate\Routing\Console\MiddlewareMakeCommand;
40+
use Illuminate\Foundation\Console\ResourceMakeCommand;
4041
use Illuminate\Foundation\Console\ListenerMakeCommand;
4142
use Illuminate\Foundation\Console\ProviderMakeCommand;
4243
use Illuminate\Foundation\Console\ClearCompiledCommand;
@@ -145,6 +146,7 @@ class ArtisanServiceProvider extends ServiceProvider
145146
'QueueFailedTable' => 'command.queue.failed-table',
146147
'QueueTable' => 'command.queue.table',
147148
'RequestMake' => 'command.request.make',
149+
'ResourceMake' => 'command.resource.make',
148150
'RuleMake' => 'command.rule.make',
149151
'SeederMake' => 'command.seeder.make',
150152
'SessionTable' => 'command.session.table',
@@ -727,6 +729,18 @@ protected function registerRequestMakeCommand()
727729
});
728730
}
729731

732+
/**
733+
* Register the command.
734+
*
735+
* @return void
736+
*/
737+
protected function registerResourceMakeCommand()
738+
{
739+
$this->app->singleton('command.resource.make', function ($app) {
740+
return new ResourceMakeCommand($app['files']);
741+
});
742+
}
743+
730744
/**
731745
* Register the command.
732746
*
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Illuminate\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Support\Collection;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Pagination\AbstractPaginator;
9+
use Illuminate\Contracts\Database\CastsToResource;
10+
11+
class CastToResource
12+
{
13+
/**
14+
* Handle an incoming request.
15+
*
16+
* @param \Illuminate\Http\Request $request
17+
* @param \Closure $next
18+
* @return mixed
19+
*/
20+
public function handle($request, Closure $next)
21+
{
22+
$response = $next($request);
23+
24+
if (! isset($response->original)) {
25+
return $response;
26+
}
27+
28+
if ($response->original instanceof Model &&
29+
$response->original instanceof CastsToResource) {
30+
return $this->castModelToResource($request, $response);
31+
}
32+
33+
if ($response->original instanceof Collection &&
34+
$response->original->first() instanceof CastsToResource) {
35+
return $this->castCollectionToResource($request, $response);
36+
}
37+
38+
if ($response->original instanceof AbstractPaginator &&
39+
$response->original->getCollection()->first() instanceof CastsToResource) {
40+
return $this->castPaginatorToResource($request, $response);
41+
}
42+
43+
return $response;
44+
}
45+
46+
/**
47+
* Cast the model sent within the response to a resource response.
48+
*
49+
* @param \Illuminate\Http\Request $request
50+
* @param \Illuminate\Http\Response $response
51+
* @return \Illuminate\Http\Response
52+
*/
53+
protected function castModelToResource($request, $response)
54+
{
55+
return $response->original->castToResource(
56+
$request, $response->original
57+
)->toResponse($request);
58+
}
59+
60+
/**
61+
* Cast the collection sent within the response to a resource response.
62+
*
63+
* @param \Illuminate\Http\Request $request
64+
* @param \Illuminate\Http\Response $response
65+
* @return \Illuminate\Http\Response
66+
*/
67+
protected function castCollectionToResource($request, $response)
68+
{
69+
return $response->original->first()->castCollectionToResource(
70+
$request, $response->original
71+
)->toResponse($request);
72+
}
73+
74+
/**
75+
* Cast the collection sent within the response to a resource response.
76+
*
77+
* @param \Illuminate\Http\Request $request
78+
* @param \Illuminate\Http\Response $response
79+
* @return \Illuminate\Http\Response
80+
*/
81+
protected function castPaginatorToResource($request, $response)
82+
{
83+
$collection = $response->original->getCollection();
84+
85+
return $collection->first()->castCollectionToResource(
86+
$request, $response->original
87+
)->toResponse($request);
88+
}
89+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Illuminate\Http\Resources;
4+
5+
use Illuminate\Support\Str;
6+
use Illuminate\Support\Collection;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Pagination\AbstractPaginator;
9+
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
10+
11+
trait CollectsResources
12+
{
13+
/**
14+
* Map the given collection resource into its individual resources.
15+
*
16+
* @param mixed $resource
17+
* @return mixed
18+
*/
19+
protected function collectResource($resource)
20+
{
21+
$this->collection = $resource->mapInto($this->collects());
22+
23+
return $resource instanceof AbstractPaginator
24+
? $resource->setCollection($this->collection)
25+
: $this->collection;
26+
}
27+
28+
/**
29+
* Get the resource that this resource collects.
30+
*
31+
* @return string
32+
*/
33+
protected function collects()
34+
{
35+
if ($this->collects) {
36+
return $this->collects;
37+
}
38+
39+
if (Str::endsWith(class_basename($this), 'Collection') &&
40+
class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) {
41+
return $class;
42+
}
43+
44+
throw new UnknownCollectionException($this);
45+
}
46+
47+
/**
48+
* Get an iterator for the resource collection.
49+
*
50+
* @return \ArrayIterator
51+
*/
52+
public function getIterator()
53+
{
54+
return $this->collection->getIterator();
55+
}
56+
}

0 commit comments

Comments
 (0)