PHP dynamically hides API fields in Laravel

Keywords: PHP JSON Laravel Database

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.

Posted by ozzysworld on Thu, 07 Nov 2019 05:49:54 -0800