Laravel 5.1 - Repository
1. Giới thiệu về Repository Repository là một trong các pattern hay được sử dụng trong lập trình hướng đối tượng. Trong Laravel, chúng ta sử dụng repository như một phần trung gian xử lý các tác vụ liên quan tới cơ sở dữ liệu. Sử dụng repository giúp tránh lặp lại code, dễ sử dụng, dễ sửa và đồng ...
1. Giới thiệu về Repository
Repository là một trong các pattern hay được sử dụng trong lập trình hướng đối tượng. Trong Laravel, chúng ta sử dụng repository như một phần trung gian xử lý các tác vụ liên quan tới cơ sở dữ liệu. Sử dụng repository giúp tránh lặp lại code, dễ sử dụng, dễ sửa và đồng thời thể hiện rõ hơn ý nghĩa của các dòng code.
2. Xây dựng repository
Nội dung bài viết được cập nhật ngày: 29/12/2016,
2.1 Repository InterfaceBước đầu tiên trong quá trình xây dựng repository là tạo ra interface cho nó.
<?php namespace AppRepositoriesContracts; interface RepositoryInterface { public function __construct(); public function makeEntity(); public function orderBy($attr, $dir = 'asc'); public function all($columns = ['*']); public function findBy($attribute, $value, $shouldThrowException = true); public function findById($id); public function whereIn($attribute, array $values, $columns = ['*']); public function where(array $where, $columns = ['*']); public function create(array $data); public function update($data, $id, $attribute = 'id'); public function updateOrCreate(array $attributes, array $values = []); public function delete($model); public function getLatestEntities($limit = null, $eagerLoad = []); public function insert(array $data); public function batchInsert(array $collection, $returnLastId = true); public function resetEntity(); }2.2 Repository abstract
Để nhắm tránh việc lặp lại code cùng xử lý các tác vụ tương tự nhau trong các repository khác nhau của các đối tượng, ta tạo ra 1 lớp abstract implements interface vừa tạo ở trên. Bài viết chỉ giới thiệu 1 số phương thức, các phương thức còn lại được tổng hợp tại file ở cuối bài.
<?php namespace AppRepositories; use Exception; use CarbonCarbon; use IlluminateDatabaseEloquentModel; /** * @property-read string $entityName */ abstract class Repository implements RepositoryInterface { /** * The entity. * * @var object */ protected $entity; /** * Create new repository instance. */ public function __construct() { $this->makeEntity(); } /** * Make entity by specified name. * * @throws Exception */ public function makeEntity() { if (! property_exists($this, 'entityName')) { throw new Exception('Class'.get_class($this).' must provide an attribute called 'entityName'); } $entity = app($this->entityName); if (!$entity instanceof Model) { throw new Exception($this->entityName.' must be an instance of IlluminateDatabaseEloquentModel'); } $this->setEntity($entity); } /** * Return the query builder order by the specified attribute * * @param string $attr * @param string $dir * @return IlluminateDatabaseEloquentBuilder */ public function orderBy($attr, $dir = 'asc') { return $this->entity->orderBy($attr, $dir); } /** * Get all available model instances. * * @param array $columns * @return IlluminateDatabaseEloquentCollection */ public function all($columns = ['*']) { return $this->entity->all($columns); } /** * Find a model instance by its attribute. * * @param string $attribute * @param mixed $value * @param bool $shouldThrowException * @return mixed */ public function findBy($attribute, $value, $shouldThrowException = true) { $query = $this->entity->where($attribute, $value); return $shouldThrowException ? $query->firstOrFail() : $query->first(); } /** * Find a model instance by its ID. * * @param int $id * @return mixed */ public function findById($id) { return $this->entity->findOrFail($id); } /** * Find entities by their attribute values. * * @param string $attribute * @param array $values * @param array $columns * @return IlluminateDatabaseEloquentCollection */ public function whereIn($attribute, array $values, $columns = ['*']) { return $this->entity->whereIn($attribute, $values)->get($columns); } /** * Find data by multiple fields * * @param array $where * @param array $columns * * @return mixed */ public function where(array $where, $columns = ['*']) { $this->applyConditions($where); $data = $this->entity->get($columns); $this->resetEntity(); return $data; } /** * Applies the given where conditions to the model. * * @param array $where * @return void */ protected function applyConditions(array $where) { foreach ($where as $field => $value) { if (is_array($value)) { list($field, $condition, $val) = $value; $this->entity = $this->entity->where($field, $condition, $val); } else { $this->entity = $this->entity->where($field, '=', $value); } } } /** * Create new model instance. * * @param array $data * @return mixed */ public function create(array $data) { return $this->entity->create($data); } /** * Update a model instance. * * @param array $data * @param int|object $id * @param string $attribute * @return mixed */ public function update($data, $id, $attribute = 'id') { $fillableFields = $this->entity->getFillable(); $data = array_only($data, $fillableFields); $id = is_object($id) ? $id->getKey() : $id; $this->entity->where($attribute, $id)->first()->update($data); return $this->findBy($attribute, $id); } /** * Update or Create an entity in repository * * @throws ValidatorException * * @param array $attributes * @param array $values * * @return mixed */ public function updateOrCreate(array $attributes, array $values = []) { $entity = $this->entity->updateOrCreate($attributes, $values); $this->resetEntity(); return $entity; } /** * Delete an model instance. * * @param int|object $model * @return int */ public function delete($model) { $modelKey = is_object($model) ? $model->getKey() : $model; $this->entity->destroy($modelKey); } /** * Get the paginated list of latest model instances. * * @param integer $limit * @param array|string $eagerLoad * @return IlluminateContractsPaginationLengthAwarePaginator */ public function getLatestEntities($limit = null, $eagerLoad = []) { $limit = is_null($limit) ? config('common.pagination_per_page') : $limit; return $this->entity->with($eagerLoad)->latest()->paginate($limit); } /** * Insert new record for the given model. * * @param array $data * @return int */ public function insert(array $data) { return $this->entity->insert($data); } /** * Batch inserting multiple database records. * * @param array $collection * @param bool $returnLastId * @return int|void */ public function batchInsert(array $collection, $returnLastId = true) { $records = array_map(function ($item) { $now = Carbon::now(); $item[Model::CREATED_AT] = $now; $item[Model::UPDATED_AT] = $now; return $item; }, $collection); $this->insert($records); if ($returnLastId) { return $this->entity->max($this->entity->getKeyName()); } } /** * Sets the value of entity name. * * @param Model $entity * @return self */ protected function setEntity($entity) { $this->entity = $entity; return $this; } /** * @throws RepositoryException */ public function resetEntity() { $this->makeEntity(); } }2.3 Repository Command
Ngoài cách tạo repository cho từng đối tượng bằng cách tạo trức tiếp, chúng ta có thể dùng command để tạo ra repository.
Đầu tiên, chúng ta tạo file RepositoryCommand.php và đặt nó vào trong thư mục AppConsoleCommands
<?php namespace AppConsoleCommands; use IlluminateConsoleCommand; use File; class RepositoryCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'make:repository {name : Repository name} {--model= : (Optional) Model name} '; /** * The console command description. * * @var string */ protected $description = 'Create repository for model'; private $repositoryPath; private $modelPath; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); $this->repositoryPath = config('repository.repository_path', 'app/Repositories/'); $this->modelPath = config('repository.model_path', 'AppModels'); } /** * Execute the console command. * * @return mixed */ public function handle() { $repositoryContent = '; $repositoryName = $this->argument('name'); $modelName = $this->option('model'); $repositoryPath = sprintf('%s/%s.php', trim($this->repositoryPath, '/'), $repositoryName); if ($repositoryName) { $repositoryContent = $this->getRepositoryContent($repositoryName, $modelName); } if (!File::isFile($repositoryPath)) { $this->setRepositoryContent($repositoryPath, $repositoryContent); } else { $overrideConfirm = $this->ask('Repository is existing. Are you sure you want to override it? (y/n)'); $yesAnswerList = ['y', 'yes']; if (in_array(strtolower($overrideConfirm), $yesAnswerList)) { $this->setRepositoryContent($repositoryPath, $repositoryContent); } } } protected function setRepositoryContent($repositoryPath, $repositoryContent) { File::put($repositoryPath, $repositoryContent); $this->info('Create repository succeced'); } protected function getRepositoryContent($repositoryName, $modelName = ') { $repositoryContent = "<?php namespace AppRepositories; use AppRepositoriesRepository; class {$repositoryName} extends Repository {"; if ($modelName) { $repositoryContent .= " public function model() { return " . trim($this->modelPath, ') . ' . $modelName . "::class; }"; } $repositoryContent .= " }"; return $repositoryContent; } }
Tiếp theo, khai báo RepositoryCommand vừa tạo ở trên vào file AppConsoleKernel.php
<?php namespace AppConsole; class Kernel extends ConsoleKernel { /** * The Artisan commands provided by your application. * * @var array */ protected $commands = [ .... AppConsoleCommandsRepositoryCommand::class, .... ];
Giờ đây chúng ta có thể tạo repository từ command line tương tự như tạo model, controller,.... php artian make:repository tên_của_repository --model=tên_của_model
3. Kết
Sử dụng Repository mang lại nhiều lợi ích. Từ những điều cơ bản như giảm code bị trùng lắp, ứng dụng dễ dàng để mở rộng, kiểm tra và bảo trì. Từ góc độ kiến trúc, controller của bạn sẽ không cần biết dữ liệu được lưu trữ ở đâu hay được sử lý như nào, tất cả đã có repository.
Tham khảo nội dung đầy đủ của các đoạn code trong bài viết ở đây:
- Repository Interface
- Repository Abstract Class