Laravel API kaynak (resource) sınıflarıyla yanıt biçimlemek
Laravel API Resource sınıflarıyla veritabanı modelini istemciye doğrudan sızdırmadan temiz yanıtlar nasıl üretilir, örneklerle anlatıyorum.
API geliştirirken en sık düştüğüm tuzaklardan biri, Eloquent modelini doğrudan yanıt olarak döndürmekti. return $user; yazdım, işe yaradı, geçtim. Sonra o modele yeni bir alan eklendi, ilişki eklendi, hassas bir kolon geldi — ve istemci tarafı beklenmedik alanlarla karşılaştı. Laravel 5.5 ile gelen API Resource sınıfları bu soruna temiz bir çözüm sunuyor.
Sorun: modeli doğrudan döndürmek
Bir User modeli düşünün. email, name, password, remember_token, created_at, updated_at gibi alanlar var. Controller’da şunu yazarsanız:
<?php
public function show(User $user)
{
return response()->json($user);
}
password ve remember_token da yanıtta gidecek. $hidden dizisini doldurarak bunu engelleyebilirsiniz ama bu, yanıt biçimini modelin sorumluluğuna yıkmak demek. Model büyüdükçe ne göstereceğinizi, ne gizleyeceğinizi takip etmek güçleşiyor.
Bir de şöyle bir senaryo var: modele is_admin alanı ekliyorsunuz, o alan da istemciye gidiyor. İstemci bunu kullanıcıya göstermeye başlıyor. Sonra o alanı yeniden adlandırıyorsunuz — ve istemci tarafı bozuluyor. Modeli doğrudan sızdırdığınızda veritabanı şemanız ile API sözleşmeniz arasındaki sınır ortadan kalkıyor; bu ikisini birbirinden yalıtmak gerekiyor.
API Resource sınıfı nasıl oluşturulur?
Laravel’in Artisan komutuyla bir kaynak sınıfı iskeletini hızla üretebilirsiniz:
php artisan make:resource UserResource
Bu komut app/Http/Resources/UserResource.php dosyasını oluşturur. toArray metodunu istediğiniz gibi doldurursunuz:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at->toDateTimeString(),
];
}
}
Controller’da kullanımı basit:
<?php
public function show(User $user)
{
return new UserResource($user);
}
Bu kadar. Artık istemci tam olarak ne görmesi gerektiğini görüyor, fazlasını değil.
Koleksiyon yanıtları
Tek model değil, liste döndürmeniz gerektiğinde ResourceCollection devreye giriyor. En hızlı yol UserResource::collection() metodunu çağırmak:
<?php
public function index()
{
return UserResource::collection(User::all());
}
Sayfalama (pagination) kullananlar için de bu yöntem çalışıyor; paginate() döndürdüğünüzde Laravel otomatik olarak meta ve links bloklarını ekliyor.
Koşullu alanlar
Bazen bir alanı yalnızca belirli durumlarda yanıta dahil etmek istiyorum. Örneğin token alanını yalnızca yeni oluşturulan kayıtta göstermek gibi. when metodu bu işi görüyor:
<?php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'token' => $this->when($this->wasRecentlyCreated, $this->api_token),
];
}
wasRecentlyCreated false döndürdüğünde token anahtarı yanıtta hiç yer almıyor; null değil, tamamen yok. Bu fark önemli.
İlişkili veriler
Kullanıcının rollerini de yanıta dahil etmek istediğinizi düşünelim. Başka bir kaynak sınıfı tanımlayıp içinden çağırabilirsiniz:
<?php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'roles' => RoleResource::collection($this->whenLoaded('roles')),
];
}
whenLoaded metodunun değeri şu: ilişki eager load edilmediyse o alanı yanıta eklemiyor. Yani Controller’da User::with('roles')->find($id) yazdığınızda roller görünüyor, yazmadığınızda görünmüyor. N+1 sorgu tuzağına düşülmeden, istek başına gereksiz veri çekilmeden çalışıyor.
Yanıta ek metadata eklemek
Bazen yanıtın etrafına ilave bilgi sarmak gerekiyor: durum kodu, mesaj, sayfalama bilgisi dışında özel bir meta gibi. Bunun için kaynak sınıfında with metodunu override edebilirsiniz:
<?php
public function with($request)
{
return [
'meta' => [
'api_version' => '1.0',
],
];
}
İstemci sayısı arttığında bu katmanın değeri netleşiyor
Resource sınıfını ilk kullandığımda projenin tek istemcisi bir web arayüzüydü. Sonradan mobil uygulama gerekti. Mobile farklı bir alan adlandırması lazımdı — full_name yerine name, created_at yerine joined_at gibi. Web istemcisini bozmadan mobil için ayrı bir UserMobileResource yazabildim; ikisi aynı User modelini farklı biçimlerle sunuyor. Modeli doğrudan döndürseydiniz, bu ayrımı yapmanın temiz bir yolu olmazdı.
Birden fazla istemci — ya da zamanla değişen tek bir istemci — olan her projede bu dönüşüm katmanı, veritabanı değişikliklerini API sözleşmesinden yalıtıyor. Yeni bir kolon eklemek, mevcut yanıtı bozmak anlamına gelmiyor.
Sonuç
API Resource sınıfları başta “ekstra bir katman daha” gibi görünüyor. Ama ilk projede kullandıktan sonra modeli yanıta bağlamak için başka bir yola dönmek istemiyorum. Hangi alanın gideceğini tek bir dosyada görüp yönetmek, istemci tarafının beklenmedik değişikliklerden etkilenmemesini sağlıyor. Özellikle birden çok istemci varsa — mobil uygulama, web uygulaması, üçüncü parti tüketici — bu katmanın değeri daha da belirginleşiyor.