關於 Repository 的設計模式

Jinrenjie發表於2017-09-27

file

為了提高程式碼的 重用率,我只在基礎類上寫了,每個模型所必須要的操作,CURD 在到具體的 Repository 上去做實現:

<?php

namespace App\Repositories;

use App\Exceptions\GeneralException;

/**
 * 抽象的 Repository 類
 *
 * @package App\Repositories
 */
abstract class BaseRepository
{
    /**
     * 根據主鍵查詢
     * 
     * @param $id
     * @param $trashed
     * @return mixed
     */
    public function find($id, $trashed = false)
    {
        if ($trashed) {
            return $this->query()->withTrashed()->findOrFail($id);
        }
        return $this->query()->findOrFail($id);
    }

    /**
     * 查詢資源集合
     *
     * @param bool $query_string
     * @param bool $keys
     * @param int $paginate
     * @param bool $trashed
     * @return mixed
     * @throws GeneralException
     */
    public function getAll($query_string = false, $keys = false, $paginate = 15, $trashed = false)
    {
        try {
            $query = $this->query();
            if ($query_string && is_array($keys)) {
                foreach ($keys as $key => $value) {
                    $query->orWhere($value, 'like', "%$query_string%");
                }
            }

            if ($trashed) {
                $query->withTrashed();
            }

            return $query->paginate($paginate);
        } catch (Exception $exception) {
            throw new GeneralException('未知錯誤,導致查詢失敗', 500);
        }
    }

    /**
     * 建立查詢構造器
     *
     * @return mixed
     */
    public function query()
    {
        return call_user_func(static::MODEL.'::query');
    }

    /**
     * 序列化模型例項
     *
     * @param array $attributes
     * @return mixed
     */
    abstract protected function serialization(array $attributes);
}
<?php

namespace App\Repositories;

use App\Models\Company;
use App\Exceptions\GeneralException;

/**
 * Class CompanyRepository
 *
 * @author George <jinrenjie@me.com>
 * @package App\Repositories
 */
class CompanyRepository extends BaseRepository
{
    /**
     * 定義資料模型
     */
    const MODEL = Company::class;

    /**
     * 建立企業資訊
     *
     * @param array $attributes
     * @return Company
     * @throws GeneralException
     */
    public function store(array $attributes)
    {
        $compnay = $this->serialization($attributes);

        try {
            $compnay->save();
            return $compnay;
        } catch (Exception $exception) {
            throw new GeneralException('建立企業資訊失敗');
        }
    }

    /**
     * 更新企業資訊
     *
     * @param Company $company
     * @param array $attributes
     * @return Company
     * @throws GeneralException
     */
    public function update(Company $company, array $attributes)
    {
        if (is_array($attributes)) {
            foreach ($attributes as $key => $value) {
                $company->$key = $value;
            }
            $company->saveOrFail();
            return $company;
        }
        throw new GeneralException('要更新的屬性必須是陣列');
    }

    /**
     * 刪除企業資訊
     *
     * @param Company $company
     * @param bool $force
     * @return bool|null
     * @throws GeneralException
     */
    public function delete(Company $company, $force = false)
    {
        try {
            return $force ? $company->forceDelete() : $company->delete();
        } catch (Exception $exception) {
            throw new GeneralException('刪除企業資訊失敗');
        }
    }

    /**
     * 序列化使用者輸入
     *
     * @param array $attributes
     * @return Company
     */
    protected function serialization(array $attributes)
    {
        $company = self::MODEL;
        $company = new $company();

        $company->title = $attributes['title'];
        $company->introduction = array_get($attributes, 'introduction', null);
        $company->url = array_get($attributes, 'url', null);
        $company->logo = array_get($attributes, 'logo', null);
        $company->address = array_get($attributes, 'address', null);
        $company->contact = array_get($attributes, 'contact', null);

        return $company;
    }
}

這一層只處理資料的 CURD,並返回對應的操作結果,除了丟擲異常勁量不涉及響應和請求……這樣Controller 裡只要呼叫方法而無需考慮異常處理。

<?php

namespace App\Http\Controllers\Interfaces;

use App\Models\Company;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Repositories\CompanyRepository;
use App\Extensions\Library\MaterialHandling;
use App\Http\Requests\Interfaces\Company\DeleteRequest;
use App\Http\Requests\Interfaces\Company\UpdateRequest;
use App\Http\Requests\Interfaces\Company\CreateRequest;
use App\Http\Requests\Interfaces\Company\ChangeLogoRequest;
use App\Http\Requests\Interfaces\Company\QueryResourceRequest;
use App\Http\Requests\Interfaces\Company\QueryCollectionRequest;

/**
 * Class CompanyController
 *
 * @package App\Http\Controllers\Interfaces\User
 */
class CompanyController extends Controller
{
    use MaterialHandling;

    /**
     * 定義儲存 CompanyRepository 的例項
     * @var CompanyRepository
     */
    protected $companyRepository;

    /**
     * CompanyController constructor.
     * @param $companyRepository
     */
    public function __construct(CompanyRepository $companyRepository)
    {
        $this->companyRepository = $companyRepository;
    }

    /**
     * 獲取企業資訊集合
     *
     * @param QueryCollectionRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function index(QueryCollectionRequest $request)
    {
        //獲取分頁大小
        $paginate = $request->get('paginate', 15);
        //獲取模糊查詢字串
        $query_string = $request->get('query_string', false);
        //獲取是否顯示軟刪除的資料
        $trashed = $request->get('trashed', false);

        //定義需要模糊查詢的欄位
        $keys = [
            'title',
            'introduction',
            'address'
        ];

        $collections = $this->companyRepository
            ->getAll($query_string, $keys, $paginate, $trashed);

        return $this->success($collections);
    }

    /**
     * 根據企業 ID 獲取企業資訊
     *
     * @param Company $company
     * @param QueryResourceRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function show(Company $company, QueryResourceRequest $request)
    {
        return $this->success($company);
    }

    /**
     * 建立企業資訊
     *
     * @param CreateRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function create(CreateRequest $request)
    {
        $attributes = $request->all();
        $company = $this->companyRepository->store($attributes);
        return $this->created($company);
    }

    /**
     * 更新企業資訊
     *
     * @param Company $company
     * @param UpdateRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function update(Company $company, UpdateRequest $request)
    {
        $attributes = $request->all();
        $company = $this->companyRepository->update($company, $attributes);
        return $this->updated($company);
    }

    /**
     * 刪除企業資訊
     *
     * @param Company $company
     * @param DeleteRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function delete(Company $company, DeleteRequest $request)
    {
        $this->companyRepository->delete($company);
        return $this->deleted();
    }

    /**
     * 更改企業 Logo
     *
     * @param Company $company
     * @param ChangeLogoRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function changeLogo(Company $company, ChangeLogoRequest $request)
    {
        $file = $request->file('logo');
        $path = $this->store($file, $request->user());
        $result = $this->companyRepository->update($company, ['logo' => $path]);
        return $this->updated($result);
    }
}

控制器裡的自定義響應,我參照了@王舉 的文章:Laravel5.5+passport 放棄 dingo 開發 API 實戰,讓 API 開發更省心

我還把 鑑權表單驗證及錯誤資訊 放到了 專門的 Request 裡面去處理這樣整體結構也更加清晰了,比如我的註冊請求:

<?php

namespace App\Http\Requests\Auth;

use Illuminate\Support\Facades\Cache;
use Illuminate\Foundation\Http\FormRequest;

/**
 * 使用者註冊請求
 *
 * @author George <jinrenjie@me.com>
 * @package App\Http\Requests\Auth
 */
class RegisterRequest extends FormRequest
{
    /**
     * 定義授權規則
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * 定義驗證規則
     *
     * @return array
     */
    public function rules()
    {
        return [
            'username' => 'required|alpha_dash|min:6|max:16|unique:users',
            'password' => 'required|confirmed|min:8|max:32',
            'mobile' => 'required|min:11|max:11|unique:users',
        ];
    }

    /**
     * 定義錯誤訊息
     *
     * @return array
     */
    public function messages()
    {
        return [
            'username.required' => '請填寫使用者名稱',
            'username.alpha_dash' => '使用者名稱只能是字母、數字、及下劃線',
            'username.min' => '使用者名稱不得少於6個字元',
            'username.max' => '使用者名稱不得超過16個字元',
            'username.unique' => '該使用者名稱稱已存在,請使用其他名稱註冊',
            'password.required' => '請填寫密碼',
            'password.confirmed' => '兩次填寫的密碼不一致',
            'password.min' => '密碼最少8個字元',
            'password.max' => '密碼最多32個字元',
            'mobile.required' => '請填寫您的手機號碼',
            'mobile.min' => '您輸入的號碼不滿11位',
            'mobile.max' => '號碼不得超過11位',
            'mobile.unique' => '該手機號碼已被註冊',
        ];
    }

    /**
     * 驗證使用者輸入的驗證碼是否正確
     *
     * @param $validator
     */
    public function withValidator($validator)
    {
        $validator->after(function ($validator) {
            if (Cache::get($this->get('mobile'), false) !== $this->get('verification')) {
                $validator->errors()->add('verification', '你輸入的驗證碼有誤');
            } else {
                Cache::forget($this->get('mobile'));
            }
        });
    }
}

當然缺點就是要增加很多程式碼量,但是對於團隊開發來說這樣也許比較規範吧……

如果你有更好的分離方法,歡迎一起討論,謝謝!

相關文章