Laravel'de Repository Pattern Kullanımı

·

9 min read

Türkçe olarak kullandığım kelimelerin orijinalleri :

  • Kalıp : Pattern
  • Çatı : Framework
  • Kalıcı : Persistent Storage
  • Katman : Layer
  • Bağımlılık : Dependencies
  • Ayırmak/Gevşek bağlamak : Decouple

Bu yazıda Laravel Framework model yaklaşımımızı kolaylaştıran Repository Pattern'den bahsedeceğim.

Laravel uygulamasında repository kalıbı uygulamak için bilmemiz gereken ilk şey, repository kalıbının ne olduğunu ve onu kullanmanın faydalarını anlamak olacaktır

Bu makalede şunları kavrayacaksınız:

  • Repository kalıbını kullanmanın temel faydaları.
  • Laravel'de PHP Repository Kalıbı nasıl uygulanır?
  • Laravel tabanlı bir uygulamada nasıl uygulanacağını size adım adım göstereceğim.

Neden Önemli ? Neden tasarım kalıbına ihtiyacımız var?

image.png

Repositor, domain ile kalıcı katman arasındaki ayrımı sağlar. Repository, bir veritabanında, dosya sisteminde veya harici hizmette depolanan verilere erişmek için bir toplama arabirimi sağlar. Veriler, nesneler biçiminde döndürülür.

Bir Laravel uygulamasında repository kalıbını kullanmanın ana fikri, modeller ve controller arasında bir köprü oluşturmaktır. Başka bir deyişle, modellerin zorunlu bağımlılıklarını controllerden ayırmak ve gevşek bağlı bir hale getirmek. Model, veri tabanıyla iletişim kurmaktan veya veri tabanından veri çekmekten sorumlu olmamalıdır. Bir model, veri yapımızdaki belirli bir tabloyu / belgeyi / nesneyi veya başka bir türü temsil eden bir nesne olmalı ve bu onun sorumluluğunda olmalıdır. Bu nedenle, Laravel kodunuzu temiz ve güvenli tutmak için, modelin hiçbir zaman sorumlu olmaması ve gereken sorumluluğu repository kalıbı üzerinden kullanmaya yönelim göstermesi gerekiyor.

Repository kalıbını kullanmanın birçok faydası var, aşağıda en önemli olanlarını yazdım:

  • Veri erişim mantığının merkezileştirilmesi, kodun bakımını kolaylaştırması,
  • Business ve veri erişim mantığının ayrı ayrı test edilebilir olması,
  • Kodun tekrarlanmasını azaltması,
  • Daha düşük programlama hataları yapma şansı sunması ve debug işlemini kolaylaştırması.

Çoğu Laravel uygulamasında şöyle bir kodla karşılaşabilirsiniz.

class UsersController extends Controller
{
   public function index()
   {
       $users = User::all();

       return view('users.index', [
           'users' => $users
       ]);
   }
}

Haklısınız, bunda ne var dediğinizi duyar gibiyim:) İlk bakışta o kadar da kötü görünmüyor. Bununla birlikte, müşteri veri yapısını değiştirmeyi teklif ederse ve bundan sonra verileri başka bir yerde, Eloquent tarafından desteklenmeyen veri motorunda saklayacak duruma gelirsek ne olacak? Böyle bir kod yazdığımızda, bu değişikliğin uygulanması çok zor, hatta imkansız hale gelebilir! Bu nedenle, kodu bu şekilde yazmak çok tehlikelidir, her uygulama interface yapılarına dayanmalıdır bu şekilde eğişiklik talebi olması durumunda uygulamadaki tüm kodu değiştirmeniz gerekmez, yalnızca interface uygulayan başka bir class oluşturmanız gerekir.

Yukarıdaki kodu şu şekilde yazmak daha mantıklı değil mi ?

class UsersController extends Controller
{
   private $userRepository;

   public function __construct(UserRepositoryInterface $userRepository)
   {
       $this->userRepository = $userRepository;
   }

   public function index()
   {
       $users = $this->userRepository->all();

       return view('users.index', [
           'users' => $users
       ]);
   }
}

Bu örnekte, müşteri veri yapısını değiştirmek istediğinde, bu değişiklikleri projemize uygulamak çocuk oyuncağı olacaktır! Biz sadece UserRepositoryInterface'i uygulayan yeni bir class üretip, her şeyin terkarardan doğru düzgün çalışmasını sağlayabileceğiz. Bu tarz bir yaklaşım sayesinde müşteri en zor değişiklik isteğiyle gelse bile bununla kolayca başa çıkabiliriz. Repository kalıbı bu dönüşüm sürecini büyük ölçüde kolaylaştırır!

Haydi şimdi bunu bir Laravel uygulamasında test edelim.

Artık Laravel'deki repository modelinin faydalarını biliyorsunuz, o halde bunu uygulamamızda deneyelim! Klasör yapımız aşağıdaki gibi olmalı:

-- app
---- Repository
------ Eloquent
-------- UserRepository.php
-------- BaseRepository.php
------ UserRepositoryInterface.php
------ EloquentRepositoryInterface.php

Bu şekilde, yarın veya başka bir gün değişiklik isteği geldiğinde -örneğin verileri MongoDB'de saklayalım gibi- o zaman MongoDB adında bir klasör açıp ilgili interface yapısına implemente edeceğiz. Hepsi bu kadar. Bu şekilde model katmanı hiçbir şeyden haberdar olmayacak. Ne kadar güzel :)

Bu aşamada sizi meraklandırabilecek bir şey - BaseRepository ve EloquentRepositoryInterface dosyalarının varlığı olacaktır? Bunu hızlıca açıklamama izin verin. Her Eloquent, repository uygulama sınıfının genişletileceği sadece bir ana sınıf olacaktır. Bu sınıf, her repository'de yaygın olarak kullanılan metodları saklayacaktır. Dolayısıyla BaseRepository sınıfı şöyle görünecek:

EloquentRepositoryInterface.php

namespace App\Repository;


use Illuminate\Database\Eloquent\Model;

/**
* Interface EloquentRepositoryInterface
* @package App\Repositories
*/
interface EloquentRepositoryInterface
{
   /**
    * @param array $attributes
    * @return Model
    */
   public function create(array $attributes): Model;

   /**
    * @param $id
    * @return Model
    */
   public function find($id): ?Model;
}

BaseRepository.php

<?php   

namespace App\Repository\Eloquent;   

use App\Repository\EloquentRepositoryInterface; 
use Illuminate\Database\Eloquent\Model;   

class BaseRepository implements EloquentRepositoryInterface 
{     
    /**      
     * @var Model      
     */     
     protected $model;       

    /**      
     * BaseRepository constructor.      
     *      
     * @param Model $model      
     */     
    public function __construct(Model $model)     
    {         
        $this->model = $model;
    }

    /**
    * @param array $attributes
    *
    * @return Model
    */
    public function create(array $attributes): Model
    {
        return $this->model->create($attributes);
    }

    /**
    * @param $id
    * @return Model
    */
    public function find($id): ?Model
    {
        return $this->model->find($id);
    }
}

Yani bu bizim temel repository depomuz. Oluşturucuya(c'tor) model sınıfı enjekte ediyoruz ve bu mevcut örnekteki her Eloquent repository kullanılabilecek yöntemleri depoluyoruz, şimdilik sadece create() ve find() yöntemlerini içeriyor.

Ana sınıfı aldık, bu yüzden bir sonraki adım user repository sınıfını uygulamak!

UserRepositoryInterface.php


<?php
namespace App\Repository;

use App\Model\User;
use Illuminate\Support\Collection;

interface UserRepositoryInterface
{
   public function all(): Collection;
}

UserRepository.php

<?php

namespace App\Repository\Eloquent;

use App\Model\User;
use App\Repository\UserRepositoryInterface;
use Illuminate\Support\Collection;

class UserRepository extends BaseRepository implements UserRepositoryInterface
{

   /**
    * UserRepository constructor.
    *
    * @param User $model
    */
   public function __construct(User $model)
   {
       parent::__construct($model);
   }

   /**
    * @return Collection
    */
   public function all(): Collection
   {
       return $this->model->all();    
   }
}

Laravel 5.5 ve sonrası versiyonlar auto-discovery özelliği sayesinde map'leme işlemi otomatik olarak yapılacaktır.

Eğer auto-discovery özelliğinin desteklenmediği bir Laravel versiyonu kullanıyorsanız şu şekilde yapabilirsiniz:

php artisan make:provider RepositoryServiceProvider
<?php 

namespace App\Providers; 

use App\Repository\EloquentRepositoryInterface; 
use App\Repository\UserRepositoryInterface; 
use App\Repository\Eloquent\UserRepository; 
use App\Repository\Eloquent\BaseRepository; 
use Illuminate\Support\ServiceProvider; 

/** 
* Class RepositoryServiceProvider 
* @package App\Providers 
*/ 
class RepositoryServiceProvider extends ServiceProvider 
{ 
   /** 
    * Register services. 
    * 
    * @return void  
    */ 
   public function register() 
   { 
       $this->app->bind(EloquentRepositoryInterface::class, BaseRepository::class);
       $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
   }
}

İşte bu kadar! Şimdi onu controller sınıfından çağıralım!

class UsersController extends Controller
{
   private $userRepository;

   public function __construct(UserRepositoryInterface $userRepository)
   {
       $this->userRepository = $userRepository;
   }

   public function index()
   {
       $users = $this->userRepository->all();

       return view('users.index', [
           'users' => $users
       ]);
   }
}

Uygulamamız, App\Repository\Eloquent\UserRepository yapısını aradığımızı biliyor. Eloquent dışında başka bir uygulama kullanmak istememiz durumunda, servis sağlayıcımızdaki App\Repository\Eloquent\UserRepository kodunu örneğin App\Repository\Mongo\UserRepository. olarak değiştiririz. Uygulamamız, veri motoru değişmiş ve controller özelinde veya başka herhangi bir yerde bir satır kodu değiştirmemiş olsak bile, gene eskisi gibi çalışacaktır!

Bu kadar! Uygulamamızda repository kalıbını uyguladık.

Son olarak, bir repository kalıbı uygularken nelere dikkat edilmesi gerektiğinin kısa bir özeti.

  • Her repository kendi interface yapısına uygulanmış olmalıdır! Kendi interface yapısını uygulamadan asla bir repository oluşturmayın ve tüm repository'ler için tek bir interface oluşturmayın. Bu iyi bir yaklaşım olmaz!

  • New anahtar sözcüğünü kullanarak bir örnek oluşturmak yerine repository'lerinizi her zaman Dependency Injection(DI) kullanarak enjekte edin ve bunu yaparken arayüz kullanın! Sınıfa nesne enjekte ederseniz, unit test yazmanız daha kolaylaşır. Böylelikle SRP'yi (Tek Sorumluluk İlkesi) uygularsınız ve kodunuz daha temiz görünür.

  • BaseRepository sayesinde kod tekrarından (DRY prensibi) kaçınmış olursunuz. Tekrar kullanılabilir yapılar inşa edebilirsiniz.