+5

Chia sẻ về làm base trong Laravel

Chào các bạn, hôm nay mình viết bài này là chia sẻ kinh nghiệm bản thân chứ không phải là 1 quy ước hay quy định gì trong cấu trúc của laravel cả, cũng như không phải là tối ưu hơn. Chỉ là mình thấy khi làm như vậy sẽ dễ trong việc custom code và dễ xử lý.

Thường xây dựng 1 file Base cho từng phần

Ở đây trong mỗi namespace mình thường hay thêm 1 file Base cho nó. Ví dụ nếu như mình tạo 1 folder Models để chứa các các file Model mình sẽ tạo 1 file là BaseModel có nội dung kiểu như

<?php
namespace App\Models;

class BaseModel extends Model
{

}

Sau đó với mỗi 1 file Model mình đều extends với BaseModel này.

Tương tự với Controller cũng vậy. Ví dụ nếu như tại namespace app\Html\Controllers\Api mình viết riêng 1 cái Base cho nó

<?php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Response;


class BaseController extends Controller
{
    public function __construct()
    {
    }
}

Tại sao việc nên có những file Base thế này, với mình có 1 mục đích đấy là khi chúng ta muốn có 1 hàm viết chung nhất thì ta đều viết trong hàm Base này. Như vậy ta giảm thiểu việc phải viết lại nhiều lần ở trong các file. Hoặc ta có thể tối ưu hơn trong các phần ta đều có thể viết trong hàm Base này hoặc use các class mà mọi phần đều dùng đến nó, giảm việc phải use nhiều lần. Ví dụ như trong Model mình muốn thêm một phần tìm kiếm mà phân biệt chữ hoa chữ thường. Để làm như vậy thì cần phải viết whereBinary. Và cái này thường không có ở trong mặc định của laravel nên ta có thể viết trong BaseModel.

<?php

namespace App\Models;

use App\Models\Traits\EloquentTrait;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    public function scopeWhereBinary($query, $field, $value)
    {
        return $query->whereRaw('BINARY LOWER(' . $field . ') = "' . strtolower($value) . '"');
    }
}

Như vậy tại tất cả các model ta đều có thể dùng $this->model->whereBinary($fiield, $value); Hoặc như trong Controller phần api, mình viết việc return response theo 1 chuẩn nào đó, nếu như viết trong từng controller thì sẽ có thể viết rất nhiều trong controller. Vậy ta có thể viết trong BaseController

<?php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Response;


class BaseController extends Controller
{
    public function __construct()
    {
    }

    public function responseError($apiCodeKey, $errors = [], $customData = [])
    {
        return api_error($apiCodeKey, $errors, $customData);
    }

    public function responseSuccess($data = [], $statusCode = 200)
    {
        return api_success($data, $statusCode);
    }

    public function response404()
    {
        return abort(Response::HTTP_NOT_FOUND);
    }
}

Từ đó trong các file controller hoàn toàn ta có thể dùng các hàm này, và việc muốn thay đổi cấu trúc trả về ta không cần phải sửa từng file Controller.

Tạo 1 hệ thống Service

Theo như theo mô hình MVC cũng như trong Laravel hỗ trợ thì chúng ta thường dùng việc lấy database trực tiếp luôn qua Model từ Controller sau đó trả về View. Cách này khá ổn nhưng theo kinh nghiệm bản thân của mình thường khi làm việc mình ít khi dùng trực tiếp Model trực tiếp trong Controller mà dùng qua 1 hệ thống Service. Như vậy cách thức làm việc của mình thường sẽ là: Controller -> Service -> Model Và như phần trên thì service mình luôn có 1 BaseService và mình thường có BaseService như sau

<?php
namespace App\Services;

use DB;

class BaseService
{
    protected $model;

    public function create($params)
    {
        return $this->model->create($params);
    }

    public function update($id, $params)
    {
        $model = $this->model->find($id);
        $model->update($params);

        return $this->model->find($id);
    }

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

        return $item ? $item->delete() : true;
    }

    public function find($id, $with = null)
    {
        $query = $this->model;
        if ($with) {
            $query = $query->with($with);
        }

        return $query->find($id);
    }

    public function deleteMore($ids)
    {
        return $this->model->destroy($ids);
    }

    public function deleteList($params)
    {
        try {
            DB::beginTransaction();

            foreach ($params['ids'] as $id) {
                $this->delete($id);
            }

            DB::commit();

            return true;
        } catch (\Exception $e) {
            DB::rollBack();

            \Log::debug($e);

            return false;
        }
    }
}

Thường tại đây mình viết sẵn các hàm thường sử dụng nhất với Database đó là create/update/delete/deleteList. Và khi sử dụng trong Controller mình viết như sau:

<?php
namespace App\Http\Controllers\Api;

use App\Services\Api\PostService;
use App\Http\Requests\Api\PostCreateRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class PostController extends BaseController
{
    protected $service;

    public function __construct(PostService $service)
    {
        $this->service = $service;
    }

    public function store(PostCreateRequest $request)
    {
        $param = $request->all();
        $item = $this->service->create($param);

        return $this->responseSuccess(compact('item'));
    }

    public function update(PostCreateRequest $request, $id)
    {
        $params = $request->all();
        $item = $this->service->update($id, $params, $isMyData);

        if ($item) {
            return $this->responseSuccess();
        }

        return $this->responseError('api.code.common.update_failed');
    }

    public function destroy(Request $request, $id)
    {
        $result = $this->service->delete($id, $isMyData);
        if ($result) {
            return $this->responseSuccess();
        }

        return $this->responseError('api.code.common.delete_failed');
    }
}

Với cách viết như thế này có thể các bạn sẽ thắc mắc tại sao phải qua service, không ta có thể dùng luôn Model có phải nhanh hơn không. Khi đó chỉ việc Model::create($params) là xong. Nhưng đấy là chỉ đúng trong trường hợp các bạn chỉ muốn thêm các Model 1 cách đơn giản không cần phải xử lý dữ liệu. trong trường hợp trong form gửi đến lại có cả 1 field là ảnh thì bạn sẽ xử lý như thế nào? hoặc lại thông qua Api file ảnh lại là base64 bạn sẽ xử lý ở đâu? Nếu xử lý ở Controller thì lại phải viết rất nhiều lần. Với việc upload ảnh dùng base64 mình thường viết như sau: Trong file base Service

protected function  uploadFile($param, $field, $folder)
    {
        list($extension, $content) = explode(';', $param[$field]);
        $tmpExtension = explode('/', $extension);
        $fileName = Carbon::now()->timestamp . '.' . $tmpExtension[1];
        $content = explode(',', $content)[1];
        $test = Storage::put('public/' . $folder . '/' . $fileName, base64_decode($content));

        return $fileName;
    }

Trong file Service của phần nào đó.

public function create($param)
    {
        if (isset($param['image_source']) && $param['image_source']) {
            $param['image'] = $this->uploadFile($param, 'image_source', 'banner');
            unset($param['image_source']);
        }

        return parent::create($param);
    }

Như vậy trong phần Controller ta chỉ cần gọi hàm create($param) của service này là xong.

Trên đây là kinh nghiệm bản thân nên có thể đúng hoặc sai, có thể có cách nào khác tốt hơn mình rất mong được chia sẻ của các bạn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí