让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 = [

    ];
}
相关推荐
黑客Ash1 小时前
【D01】网络安全概论
网络·安全·web安全·php
->yjy1 小时前
计算机网络(第一章)
网络·计算机网络·php
阳光帅气男孩3 小时前
PhpSpreadsheet导出图片
php
周全全3 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
xiaohuatu4 小时前
CSRF保护--laravel进阶篇
vue3·laravel·csrf
Mr.Pascal4 小时前
刚学php序列化/反序列化遇到的坑(攻防世界:Web_php_unserialize)
开发语言·安全·web安全·php
建群新人小猿5 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
黑客Ela8 小时前
网络安全问题概述
安全·web安全·php
Wh1teR0se8 小时前
详解php://filter--理论
web安全·php