Repository là phần trung gian, ở giữa phần dữ liệu và phần xử lý logic 😍(ok)

https://viblo.asia/p/trien-khai-repository-trong-laravel-m68Z0x6MZkG

👍👍👍 Repository là nơi lưu trữ logic truy vấn dữ liệu. Các lệnh truy vẫn dữ liệu vốn được viết trực tiếp ở controller sẽ được đưa vào Repository. Khi đó, Controller sẽ dùng Repository để tương tác với dữ liệu thay vì sử dụng trực tiếp. Việc truy cập dữ liệu được giấu kín trong Repository.

Ví dụ 1:

C:\xampp\htdocs\bangtaivietnam\app\Repositories\User\UserInterface.php

<?php
namespace App\Repositories\User;
interface UserInterface {
  public function getAll();
  public function find($id);
  public function delete($id);
}
?>

C:\xampp\htdocs\bangtaivietnam\app\Repositories\User\UserRepository.php

<?php
namespace App\Repositories\User;
use App\Repositories\User\UserInterface as UserInterface;
use App\Models\User;
class UserRepository implements UserInterface {
  public $user;
  function __construct(User $user) {
    $this->user = $user;
  }
  public function getAll() {
    return $this->user->getAll();
  }
  public function find($id) {
    return $this->user->findUser($id);
  }
  public function delete($id) {
    return $this->user->deleteUser($id);
  }
}
?>

C:\xampp\htdocs\bangtaivietnam\app\Repositories\User\UserRepoServiceProvide.php

<?php
namespace App\Repositories\User;
use Illuminate\Support\ServiceProvider;
class UserRepoServiceProvide extends ServiceProvider {
  /**
   * Register the application services.
   *
   * @return void
   */
  public function register() {
    $this->app->bind('App\Repositories\User\UserInterface', 'App\Repositories\User\UserRepository');
  }
  /**
   * Bootstrap the application services.
   *
   * @return void
   */
  public function boot() {}
}
?>

C:\xampp\htdocs\bangtaivietnam\app\Models\User.php

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable {
  use HasApiTokens, HasFactory, Notifiable;
  /**
   * The attributes that are mass assignable.
   *
   * @var array<int, string>
   */
  protected $fillable = [
    'name',
    'email',
    'password',
  ];
  /**
   * The attributes that should be hidden for serialization.
   *
   * @var array<int, string>
   */
  protected $hidden = [
    'password',
    'remember_token',
  ];
  /**
   * The attributes that should be cast.
   *
   * @var array<string, string>
   */
  protected $casts = [
    'email_verified_at' => 'datetime',
  ];
  public function getAll() {
    return static::all();
  }
  public function findUser($id) {
    return static::find($id);
  }
  public function deleteUser($id) {
    return static::find($id)->delete();
  }
}

C:\xampp\htdocs\bangtaivietnam\app\Http\Controllers\UserController.php

<?php
namespace App\Http\Controllers;
use App\Repositories\User\UserInterface as UserInterface;
class UserController extends Controller {
  public function __construct(UserInterface $user) {
    $this->user = $user;
  }
  /**
   * Display a listing of the resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function index() {
    $users = $this->user->getAll();
    return view('users.index')->with(compact('users'));
  }
  /**
   * Display a listing of the resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function show($id) {
    $user = $this->user->find($id);
    return view('users.show')->with("user",$user);
  }
  /**
   * Display a listing of the resource.
   *
   * @return \Illuminate\Http\Response
   */
  public function destroy($id) {
    $this->user->delete($id);
    return redirect()->route('users.index')->with('success','Product deleted successfully');
  }
}
?>

C:\xampp\htdocs\bangtaivietnam\config\app.php

'providers' => [
    ...
    App\Repositories\User\UserRepoServiceProvide::class,
    ...
]

C:\xampp\htdocs\bangtaivietnam\routes\web.php

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
  return view('welcome');
});
Route::resource('users', [UserController::class]);

C:\xampp\htdocs\bangtaivietnam\resources\views\layout.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>Laravel 9 CRUD with Image Upload Application - ItSolutionStuff.com</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    @yield('content')
</div>
</body>
</html>

C:\xampp\htdocs\bangtaivietnam\resources\views\users\index.blade.php

@extends('layout')
@section('content')
  <div class="row">
    <div class="col-lg-12 margin-tb">
      <div class="pull-left">
        <h2>Laravel 9 CRUD with Image Upload Example from scratch - ItSolutionStuff.com</h2>
      </div>
      <div class="pull-right">
        <a class="btn btn-success" href="{{ route('users.create') }}"> Create New Product</a>
      </div>
    </div>
  </div>
  @if ($message = Session::get('success'))
    <div class="alert alert-success">
      <p>{{ $message }}</p>
    </div>
  @endif
  <table class="table table-bordered">
    <tr>
      <th>No</th>
      <th>Name</th>
      <th>Details</th>
      <th width="280px">Action</th>
    </tr>
    @php
    $i = 0;
    @endphp
    @foreach ($users as $user)
    <tr>
      <td>{{ ++$i }}</td>
      <td>{{ $user->name }}</td>
      <td>{{ $user->detail }}</td>
      <td>
        <form action="{{ route('users.destroy',$user->id) }}" method="POST">
          <a class="btn btn-info" href="{{ route('users.show',$user->id) }}">Show</a>
          <a class="btn btn-primary" href="{{ route('users.edit',$user->id) }}">Edit</a>
          @csrf
          @method('DELETE')
          <button type="submit" class="btn btn-danger">Delete</button>
        </form>
      </td>
    </tr>
    @endforeach
  </table>
@endsection

C:\xampp\htdocs\bangtaivietnam\resources\views\users\show.blade.php

@extends('layout')
@section('content')
    <div class="row">
      <div class="col-lg-12 margin-tb">
        <div class="pull-left">
          <h2>Laravel 9 CRUD with Image Upload Example from scratch - ItSolutionStuff.com</h2>
        </div>
        <div class="pull-right">
          <a class="btn btn-success" href="{{ route('users.create') }}"> Create New Product</a>
        </div>
      </div>
    </div>
    @if ($message = Session::get('success'))
      <div class="alert alert-success">
        <p>{{ $message }}</p>
      </div>
    @endif
    <table class="table table-bordered">
        <tr>
          <th>No</th>
          <th>Name</th>
          <th>Details</th>
          <th width="280px">Action</th>
        </tr>
        @php
        $i = 0;
        @endphp
        <tr>
          <td>{{ ++$i }}</td>
          <td>{{ $user->name }}</td>
          <td>{{ $user->detail }}</td>
          <td>
            <form action="{{ route('users.destroy',$user->id) }}" method="POST">
              <a class="btn btn-info" href="{{ route('users.show',$user->id) }}">Show</a>
              <a class="btn btn-primary" href="{{ route('users.edit',$user->id) }}">Edit</a>
              @csrf
              @method('DELETE')
              <button type="submit" class="btn btn-danger">Delete</button>
            </form>
          </td>
        </tr>
    </table>
@endsection

Ví dụ 2:

C:\xampp\htdocs\bangtaivietnam\routes\web.php

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
  return view('welcome');
});
Route::resource('posts', PostController::class);

C:\xampp\htdocs\bangtaivietnam\app\Repositories\BackendServiceProvider.php

<?php
namespace App\Repositories;
use Illuminate\Support\ServiceProvider;
class BackendServiceProvider extends ServiceProvider {
  public function register() {
    $this->app->bind(
      'App\Repositories\PostRepositoryInterface',
      'App\Repositories\PostRepository'
    );
  }
}
?>

C:\xampp\htdocs\bangtaivietnam\app\Repositories\PostRepository.php

<?php
namespace App\Repositories;
use App\Models\Post;
use App\Repositories\PostRepositoryInterface;
class PostRepository implements PostRepositoryInterface {
  /**
   * Get's a post by it's ID
   *
   * @param int
   * @return collection
   */
  public function get($post_id) {
    return Post::find($post_id);
  }
  /**
   * Get's all posts.
   *
   * @return mixed
   */
  public function all() {
    return Post::all();
  }
  /**
   * Deletes a post.
   *
   * @param int
   */
  public function delete($post_id) {
    Post::destroy($post_id);
  }
  /**
   * Updates a post.
   *
   * @param int
   * @param array
   */
  public function update($post_id, array $post_data) {
    Post::find($post_id)->update($post_data);
  }
}

C:\xampp\htdocs\bangtaivietnam\app\Repositories\PostRepositoryInterface.php

<?php
namespace App\Repositories;
interface PostRepositoryInterface {
  /**
   * Get's a post by it's ID
   *
   * @param int
   */
  public function get($post_id);
  /**
   * Get's all posts.
   *
   * @return mixed
   */
  public function all();
  /**
   * Deletes a post.
   *
   * @param int
   */
  public function delete($post_id);
  /**
   * Updates a post.
   *
   * @param int
   * @param array
   */
  public function update($post_id, array $post_data);
}
?>

C:\xampp\htdocs\bangtaivietnam\config\app.php

'providers' => [
    ...
    App\Repositories\BackendServiceProvider::class,
    ...
]

C:\xampp\htdocs\bangtaivietnam\app\Http\Controllers\PostController.php

<?php
namespace App\Http\Controllers;
use App\Repositories\PostRepositoryInterface;
class PostController extends Controller {
  protected $post;
  /**
   * PostController constructor.
   *
   * @param PostRepositoryInterface $post
   */
  public function __construct(PostRepositoryInterface $post) {
    $this->post = $post;
  }
  /**
   * List all posts.
   *
   * @return mixed
   */
  public function index() {
    $data = [
      'posts' => $this->post->all(),
    ];
    return view('posts', $data);
  }
}

C:\xampp\htdocs\bangtaivietnam\resources\views\posts.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Laravel</title>
    </head>
    <body class="antialiased">
      @foreach ($posts as $post)
        <p>{{ $post->post_name }}</p>
      @endforeach
    </body>
</html>

More than one Repository

https://www.larashout.com/how-to-use-repository-pattern-in-laravel#toc-creating-the-repository-interface

namespace App\Repositories;

use Illuminate\Support\ServiceProvider;

class BackendServiceProvider extends ServiceProvider
{

    public function register()
    {
        $this->app->bind(
            'App\Repositories\PostRepositoryInterface',
            'App\Repositories\PostRepository'
        );

        $this->app->bind(
            'App\Repositories\CommentRepositoryInterface',
            'App\Repositories\CommentRepository'
        );
    }
}

Bướcc 1: Tạo thư mục và file

C:\xampp\htdocs\bangtaivietnam\app\Repositories\RepositoryInterface.php

<?php
namespace App\Repositories;
interface RepositoryInterface {
  /**
   * Get all
   * @return mixed
   */
  public function getAll();
  /**
   * Get one
   * @param $id
   * @return mixed
   */
  public function find($id);
  /**
   * Create
   * @param array $attributes
   * @return mixed
   */
  public function create($attributes = []);
  /**
   * Update
   * @param $id
   * @param array $attributes
   * @return mixed
   */
  public function update($id, $attributes = []);
  /**
   * Delete
   * @param $id
   * @return mixed
   */
  public function delete($id);
}

C:\xampp\htdocs\bangtaivietnam\app\Repositories\BaseRepository.php

<?php
namespace App\Repositories;
use App\Repositories\RepositoryInterface;
abstract class BaseRepository implements RepositoryInterface {
  //model muốn tương tác
  protected $model;
  //khởi tạo
  public function __construct() {
    $this->setModel();
  }
  //lấy model tương ứng
  abstract public function getModel();
  /**
   * Set model
   */
  public function setModel() {
    $this->model = app()->make(
      $this->getModel()
    );
  }
  public function getAll() {
    return $this->model->all();
  }
  public function find($id) {
    $result = $this->model->find($id);
    return $result;
  }
  public function create($attributes = []) {
    return $this->model->create($attributes);
  }
  public function update($id, $attributes = []) {
    $result = $this->find($id);
    if ($result) {
      $result->update($attributes);
      return $result;
    }
    return false;
  }
  public function delete($id) {
    $result = $this->find($id);
    if ($result) {
      $result->delete();
      return true;
    }
    return false;
  }
}

Bước 2: Đăng kí trong app/Providers/AppServiceProvider.php

C:\xampp\htdocs\bangtaivietnam\app\Providers\AppServiceProvider.php

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
  /**
   * Register any application services.
   *
   * @return void
   */
  public function register() {
    $this->app->singleton(
      App\Repositories\Product\ProductRepositoryInterface::class,
      App\Repositories\Product\ProductRepository::class
    );
  }
  /**
   * Bootstrap any application services.
   *
   * @return void
   */
  public function boot() {
    //
  }
}

Bước 3: Viết Interface và Repository cho Model tương ứng.

Bước 4: Tạo ProductController

Triển khai Repository trong Laravel

1. Design Pattern là gì?

Design Pattern là các mẫu thiết kế chuẩn, được đúc kết và tạo ra sau quá trình làm việc và nghiên cứu lâu dài của các chuyên gia.

Design Pattern là một giải pháp tổng thể, một khuôn mẫu cho các vấn đề chung trong thiết kế phần mềm.

Trong hướng đối tượng, Design Pattern cho thấy mối quan hệ và tương tác giữa các lớp hoặc các đối tượng mà không cần chỉ rõ đối tượng cụ thể.

Lợi ích của Design Pattern:

  • Dễ bảo trì, nâng cấp và mở rộng dự án

  • Hạn chế được các lỗi tiềm ẩn

  • Code dễ đọc, dễ hiểu và dễ phát triển

2. Repository là gì?

Repository là phần trung gian, ở giữa phần dữ liệu và phần xử lý logic.

Repository là nơi lưu trữ logic truy vấn dữ liệu. Các lệnh truy vẫn dữ liệu vốn được viết trực tiếp ở controller sẽ được đưa vào Repository. Khi đó, Controller sẽ dùng Repository để tương tác với dữ liệu thay vì sử dụng trực tiếp. Việc truy cập dữ liệu được giấu kín trong Repository.

Thông thường trong Laravel, các phương thức như find, create, update, delete,... được viết khá giống nhau, chỉ khác nhau ở tên Model (đối tượng) cần tương tác. Vì vậy, các đoạn code này nên đưa ra Repository để tiết kiệm việc viết code.

Lợi ích của Repository:

  • Code dễ đọc, dễ phát triển sản phẩm trong làm việc nhóm

  • Giảm thay đổi code khi phần mềm có thay đổi cấu trúc dữ liệu

  • Tránh việc lặp code

  • Hạn chế lỗi trong truy vấn

  • Dễ dàng thực hiện test

3. Ví dụ

Bước 1:

Trong thư mục apptrong ứng dụng Laravel của bạn, tạo 1 thư mục có tên là Repositories. Trong thư mục sẽ có:

  • RepositoryInterface: interface khuôn mẫu, khai báo các phương thức chung cho các Models.

  • BaseRepository: implements RepositoryInterface, triển khai các phương thức chung cho các Model

  • Các thư mục riêng tương ứng với các Model. (Ở đây mình ví dụ là Product). Trong thư mục này chứa:

    • ProductRepositoryInterface: interface chứa các phương thức riêng của Model Product.

    • ProductRepository: implements ProductRepositoryInterface

Trong ProductController, thay vì viết trực tiếp truy vẫn, chúng ta sẽ khởi tạo controller có thuộc tính $productRepo để truy vấn dữ liệu qua nó.

Bước 2: Đăng kí trong app/Providers/AppServiceProvider.php

Khi khởi tạo controller, chúng ta sẽ gán thuộc tính $productRepo có kiểu là ProductRepositoryInterface. Tại sao không phải là ProductRepository?

Mục đích là để khi các logic làm việc với dữ liệu thay đổi hay để dễ hiểu, các bạn nghĩ đến khi database thay đổi (ví dụ chuyển từ MySQL sang Redis), các lệnh truy vấn dữ liệu sẽ thay đổi. Khi đó, ta sẽ phải tạo thêm 1 class ProductRepository khác (ví dụ RedisProductRepository). Để dễ dàng cho các thay đổi như vậy, không cần sửa lại các controller đang dùng repository cũ, ta đăng kí và sau đó chỉ cần thay đổi repository ở file app/Providers/AppServiceProvider.php.

public function register()
{
    $this->app->singleton(
        \App\Repositories\Product\ProductRepositoryInterface::class,
        \App\Repositories\Product\ProductRepository::class
    );
}

Bước 3: Tạo RepositoryInterface và BaseRepository

RepositoryInterface

<?php

namespace App\Repositories;

interface RepositoryInterface
{
    /**
     * Get all
     * @return mixed
     */
    public function getAll();

    /**
     * Get one
     * @param $id
     * @return mixed
     */
    public function find($id);

    /**
     * Create
     * @param array $attributes
     * @return mixed
     */
    public function create($attributes = []);

    /**
     * Update
     * @param $id
     * @param array $attributes
     * @return mixed
     */
    public function update($id, $attributes = []);

    /**
     * Delete
     * @param $id
     * @return mixed
     */
    public function delete($id);
}

BaseRepository

<?php

namespace App\Repositories;

use App\Repositories\RepositoryInterface;

abstract class BaseRepository implements RepositoryInterface
{
    //model muốn tương tác
    protected $model;

   //khởi tạo
    public function __construct()
    {
        $this->setModel();
    }

    //lấy model tương ứng
    abstract public function getModel();

    /**
     * Set model
     */
    public function setModel()
    {
        $this->model = app()->make(
            $this->getModel()
        );
    }

    public function getAll()
    {
        return $this->model->all();
    }

    public function find($id)
    {
        $result = $this->model->find($id);

        return $result;
    }

    public function create($attributes = [])
    {
        return $this->model->create($attributes);
    }

    public function update($id, $attributes = [])
    {
        $result = $this->find($id);
        if ($result) {
            $result->update($attributes);
            return $result;
        }

        return false;
    }

    public function delete($id)
    {
        $result = $this->find($id);
        if ($result) {
            $result->delete();

            return true;
        }

        return false;
    }
}

Bước 4: Viết Interface và Repository cho Model tương ứng.

ProductRepositoryInterface

<?php
namespace App\Repositories\Product;

use App\Repositories\RepositoryInterface;

interface ProductRepositoryInterface extends RepositoryInterface
{
    //ví dụ: lấy 5 sản phầm đầu tiên
    public function getProduct();
}

ProductRepository

<?php
namespace App\Repositories\Post;

use App\Repositories\BaseRepository;

class ProductRepository extends BaseRepository implements ProductRepositoryInterface
{
    //lấy model tương ứng
    public function getModel()
    {
        return \App\Models\Product::class;
    }

    public function getProduct()
    {
        return $this->model->select('product_name')->take(5)->get();
    }
}

Bước 5: Tạo ProductController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\Product\ProductRepositoryInterface;

class ProductController extends Controller
{
    /**
     * @var PostRepositoryInterface|\App\Repositories\Repository
     */
    protected $productRepo;

    public function __construct(ProductRepositoryInterface $productRepo)
    {
        $this->productRepo = $productRepo;
    }

    public function index()
    {
        $products = $this->productRepo->getAll();

        return view('home.products', ['products' => $products]);
    }

    public function show($id)
    {
        $product = $this->productRepo->find($id);

        return view('home.product', ['product' => $product]);
    }

    public function store(Request $request)
    {
        $data = $request->all();

        //... Validation here

        $product = $this->productRepo->create($data);

        return view('home.products');
    }

    public function update(Request $request, $id)
    {
        $data = $request->all();

        //... Validation here

        $product = $this->productRepo->update($id, $data);

        return view('home.products');
    }

    public function destroy($id)
    {
        $this->productRepo->delete($id);
        
        return view('home.products');
    }
}

Tài liệu tham khảo:

https://www.larashout.com/how-to-use-repository-pattern-in-laravel

https://medium.com/employbl/use-the-repository-design-pattern-in-a-laravel-application-13f0b46a3dce

https://viblo.asia/p/repository-pattern-trong-laravel-gGJ59jPaKX2

Để hiểu thêm, các bạn đọc cả Dependency Injection nha :3

Last updated