Laravel 5.5 (LTS): API Resources

Emir Karşıyakalı
Emir Karşıyakalı
4 min readOct 2, 2017

RESTful API geliştirirken dikkat etmemiz gereken en önemli konulardan biri tutarlılık(consistency). Versiyonlama, HTTP durum(status) kodlarınının kullanımı, kaynakların(resource) isimlendirmeleri, çıktılarımızın(response) kapsüllemeleri ve çıktılarımızı oluşturan alanların(field) isimleri gibi tutarlılığı korumamız gereken farklı seviyeler var. Bu yazının konusu tutarlılığın çıktılarla alakalı olan kısmı kapsülleme ve alan isimlendirmeleri.

API Resources’ı tanıtmadan önce daha önce nasıl yaptığımıza bakalım. Problem çıktıları kapsülleme olduğunda birçok farklı yol deneyebilirsiniz. Laravel’in Controller’ından kalıtım alan bir BaseController oluşturup buraya respondSuccess, respondBadRequest vb. methodlar ekleyebilir ve bu methodlar içerisinde çıktılarınızı kapsülleyebilirsiniz:

//BaseControllerpublic function respondSuccess(array $data): JsonResponse
{
return response()->json([
'data' => $data
], 200);
}
//UsersControllerpublic function index()
{
$users = User::all();

$this->respondSuccess((array) $users);
}

veya bir Trait oluşturup çıktılarınızı burada da kapsülleyebilirsiniz:

trait Response
{
public function respondSuccess(array $data): JsonResponse
{
return response()->json([
'data' => $data
]);
}
}
//UsersControlleruse Response;public function show()
{
$users = User::all();

$this->respondSuccess((array) $users);
}

Bir Response sınıfı oluşturup AppServiceProvider’da Controller’a inject edip $this->response->respondSuccess şeklinde de kullanabilirsiniz. Bunlar aklıma gelen ilk DRY(Don’t repeat yourself) örnekler. Direk methodlarınız içerisinde return response()->json([‘data’ => $data]); şeklinde de kullanabilirsiniz ama sizden sonra gelen geliştirici çıktılarla oynamak istediğinde muhtemelen sağlam sövecektir.

Yukarıda örneklerin hepsinde amaç çıktılara data kapsülü eklemekti. Özetle ulaşmaya çalıştığımız nihai çıktı:

{
"data": [
{
"id": 1
},
{
"id": 2
}
]
}

Problem çıktılarımızdaki alanların isimlendirmeleri olduğunda da array_map kullanabiliriz:

public function transformCollection(array $data): array 
{
return array_map([$this, 'transform'], $data);
}

public function transform(array $item): array
{
return [
'identifier' => (int) $item['id']
];
}

$data dizisi(array) içerisinde id olan alanın adını identifier olarak değiştirdik ve tipini integer olmaya zorladık(type casting). Kullanırken de:

//UsersControllerpublic function index()
{
$users = User::all();

$this->respondSuccess((array) $users);
}
//BaseControllerpublic function respondSuccess(array $data): JsonResponse
{
return response()->json([
'data' => $this->transformCollection($data)
], 200);
}
//Response{
"data": [
{
"identifier": 1
},
{
"identifier": 2
}
]
}

Buraya kadar asıl konuya gelmeden çıktılarınızda tutarlığı nasıl koruyabileceğinizi farklı örneklerle anlatmaya çalıştım. Ben geliştirdiğim uygulamalarda genellikle The PHP League’in Fractal kütüphanesini kullanıyorum. Laravel için Spatie’in yazdığı bir wrapper da mevcut.

API Resources

Laravel 5.5 ile gelen API Resources bize modellerimiz ile çıktılarımız arası tutarlılığı korumamız için ara katman sağlıyor.

Resource ve Collections
Resource dediğimiz tekil Model::find(1), Collections dediğimiz çoğul Model::all() yani Resource'ların Illuminate/Support/Collection ile dönmesi.

Resource ve Collections Oluşturma
Artisan’ı kullanarak yeni bir Resource ve Resource’a bağlı Collections oluşturabiliyoruz:

php artisan make:resource User

Bu komut bizim için tekil döndüreceğimiz ve Illuminate\Http\Resources\Json\Resource; sınıfından katılım alan bir User resource sınıfı oluşturuyor. Sınıf içerisindeki toArray methodu çıktıda hangi alanları döndüreceğimiz($this ile ilgili Model’in alanlarına(property) ulaşabiliyoruz) ve yapmak istersek type casting yapacağımız alanları belirlememize olanak sağlıyor:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\Resource;

class User 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,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

Yine aynı komutun sonuna --collection ekleyerek veya ismine Collection şeklinde suffix ekleyerek çoğul veri döndüreceğimiz, Illuminate\Http\Resources\Json\ResourceCollection; sınıfından kalıtım alan bir UserCollection sınıfı oluşturuyor:

php artisan make:resource Users --collectionveyaphp artisan make:resource UserCollection 

Oluşturulan UserCollection sınıfı:

<?phpnamespace App\Http\Resources;use Illuminate\Http\Resources\Json\ResourceCollection;class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}

toArray() methodu dışında, Resource ve ResourceCollections ile birlikte çıktılarımızda header göndermek istersek withResponse methodunu kullanabiliyoruz:

/**
* Customize the outgoing response for the resource.
*
* @param \Illuminate\Http\Request
* @param \Illuminate\Http\Response
* @return void
*/
public function withResponse($request, $response)
{
$response->header('X-Value', 'True');
}

Resource ve Collection’ların Kullanımı

Model’den dönen Resource(User::find(1)) veya Collection’ı(User:all()) UserResource sınıfımıza parametre olarak geçiyoruz:

//Resource Kullanım:use App\User;
use App\Http\Resources\User as UserResource;

Route::get('/user', function () {
return new UserResource(User::find(1));
});
//Collections Kullanım:use App\User;
use App\Http\Resources\User as UserResource;

Route::get('/user', function () {
return UserResource::collection(User::all());
});

İlişkiler (Relationships)

Aşağıda User ve Post arasında One-to-many ilişki(relation) örneği mevcut. toArray methodu içerisinde ilgili anahtarda(key) ayrı bir collection çağırabiliyoruz:

/**
* 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,
'posts' => Post::collection($this->posts),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

Durumun tam tersinde Post altında User getirmek istersek(Belongs to) Post::resource($this->post) şeklinde kullanabiliyoruz.

Duruma bağlı ilişki eklemek istersek:

'posts' => Post::collection($this->whenLoaded('posts')),

Kullanıcının Post’ları yoksa çıktımızda posts görünmeyecek.

Durumlar (Conditions)

API’ımızı admin olarak kullandığımız zamanlar çıktılarımıza farklı alanlar da eklemek isteyebiliriz, burada when() methodunu kullanıyoruz. Aldığı birinci parametre şartı sağlayacak durum, ikinci parametre şart sağlandığında hangi alanın gösterileceği yani model’deki tanımlaması(property):

/**
* 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,
'secret' => $this->when($this->isAdmin(), 'secret-value'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}

Aynı zamanda durumları mergeWhen() methoduyla gruplayabiliyoruz:

$this->mergeWhen($this->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),

when veya mergeWhen methodlarının birinci parametresi false dönerse şart sağlanmıyor demektir. Bu durumda da o alanlar çıktılarımızdan çıkarılır.

Gördüğünüz üzere API Resources kullanımı oldukça basit ve size çıktılarınızı 360' kontrol etme, tutarlığı sağlama imkanı sağlıyor. Buradan official dökümantasyonuna ulaşabilirsiniz.

--

--

Emir Karşıyakalı
Emir Karşıyakalı

Founder of @Kommunitycom / @itsmoneo / @Kodilancom . Entrepreneur. Software Architect & DevOps enthusiast. PHP Evangelist. @istanbulphp & #PHPKonf Organizer.