I recently saw a problem in the Laravel Brasil community that turned out to be more interesting than it seemed.Imagine that you have a UsersResource implemented with the following:
1 <?php 2 namespace App\Http\Resources; 3 use Illuminate\Http\Resources\Json\Resource; 4 class UsersResource extends Resource 5 { 6 /** 7 * Transform the resource into an array. 8 * 9 * @param \Illuminate\Http\Request 10 * @return array 11 */ 12 public function toArray($request) 13 { 14 return [ 15 'id' => $this->id, 16 'name' => $this->name, 17 'email' => $this->email 18 ]; 19 } 20 }
For some reason, you may want to reuse the resource class at another endpoint, but hide the email field.This article tells you how to do this.If you don't know what API Resources are, check out my previous article on this.
First Impression on API Resources
API Resources with Nested Relationship
1-Initialize project
What's interesting starts with Section 3.
composer create-project --prefer-dist laravel/laravel api-fields cd api-fields touch database/database.sqlite
Edit the.env file, delete database settings, and use SQLite
DB_CONNECTION=sqlite
Continue setting up the project
1 php artisan migrate 2 php artisan make:resource UsersResource 3 php artisan make:resource --collection UsersResourceCollection 4 php artisan make:controller UsersController 5 php artisan tinker 6 factory(App\User::class)->times(20)->create(); 7 quit
2-Routing
Make sure to create a route in the api.php file.
Route::apiResource('/users', 'UsersController');
3-Controller
The controller represents the desired goal.In this example, let's assume that in the user list, we only want the names of all users, while in the user display, we only want to hide the e-mail address.
1 <?php 2 namespace App\Http\Controllers; 3 use App\Http\Resources\UsersResource; 4 use App\User; 5 class UsersController extends Controller 6 { 7 /** 8 * Display a listing of the resource. 9 * 10 * @param User $user 11 * @return \Illuminate\Http\Response 12 */ 13 public function index(User $user) 14 { 15 return UsersResource::collection($user->paginate())->hide(['id', 'email']); 16 } 17 /** 18 * Display a user. 19 * 20 * @param User $user 21 * @return \Illuminate\Http\Response 22 */ 23 public function show(User $user) 24 { 25 return UsersResource::make($user)->hide(['id']); 26 } 27 }
To do this, we need both UsersResourceCollection and UsersResource to know how to handle hide calls.
4- UsersResource class
Let's start with the show method. UsersResource::make will return the object of UsersResource. So we should unveil the mystery of hide, which stores the keys we expect to remove from the response.
1 <?php 2 namespace App\Http\Resources; 3 use Illuminate\Http\Resources\Json\Resource; 4 class UsersResource extends Resource 5 { 6 /** 7 * @var array 8 */ 9 protected $withoutFields = []; 10 11 /** 12 * Transform the resource into an array. 13 * 14 * @param \Illuminate\Http\Request 15 * @return array 16 */ 17 public function toArray($request) 18 { 19 return $this->filterFields([ 20 'id' => $this->id, 21 'name' => $this->name, 22 'email' => $this->email 23 ]); 24 } 25 /** 26 * Set the keys that are supposed to be filtered out. 27 * 28 * @param array $fields 29 * @return $this 30 */ 31 public function hide(array $fields) 32 { 33 $this->withoutFields = $fields; 34 return $this; 35 } 36 /** 37 * Remove the filtered keys. 38 * 39 * @param $array 40 * @return array 41 */ 42 protected function filterFields($array) 43 { 44 return collect($array)->forget($this->withoutFields)->toArray(); 45 } 46 }
That's it! Now that we can access http://api.dev/api/users/1, you will find that there is no id field in the response.
1 { 2 "data": { 3 "name": "Mr. Frederik Morar", 4 "email": "darryl.wilkinson@example.org" 5 } 6 }
5- UsersResourceCollection class
To execute the index method in the project collection, we need to make the following modifications:
(1) Ensure that UsersResource::collection returns the UsersResourceCollection instance
(2) Expose the hide method on the UsersResourceCollection
(3) Pass hidden fields to UsersResource
For (1), we only need to override the collection method in UsersResource
1 <?php 2 namespace App\Http\Resources; 3 use Illuminate\Http\Resources\Json\Resource; 4 class UsersResource extends Resource 5 { 6 public static function collection($resource) 7 { 8 return tap(new UsersResourceCollection($resource), function ($collection) { 9 $collection->collects = __CLASS__; 10 }); 11 } 12 13 /** 14 * @var array 15 */ 16 protected $withoutFields = []; 17 /** 18 * Transform the resource into an array. 19 * Convert resources to an array 20 * 21 * @param \Illuminate\Http\Request 22 * @return array 23 */ 24 public function toArray($request) 25 { 26 return $this->filterFields([ 27 'id' => $this->id, 28 'name' => $this->name, 29 'email' => $this->email 30 ]); 31 } 32 /** 33 * Set the keys that are supposed to be filtered out. 34 * Set keys that need to be hidden for filtering 35 * 36 * @param array $fields 37 * @return $this 38 */ 39 public function hide(array $fields) 40 { 41 $this->withoutFields = $fields; 42 return $this; 43 } 44 /** 45 * Remove the filtered keys. 46 * Remove hidden keys 47 * 48 * @param $array 49 * @return array 50 */ 51 protected function filterFields($array) 52 { 53 return collect($array)->forget($this->withoutFields)->toArray(); 54 } 55 }
For (2) and (3) we need to modify the UsersResourceCollection file. Let's expose the hide method and use the hidden field to process the collection..
1 <?php 2 namespace App\Http\Resources; 3 use Illuminate\Http\Resources\Json\ResourceCollection; 4 class UsersResourceCollection extends ResourceCollection 5 { 6 /** 7 * @var array 8 */ 9 protected $withoutFields = []; 10 /** 11 * Transform the resource collection into an array. 12 * 13 * @param \Illuminate\Http\Request 14 * @return array 15 */ 16 public function toArray($request) 17 { 18 return $this->processCollection($request); 19 } 20 public function hide(array $fields) 21 { 22 $this->withoutFields = $fields; 23 return $this; 24 } 25 /** 26 * Send fields to hide to UsersResource while processing the collection. 27 * Processing collections with hidden fields through UsersResource 28 * 29 * @param $request 30 * @return array 31 */ 32 protected function processCollection($request) 33 { 34 return $this->collection->map(function (UsersResource $resource) use ($request) { 35 return $resource->hide($this->withoutFields)->toArray($request); 36 })->all(); 37 } 38 }
That's it! Now when we visit http://api.dev/api/users, we see that there are no id and email fields in the returned results, such as the specified method in UsersController.
1 { 2 "data": [{ 3 "name": "Mr. Frederik Morar" 4 }, { 5 "name": "Angel Daniel" 6 }, { 7 "name": "Brianne Mueller" 8 }], 9 "links": { 10 "first": "http://lab.php71/api-fields-2/public/api/users?page=1", 11 "last": "http://lab.php71/api-fields-2/public/api/users?page=7", 12 "prev": null, 13 "next": "http://lab.php71/api-fields-2/public/api/users?page=2" 14 }, 15 "meta": { 16 "current_page": 1, 17 "from": 1, 18 "last_page": 7, 19 "path": "http://api-fields.lab.php71/api/users", 20 "per_page": 3, 21 "to": 3, 22 "total": 20 23 } 24 }
6-Summary
The goal of this article is to make the Resource class more flexible by hiding fields that are allowed to be exposed on other interfaces.For example, when we request the/users interface, the data we respond to does not contain the avatar field, but when we request/users/99, the data we respond to contains the avatar field.
I don't recommend over-repeating requests for API resources, because it is likely to make simple things more complex, so hiding certain fields while requesting is a simpler and more reasonable solution.