我最近在 Laravel Brasil 社区看到一个问题,结果比看起来更有趣。想象一下你有一个 UsersResource
用下面的实现:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UsersResource extends Resource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email
];
}
}
出于某种原因,您可能希望在另一个端点上重新使用该资源类,但隐藏email
字段。这篇文章就是告诉你如何实现这一点的。
如果你不知道 API Resources
是什么,请查看我之前关于这个的文章。
1- Setting up the project
The interesting stuff start at section 3.
composer create-project --prefer-dist laravel/laravel api-fields
cd api-fields
touch database/database.sqlite
Edit your .env
file to remove database settings and use SQLite
DB_CONNECTION=sqlite
Continue setting up the project
php artisan migrate
php artisan make:resource UsersResource
php artisan make:resource --collection UsersResourceCollection
php artisan make:controller UsersController
php artisan tinker
factory(App\User::class)->times(20)->create();
quit
2- The Routes
Make sure to create a route in the api.php
file.
Route::apiResource('/users', 'UsersController');
3- The Controller
The Controller represents the desired goal. In this example let's suppose in the listing we only want the name of all users whereas in the show we want to hide only the email address.
<?php
namespace App\Http\Controllers;
use App\Http\Resources\UsersResource;
use App\User;
class UsersController extends Controller
{
/**
* Display a listing of the resource.
*
* @param User $user
* @return \Illuminate\Http\Response
*/
public function index(User $user)
{
return UsersResource::collection($user->paginate())->hide(['id', 'email']);
}
/**
* Display a user.
*
* @param User $user
* @return \Illuminate\Http\Response
*/
public function show(User $user)
{
return UsersResource::make($user)->hide(['id']);
}
}
In order to achieve this, we need both our UsersResourceCollection
and our UsersResource
to know how to handle the hide
call.
4- The UsersResource Class
Let's start with the show
method. The UsersResource::make
will return an object of UsersResource
. As such, we should expose a method hide
that stores the desired keys to be removed from the response.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UsersResource extends Resource
{
/**
* @var array
*/
protected $withoutFields = [];
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return $this->filterFields([
'id' => $this->id,
'name' => $this->name,
'email' => $this->email
]);
}
/**
* Set the keys that are supposed to be filtered out.
*
* @param array $fields
* @return $this
*/
public function hide(array $fields)
{
$this->withoutFields = $fields;
return $this;
}
/**
* Remove the filtered keys.
*
* @param $array
* @return array
*/
protected function filterFields($array)
{
return collect($array)->forget($this->withoutFields)->toArray();
}
}
Done! At this point we should be able to access http://api.dev/api/users/1
and se a response without the id
field.
{
"data": {
"name": "Mr. Frederik Morar",
"email": "darryl.wilkinson@example.org"
}
}
5- The UsersResourceCollection Class
For a collection of items to work on the index
method, we need to perform a few changes:
- (1) Make sure
UsersResource::collection
returns an instance ofUsersResourceCollection
- (2) Expose the
hide
method onUsersResourceCollection
- (3) Pass through the hidden fields to
UsersResource
For (1), we just need to override the collection
method on UsersResource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UsersResource extends Resource
{
public static function collection($resource)
{
return tap(new UsersResourceCollection($resource), function ($collection) {
$collection->collects = __CLASS__;
});
}
/**
* @var array
*/
protected $withoutFields = [];
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return $this->filterFields([
'id' => $this->id,
'name' => $this->name,
'email' => $this->email
]);
}
/**
* Set the keys that are supposed to be filtered out.
*
* @param array $fields
* @return $this
*/
public function hide(array $fields)
{
$this->withoutFields = $fields;
return $this;
}
/**
* Remove the filtered keys.
*
* @param $array
* @return array
*/
protected function filterFields($array)
{
return collect($array)->forget($this->withoutFields)->toArray();
}
}
For (2) and (3) we need to change the UsersResourceCollection
file. Let's expose the hide
method and process the collection with the hidden fields.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UsersResourceCollection extends ResourceCollection
{
/**
* @var array
*/
protected $withoutFields = [];
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return $this->processCollection($request);
}
public function hide(array $fields)
{
$this->withoutFields = $fields;
return $this;
}
/**
* Send fields to hide to UsersResource while processing the collection.
*
* @param $request
* @return array
*/
protected function processCollection($request)
{
return $this->collection->map(function (UsersResource $resource) use ($request) {
return $resource->hide($this->withoutFields)->toArray($request);
})->all();
}
}
And that's it! Now if we call http://api.dev/api/users
we can see a response without id
and email
fields like the UsersController
specified.
{
"data": [{
"name": "Mr. Frederik Morar"
}, {
"name": "Angel Daniel"
}, {
"name": "Brianne Mueller"
}],
"links": {
"first": "http://lab.php71/api-fields-2/public/api/users?page=1",
"last": "http://lab.php71/api-fields-2/public/api/users?page=7",
"prev": null,
"next": "http://lab.php71/api-fields-2/public/api/users?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 7,
"path": "http://api-fields.lab.php71/api/users",
"per_page": 3,
"to": 3,
"total": 20
}
}
6- Conclusion
The goal was to make the Resource
class a little flexible by allowing it to hide some fields that another endpoint may expose. An actual example of this implementation would be a /users
endpoint not including the avatar
attribute, but when requesting a specific user via /users/99
we may desire to include the avatar
in the response.
I wouldn't recommend reusing API Resources too much as it may easily increase the complexity of a layer that is suppose to be simple. With that said, hiding some specific fields between requests of list and specific record does seem a reasonable request on the account of the simplicity of the implementation.