前言
使用 Laravel 有一段時間了,雖然公司專案使用的都是 Thinkphp 框架,但我個人還是比較偏好 Laravel,今天來總結我平時進行開發前的一些準備工作,如果有不合理的地方或者有更好的建議歡迎各位大佬指出糾正!
環境
PHP8 + MySQL5.7 + Nginx1.20
IDE:PhpStorm
搭建
安裝
推薦使用 composer 安裝 或者 Laravel 安裝器安裝:
Composer 安裝
composer create-project laravel/laravel example-app
Laravel 安裝器安裝
composer global require laravel/installer
laravel new example-app
具體詳見文件
配置
1、資料庫
配置根目錄下 .env
檔案
DB_CONNECTION=mysql
// host地址
DB_HOST=127.0.0.1
// 埠號
DB_PORT=3306
// 資料庫名
DB_DATABASE=laravel9
// 使用者名稱
DB_USERNAME=root
// 密碼
DB_PASSWORD=
2、時區
配置 config/app.php
檔案
// 時區修改,感覺兩者皆可,自己根據實際情況定義
'timezone' => 'PRC', // 大陸時間
或
'timezone' => 'Asia/Shanghai' // 上海時間
3、設定 Accept 頭中介軟體
(1)生成中介軟體
php artisan make:middleware AcceptHeader
(2)修改為以下內容
<?php
namespace App\Http\Middleware;
use Closure;
class AcceptHeader
{
public function handle($request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
(3)新增中介軟體
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
\App\Http\Middleware\AcceptHeader::class,
...
],
];
(4)效果
設定前:
會返回 HTML ,且響應時間和樣式很糟糕
設定後:
返回 JSON 型別,響應時間很快和格式一目瞭然
擴充套件包
只列出專案必備的幾個,如果有需要可以從下面連結裡看一下
下載量最高的 100 個 Laravel 擴充套件包推薦
1、程式碼提示工具
composer require barryvdh/laravel-ide-helper --dev
2、語言包
composer require caouecs/laravel-lang
使用:將 vendor/caouecs/laravel-lang/src/zh-CN
檔案放到 resources/lang
目錄下,如果是 laravel9 則直接放到根目錄 lang
目錄下
修改 config/app
中:
'locale' => 'zh_CN',
3、開發除錯利器(debugbar 在 dev 環境安裝)
composer require barryvdh/laravel-debugbar --dev
Response 響應
這裡不使用 dingo 進行開發,個人感覺不怎好用,我們下面自己定義
首先,我們需要在 app
目錄下建立一個 Helpers
目錄
一、封裝統一狀態碼(ResponseEnum)
在 app/Helpers
目錄下建立 ResponseEnum.php
檔案
<?php
namespace App\Helpers;
class ResponseEnum
{
// 001 ~ 099 表示系統狀態;100 ~ 199 表示授權業務;200 ~ 299 表示使用者業務
/*-------------------------------------------------------------------------------------------*/
// 100開頭的表示 資訊提示,這類狀態表示臨時的響應
// 100 - 繼續
// 101 - 切換協議
/*-------------------------------------------------------------------------------------------*/
// 200表示伺服器成功地接受了客戶端請求
const HTTP_OK = [200001, '操作成功'];
const HTTP_ERROR = [200002, '操作失敗'];
const HTTP_ACTION_COUNT_ERROR = [200302, '操作頻繁'];
const USER_SERVICE_LOGIN_SUCCESS = [200200, '登入成功'];
const USER_SERVICE_LOGIN_ERROR = [200201, '登入失敗'];
const USER_SERVICE_LOGOUT_SUCCESS = [200202, '退出登入成功'];
const USER_SERVICE_LOGOUT_ERROR = [200203, '退出登入失敗'];
const USER_SERVICE_REGISTER_SUCCESS = [200104, '註冊成功'];
const USER_SERVICE_REGISTER_ERROR = [200105, '註冊失敗'];
const USER_ACCOUNT_REGISTERED = [23001, '賬號已註冊'];
/*-------------------------------------------------------------------------------------------*/
// 300開頭的表示伺服器重定向,指向的別的地方,客戶端瀏覽器必須採取更多操作來實現請求
// 302 - 物件已移動。
// 304 - 未修改。
// 307 - 臨時重定向。
/*-------------------------------------------------------------------------------------------*/
// 400開頭的表示客戶端錯誤請求錯誤,請求不到資料,或者找不到等等
// 400 - 錯誤的請求
const CLIENT_NOT_FOUND_HTTP_ERROR = [400001, '請求失敗'];
const CLIENT_PARAMETER_ERROR = [400200, '引數錯誤'];
const CLIENT_CREATED_ERROR = [400201, '資料已存在'];
const CLIENT_DELETED_ERROR = [400202, '資料不存在'];
// 401 - 訪問被拒絕
const CLIENT_HTTP_UNAUTHORIZED = [401001, '授權失敗,請先登入'];
const CLIENT_HTTP_UNAUTHORIZED_EXPIRED = [401200, '賬號資訊已過期,請重新登入'];
const CLIENT_HTTP_UNAUTHORIZED_BLACKLISTED = [401201, '賬號在其他裝置登入,請重新登入'];
// 403 - 禁止訪問
// 404 - 沒有找到檔案或目錄
const CLIENT_NOT_FOUND_ERROR = [404001, '沒有找到該頁面'];
// 405 - 用來訪問本頁面的 HTTP 謂詞不被允許(方法不被允許)
const CLIENT_METHOD_HTTP_TYPE_ERROR = [405001, 'HTTP請求型別錯誤'];
// 406 - 客戶端瀏覽器不接受所請求頁面的 MIME 型別
// 407 - 要求進行代理身份驗證
// 412 - 前提條件失敗
// 413 – 請求實體太大
// 414 - 請求 URI 太長
// 415 – 不支援的媒體型別
// 416 – 所請求的範圍無法滿足
// 417 – 執行失敗
// 423 – 鎖定的錯誤
/*-------------------------------------------------------------------------------------------*/
// 500開頭的表示伺服器錯誤,伺服器因為程式碼,或者什麼原因終止執行
// 服務端操作錯誤碼:500 ~ 599 開頭,後拼接 3 位
// 500 - 內部伺服器錯誤
const SYSTEM_ERROR = [500001, '伺服器錯誤'];
const SYSTEM_UNAVAILABLE = [500002, '伺服器正在維護,暫不可用'];
const SYSTEM_CACHE_CONFIG_ERROR = [500003, '快取配置錯誤'];
const SYSTEM_CACHE_MISSED_ERROR = [500004, '快取未命中'];
const SYSTEM_CONFIG_ERROR = [500005, '系統配置錯誤'];
// 業務操作錯誤碼(外部服務或內部服務呼叫)
const SERVICE_REGISTER_ERROR = [500101, '註冊失敗'];
const SERVICE_LOGIN_ERROR = [500102, '登入失敗'];
const SERVICE_LOGIN_ACCOUNT_ERROR = [500103, '賬號或密碼錯誤'];
const SERVICE_USER_INTEGRAL_ERROR = [500200, '積分不足'];
//501 - 頁首值指定了未實現的配置
//502 - Web 伺服器用作閘道器或代理伺服器時收到了無效響應
//503 - 服務不可用。這個錯誤程式碼為 IIS 6.0 所專用
//504 - 閘道器超時
//505 - HTTP 版本不受支援
/*-------------------------------------------------------------------------------------------*/
}
二、建立業務異常捕獲 Exception 檔案
在 app/Exceptions
目錄下建立 BusinessException.php
檔案用於業務異常的丟擲
<?php
namespace App\Exceptions;
use Exception;
class BusinessException extends Exception
{
/**
* 業務異常建構函式
* @param array $codeResponse 狀態碼
* @param string $info 自定義返回資訊,不為空時會替換掉codeResponse 裡面的message文字資訊
*/
public function __construct(array $codeResponse, $info = '')
{
[$code, $message] = $codeResponse;
parent::__construct($info ?: $message, $code);
}
}
三、自定義返回異常
修改 app/Exceptions
目錄下的 Handler.php
檔案
<?php
namespace App\Exceptions;
use App\Helpers\ApiResponse;
use App\Helpers\ResponseEnum;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;
class Handler extends ExceptionHandler
{
use ApiResponse;
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
public function render($request, Throwable $exception)
{
// 如果是生產環境則返回500
if (!config('app.debug')) {
$this->throwBusinessException(ResponseEnum::SYSTEM_ERROR);
}
// 請求型別錯誤異常丟擲
if ($exception instanceof MethodNotAllowedHttpException) {
$this->throwBusinessException(ResponseEnum::CLIENT_METHOD_HTTP_TYPE_ERROR);
}
// 引數校驗錯誤異常丟擲
if ($exception instanceof ValidationException) {
$this->throwBusinessException(ResponseEnum::CLIENT_PARAMETER_ERROR);
}
// 路由不存在異常丟擲
if ($exception instanceof NotFoundHttpException) {
$this->throwBusinessException(ResponseEnum::CLIENT_NOT_FOUND_ERROR);
}
// 自定義錯誤異常丟擲
if ($exception instanceof BusinessException) {
return response()->json([
'status' => 'fail',
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
'data' => null,
'error' => null,
]);
}
return parent::render($request, $exception);
}
}
四、封裝 API 返回的統一訊息(ApiResponse)
在 app/Helpers
目錄下建立 ApiResponse.php
檔案
<?php
namespace App\Helpers;
use App\Helpers\ResponseEnum;
use App\Exceptions\BusinessException;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
trait ApiResponse
{
/**
* 成功
* @param null $data
* @param array $codeResponse
* @return JsonResponse
*/
public function success($data = null, $codeResponse=ResponseEnum::HTTP_OK): JsonResponse
{
return $this->jsonResponse('success', $codeResponse, $data, null);
}
/**
* 失敗
* @param array $codeResponse
* @param null $data
* @param null $error
* @return JsonResponse
*/
public function fail($codeResponse=ResponseEnum::HTTP_ERROR, $data = null, $error=null): JsonResponse
{
return $this->jsonResponse('fail', $codeResponse, $data, $error);
}
/**
* json響應
* @param $status
* @param $codeResponse
* @param $data
* @param $error
* @return JsonResponse
*/
private function jsonResponse($status, $codeResponse, $data, $error): JsonResponse
{
list($code, $message) = $codeResponse;
return response()->json([
'status' => $status,
'code' => $code,
'message' => $message,
'data' => $data ?? null,
'error' => $error,
]);
}
/**
* 成功分頁返回
* @param $page
* @return JsonResponse
*/
protected function successPaginate($page): JsonResponse
{
return $this->success($this->paginate($page));
}
private function paginate($page)
{
if ($page instanceof LengthAwarePaginator){
return [
'total' => $page->total(),
'page' => $page->currentPage(),
'limit' => $page->perPage(),
'pages' => $page->lastPage(),
'list' => $page->items()
];
}
if ($page instanceof Collection){
$page = $page->toArray();
}
if (!is_array($page)){
return $page;
}
$total = count($page);
return [
'total' => $total, //資料總數
'page' => 1, // 當前頁碼
'limit' => $total, // 每頁的資料條數
'pages' => 1, // 最後一頁的頁碼
'list' => $page // 資料
];
}
/**
* 業務異常返回
* @param array $codeResponse
* @param string $info
* @throws BusinessException
*/
public function throwBusinessException(array $codeResponse=ResponseEnum::HTTP_ERROR, string $info = '')
{
throw new BusinessException($codeResponse, $info);
}
}
五、建立控制器基類
1、在 app/Http/controller
目錄下建立一個 BaseController.php
作為 Api 的基類,然後在 BaseController.php
這個基類中繼承 Controller,並引入封裝 API 返回的統一訊息(ApiResponse)
<?php
namespace App\Http\Controllers;
use App\Helpers\ApiResponse;
class BaseController extends Controller
{
// API介面響應
use ApiResponse;
}
六、使用
1、返回成功資訊
return $this->success($data);
2、返回失敗資訊
return $this->fail($codeResponse);
3、丟擲異常
$this->throwBusinessException($codeResponse);
4、分頁
return $this->successPaginate($data);
引數輸入校驗
一、建立
在 App\Helpers
目錄下建立 VerifyRequestInput.php
檔案,並引入 ApiResponse
,這樣可以更便捷地校驗表單引數,其中 verifyData
方法可以自定義校驗欄位及規則
<?php
namespace App\Helpers;
use App\Helpers\ResponseEnum;
use App\Exceptions\BusinessException;
use Illuminate\Validation\Rule;
trait VerifyRequestInput
{
use ApiResponse;
/**
* 驗證ID
* @param $key
* @param null $default
* @return mixed|null
* @throws BusinessException
*/
public function verifyId($key, $default=null)
{
return $this->verifyData($key, $default, 'integer|digits_between:1,20');
}
/**
* 驗證是否為整數
* @param $key
* @param null $default
* @return mixed|null
* @throws BusinessException
*/
public function verifyInteger($key, $default=null)
{
return $this->verifyData($key, $default, 'integer');
}
/**
* 驗證是否為數字
* @param $key
* @param null $default
* @return mixed|null
* @throws BusinessException
*/
public function verifyNumeric($key, $default=null)
{
return $this->verifyData($key, $default, 'numeric');
}
/**
* 驗證是否為字串
* @param $key
* @param null $default
* @return mixed|null
* @throws BusinessException
*/
public function verifyString($key, $default=null)
{
return $this->verifyData($key, $default, 'string');
}
/**
* 驗證是否為布林值
* @param $key
* @param null $default
* @return mixed|null
* @throws BusinessException
*/
public function verifyBoolean($key, $default=null)
{
return $this->verifyData($key, $default, 'boolean');
}
/**
* 驗證是否為列舉
* @param $key
* @param null $default
* @param array $enum
* @return mixed|null
* @throws BusinessException
*/
public function verifyEnum($key, $default=null, array $enum=[])
{
return $this->verifyData($key, $default, Rule::in($enum));
}
/**
* 自定義校驗引數
* @param $key string 欄位
* @param $default string 預設值
* @param $rule string 驗證規則
* @return mixed|null
* @throws BusinessException
*/
public function verifyData($key, $default, $rule)
{
$value = request()->input($key, $default);
$validator = \Validator::make([$key => $value], [$key => $rule]);
if (is_null($value)){
$this->throwBusinessException(ResponseEnum::CLIENT_PARAMETER_ERROR);
}
if ($validator->fails()){
$this->throwBusinessException(ResponseEnum::CLIENT_PARAMETER_ERROR, $validator->errors()->first());
}
return $value;
}
}
2、使用
需要在 App\Http\Controllers\BaseController
這個控制器基類中引入 VerifyRequestInput
use App\Helpers\VerifyRequestInput;
// 驗證表單引數輸入請求
use VerifyRequestInput;
3、案例:
有一個 index
方法,我們在獲取引數時使用 verifyId
來校驗請求的引數
public function index()
{
$id = $this->verifyId('id', null);
}
當我們請求時,因為傳入的引數是字串
http://127.0.0.1:8000/api/user/index?id=xddd
所以返回 The id must be an integer
,提示id必須為整數
建立服務層 Service
如果專案比較小,介面較少,業務邏輯放在 controller 和 model 層就可以。否則就需要建立一個 Service 層來存放一些較複雜些的業務邏輯。
一、在 app
目錄下,建立名叫 Services
的資料夾
二、在新建立的 Services
目錄下建立基類 BaseService.php
,採用單例模式避免對記憶體造成浪費,也方便呼叫
<?php
namespace App\Services;
use App\Helpers\ApiResponse;
class BaseService
{
// 引入api統一返回訊息
use ApiResponse;
protected static $instance;
public static function getInstance()
{
if (static::$instance instanceof static){
return self::$instance;
}
static::$instance = new static();
return self::$instance;
}
protected function __construct(){}
protected function __clone(){}
}
三、使用
例如要實現一個獲取使用者資訊的功能
1、在 Service 層建立一個 UserService.php
的檔案
<?php
namespace App\Services;
use App\Services\BaseService;
class UserService extends BaseService
{
// 獲取使用者資訊
public function getUserInfo()
{
return ['id' => 1, 'nickname' => '張三', 'age' => 18];
}
}
2、在控制器 UserController
中增加一個 info
方法,並呼叫服務層中的 getUserInfo() 方法
use App\Services\UserService;
public function info()
{
$user = UserService::getInstance()->getUserInfo();
return $this->success($user);
}
3、返回
監聽sql語句
1、建立監聽器
php artisan make:listener QueryListener
修改 handle
方法
public function handle(QueryExecuted $event)
{
// 只在測試環境下輸出 log 日誌
if (!app()->environment(['testing', 'local'])) {
return;
}
$sql = $event->sql;
$bindings = $event->bindings;
$time = $event->time; // 毫秒
$bindings = array_map(function ($binding) {
if (is_string($binding)) {
return (string)$binding;
}
if ($binding instanceof \DateTime) {
return $binding->format("'Y-m-d H:i:s'");
}
return $binding;
}, $bindings);
$sql = str_replace('?', '%s', $sql);
$sql = sprintf($sql, ...$bindings);
Log::info('sql_log', ['sql' => $sql, 'time' => $time . 'ms']);
}
2、註冊監聽事件
在系統的服務提供者 App\Providers\EventServiceProvider
中註冊監聽事件
protected $listen = [
...
QueryExecuted::class => [
QueryListener::class,
],
];
3、執行 sql 檢視日誌
可以在日誌檔案中看到 sql 的執行時間、sql語句、毫秒數
[2022-05-08 22:45:04] local.INFO: sql_log {"sql":"select * from `user` where `user`.`id` = 3 limit 1","time":"51.59ms"}
專案地址
不喜勿噴,如有錯誤或建議歡迎指出提出,持續更新中…
本作品採用《CC 協議》,轉載必須註明作者和本文連結