让php开发更优雅-Laravel篇

前言


随着开发经验的增加,也伴随团队开发的积累,规范开发显得越来越重要,本文给大家提供一些laravel开发的进阶思路和经验,让大家开发更加统一规范,代码看起来更加优雅。

1.更多使用第三方库。团队开发的时候,各个人的开发经验和思考方式不同,也有一些思维局限性,可以多多使用第三方库,统一代码开发规范。

2.抛弃传统MVC模式,在结构上分出更新的结构分层,进行代码解耦。一般分为下面几层:

  • 数据验证层(Request):负责验证请求参数,单独创建一个验证请求器,避免验证逻辑放在控制器中验证,例如:

    Laravel框架使用FormRequest Laravel中使用FormRequest进行表单验证及对验证异常进行自定义处理_51CTO博客_laravel 表单验证

    TP框架使用FormRequest thinkphp6 FormRequest,laravel 表单验证_think\request和laravel-CSDN博客

  • 控制器层(Controller):负责接收参数、验证参数、调用各个模块的服务层(可以用事务包裹,用到其他服务的可以注入多个服务)、返回响应、返回视图等

  • 服务层(Service):负责具体的业务逻辑实现,将原本控制器的负责流程按模块拆分为一个个小的服务,方便给控制器层组合调用,一般不要跨模块调用服务,服务中可以调用本模块的仓库层方法

  • 仓库层(Repository):调用模型层封装一些负责的查询,方便服务层调用,一般负责只查询本模块的内容

  • 模型层(Model):只包含默认的表属性(表名、字段等)和联表关系

  • (更多层级)

环境搭建


为了不重复造轮子和方便命令行创建文件,本文直接用laravel自带的FormRequest和第三方phpno1-architecture这两个包进行开发示例,这两个库的具体使用可以参考下面几篇文章:

https://learnku.com/articles/9763/open-source-wheel-laravel-project-architecture-extension-package

Laravel中使用FormRequest进行表单验证及对验证异常进行自定义处理_51CTO博客_laravel 表单验证

Laravel实现表单验证(多场景,适用于API)_laravel formrequest scenes-CSDN博客

  • 创建laravel项目

    bash 复制代码
    composer create-project laravel/laravel your_project_name
  • 安装phpno1/architecture并发布配置

    bash 复制代码
    composer require phpno1/architecture
    php artisan vendor:publish --provider "Phpno1\Architecture\Providers\ArchitectureServiceProvider"

    如有需要,可以修改config/architecture.php中的配置

  • 注册到服务容器

    php 复制代码
    # 在config/app.php中
        'providers' => [
            // ......
            App\Providers\ArchitectureServiceProvider::class,
        ];
  • 如果你的laravel或php版本比较高,可能有部分辅助函数已经没有了,那么你需要添加自定义函数去代替它们,例如在app/Helpers/functions.php添加下面的辅助函数

    php 复制代码
    <?php
    /**
     * 自定义 ends_with 函数
     * @param $haystack
     * @param $needle
     * @return bool
     */
    function ends_with($haystack, $needle) {
        return str_ends_with($haystack, $needle);
    }
    
    /**
     * 下划线转驼峰命名法
     * @param $string
     * @return string
     */
    function camel_case($string){
        $parts = explode('_', $string);
        foreach ($parts as $index => $part) {
            $parts[$index] = ucfirst($part);
        }
        $camelCaseString = lcfirst(implode('', $parts));
        return $camelCaseString;
    }
    
    /**
     * 驼峰命名法转下划线命名法
     * @param $string
     * @return string
     */
    function snake_case($string) {
        // 使用正则表达式将字符串转换为小写,并将非单词字符替换为下划线
        $snake = strtolower(preg_replace('/[^A-Za-z0-9-]+/', '_', $string));
        // 去除开头和结尾的下划线
        $snake = trim($snake, '_');
        return $snake;
    }

    然后修改文件composer.json,在autoload节点添加自定义函数文件

    php 复制代码
    "autoload": {
            "psr-4": {
                "App\\": "app/",
                "Database\\Factories\\": "database/factories/",
                "Database\\Seeders\\": "database/seeders/"
            },
            "files": [
                "app/Helpers/functions.php"
            ]
        },

    最后执行composer dump-autoload使其生效即可

开发示例


以用户注册流程为例

统一响应

这里为了统一响应格式,直接用第三方封装的响应数据包,具体用法参考官方文档:

laravel-response: 🤖 Provide a standardized and unified response data format for Laravel and Lumen API projects. - 为 Laravel 和 Lumen API 项目提供一个规范统一的响应数据格式。 (gitee.com)

示例

php 复制代码
Response::fail();
Response::ok();
Response::success([]);

控制器

创建控制器

bash 复制代码
php artisan make:controller UserController

控制器校验请求后调用不同 service 进行业务处理,调用 API Resource 转换资源数据返回,示例:

php 复制代码
<?php

namespace App\Http\Controllers;

use App\Http\Requests\UserRequest;
use App\Services\SmsService;
use App\Services\UserService;
use Jiannei\Response\Laravel\Support\Facades\Response;

class UserController extends Controller
{
    private $userId; //用户id
    private $userService; //用户业务层

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
        $this->userId = 1;
    }

    /**
     * 获取用户信息
     */
    public function info()
    {
        return Response::success($this->userService->getUserInfo($this->userId));
    }

    /**
     * 注册
     * 
     * @param UserRequest $request
     * @param SmsService $smsService
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Resources\Json\JsonResource
     * @throws \Illuminate\Auth\Access\AuthorizationException
     * @throws \Illuminate\Validation\ValidationException
     */
    public function register(UserRequest $request, SmsService $smsService)
    {
        //参数验证
        $request->validate('register');

        //短信验证码验证
        if (!$smsService->check($request->mobile, 'register', $request->code)){
            return Response::fail('验证码错误');
        }

        try{
            //新增用户
            $this->userService->addUser($request->all());

            //绑定推荐人...

            //其他服务操作...

            return Response::ok();
        }catch (\Exception $e){
            return Response::fail($e->getMessage());
        }
    }
}

重点事项:

  • 将注入的Illuminate\Http\Request换成自定义的FormRequest

验证器

创建基础验证器

bash 复制代码
php artisan make:request BaseRequest

创建的验证器在app/Http/Requests/BaseRequest.php

然后修改其内容为:

php 复制代码
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;

/**
 * 基础验证器
 * Class AbstractRequest
 * @package App\Http\Requests
 */
class BaseRequest extends FormRequest
{
    public $scenes = [];                //场景集合
    public $currentScene;               //当前场景
    public $autoValidate = false;       //是否注入之后自动验证

    public function authorize()
    {
        return true;
    }

    /**
     * 设置场景
     * @param $scene
     * @return $this
     */
    public function scene($scene)
    {
        $this->currentScene = $scene;
        return $this;
    }


    /**
     * 覆盖自动验证方法
     */
    public function validateResolved()
    {
        if ($this->autoValidate) {
            $this->handleValidate();
        }
    }

    /**
     * 验证方法
     * @param string $scene
     * @throws \Illuminate\Auth\Access\AuthorizationException
     * @throws \Illuminate\Validation\ValidationException
     */
    public function validate($scene = '')
    {
        if ($scene) {
            $this->currentScene = $scene;
        }
        $this->handleValidate();
    }

    /**
     * 根据场景获取规则
     * @return array|mixed
     */
    public function getRules()
    {
        $rules = $this->container->call([$this, 'rules']);
        $newRules = [];
        if ($this->currentScene && isset($this->scenes[$this->currentScene])) {
            $sceneFields = is_array($this->scenes[$this->currentScene])
                ? $this->scenes[$this->currentScene] : explode(',', $this->scenes[$this->currentScene]);
            foreach ($sceneFields as $field) {
                if (array_key_exists($field, $rules)) {
                    $newRules[$field] = $rules[$field];
                }
            }
            return $newRules;
        }
        return $rules;
    }

    /**
     * 覆盖设置 自定义验证器
     * @param $factory
     * @return mixed
     */
    public function validator($factory)
    {
        return $factory->make(
            $this->validationData(), $this->getRules(),
            $this->messages(), $this->attributes()
        );
    }

    /**
     * 最终验证方法
     * @throws \Illuminate\Auth\Access\AuthorizationException
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function handleValidate()
    {
        $instance = $this->getValidatorInstance();
        if ($instance->fails()) {
            $this->failedValidation($instance);
        }
        $this->passedValidation();
    }

    /**
     * 重写验证失败返回
     */
    protected function failedValidation(Validator $validator)
    {
        $error = $validator->errors()->all();
        throw new HttpResponseException(response()->json([
            'code' => 505, 
            'msg' => $error['0'], 
            'data' => []
        ]));
    }
}

接下来创建其他验证器的时候继承BaseRequest就可以了,比如创建一个UserRequest

bash 复制代码
php artisan make:request UserRequest

然后修改其内容为:

php 复制代码
<?php

namespace App\Http\Requests;

/**
 * 用户模块验证
 */
class UserRequest extends BaseRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * 验证规则
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'mobile' => 'required|regex:/^1[3-9]\d{9}$/',
            'password' => 'required|min:6',
        ];
    }

    /**
     * 验证字段
     * Get custom attributes for validator errors.
     *
     * @return array
     */
    public function attributes()
    {
        return [
            'mobile' => '手机号码',
            'password' => '登录密码'
        ];
    }


    /**
     * 自定义提示信息
     * Get custom messages for validator errors.
     *
     * @return array
     */
    public function messages()
    {
        return [
            '*.required' => ':attribute不能为空',
            '*.regex' => ':attribute格式不正确',
            '*.min' => ':attribute不能少于6位',
        ];
    }

    /**
     * 定义验证场景和对应的验证字段
     */
    public $scenes = [
        //注册
        'register' => [
            'mobile',
            'password'
        ],

        //登录
        'login' => [
            'mobile',
            'password'
        ],
    ];
}

服务

创建服务

bash 复制代码
php artisan phpno1:service User

上面的命令会生成app/Services/UserService.php,他会自动绑定对应的仓库App\Repository\Contracts\UserRepository

php 复制代码
<?php

namespace App\Services;

use App\Repository\Contracts\UserRepository;

class UserService
{
    /**
     * @var UserRepositoryEloquent
     */
    protected $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    /**
     * 添加用户
     */
    public function addUser($data)
    {
        $this->userRepository->create($data);
    }

    /**
     * 查找用户
     * @param $id
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function getUserInfo($id)
    {
        return $this->userRepository->find($id);
    }
}

仓库

创建仓库

bash 复制代码
php artisan phpno1:repository User

执行命令之后会生成app/Repository/Contracts/UserRepository.phpapp/Repository/Eloquent/UserRepositoryEloquent.php;我们平时写数据库查询逻辑在UserRepositoryEloquent中写就可以了

模型

创建模型

复制代码
php artisan make:model User

模型里面只保留默认的表属性和关联绑定方法即可,不处理业务

php 复制代码
<?php

namespace App\Models;

use App\Traits\SerializeDate;
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, serializeDate;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'mobile',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'updated_at',
        'remember_token',
        'email_verified_at',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [

    ];
}
相关推荐
ServBay9 小时前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户9623779544811 小时前
CTF 伪协议
php
BingoGo3 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack3 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo4 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack4 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack5 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo5 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
BingoGo6 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·laravel
JaguarJack6 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel