花了四个月打磨的 Laravel Plus 开源

花了四个月时间,经过了两个项目的打磨,我写的 Laravel Plus 终于像个样子了,可以开箱即用了。

采用最新的 PHP8 的语法,大量借鉴了 SpringBoot 中的 AOP 编程思想,全方面拥抱注解。来看一下吧。

路由和控制器

采用注解路由,避免了在控制器和 api.php 的路由文件间跳转。首先在 api.php 中注册:

use Zenith\LaravelPlus\EasyRouter;

EasyRouter::register();

接着就可以非常流畅的写路由了:

use Zenith\LaravelPlus\Attributes\Routes\GetMapping;
use Zenith\LaravelPlus\Attributes\Routes\PostMapping;
use Zenith\LaravelPlus\Attributes\Routes\Prefix;

#[Prefix(path: '/user')]
class UserController
{
    #[GetMapping(path: '/login')]
    public function login() {}

    #[PostMapping(path: '/register')]
    public function register() {}
}

获取请求参数

获取请求参数可以使用 #RequestBody 注解。先注册一个控制器中间件:

use Zenith\LaravelPlus\Middlewares\RequestBodyInjector;

abstract class Controller implements HasMiddleware
{
    public static function middleware(): array
    {
        return [
           RequestBodyInjector::class, 
        ];
    }
}

然后就可以使用注解来将 Request Body 中的参数注入到类中的,结合 Getter/Setter 就很方便,IDE 给出的提示会很友好:

use Zenith\LaravelPlus\Attributes\Routes\GetMapping;
use Zenith\LaravelPlus\Attributes\Requests\RequestBody;

class UserController extends Controller
{
    #[GetMapping(path: '/login')]
    public function login(#[RequestBody] RegisterParams $params) {}
}

class RegisterParams
{
    // The modifiers must be public or protected.
    protected string $username;

    protected string $password;
}

参数校验

一样的,你需要先注册中间件,参数校验以及填充默认值:

use Zenith\LaravelPlus\Middlewares\RequestParamsDefaultValueInjector;

abstract class Controller implements HasMiddleware
{
    public static function middleware(): array
    {
        return [
            RequestParamsDefaultValueInjector::class
            ParameterValidation::class,
        ];
    }
}

接着就可以使用 #[Param] 注解来校验参数了:

use Zenith\LaravelPlus\Attributes\Validators\Param;

class UserController extends Controller
{
    #[GetMapping(path: '/login')]
    #[Param(key: 'username', rules: 'required|string|max:32', message: 'Username is required.')]
    public function login() {}
}

也支持自定义的验证器,如下:

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class PasswordRule implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $isPass = strlen($value) >= 8 && preg_match('/[a-zA-Z]/', $value) &&
            preg_match('/\d/', $value) &&
            preg_match('/[^a-zA-Z\d]/', $value);
        if (! $isPass) {
            $fail('The :attribute must be least 8 characters and contain at least one letter, one number and one special character.');
        }
    }
}

验证规则写类名就可以了:

use Zenith\LaravelPlus\Attributes\Validators\Param;

class UserController
{
    #[GetMapping(path: '/login')]
    #[Param(key: 'username', rules: PasswordRule::class, message: 'password is error')]
    public function login() {}
}

支持参数的默认值设置:

use Zenith\LaravelPlus\Attributes\Validators\Param;
use Zenith\LaravelPlus\Attributes\Requests\RequestBody;

class UserController extends Controller
{

    #[Param(key: 'page', rule: 'integer|min:1|max:100', default: 1, required: false, message: 'page is error')]
    public function queryList(#[RequestBody] QueryParams $params) 
    {
        dump($params->getPage());       // output: 1
    }
}

DTO、VO、Bean

这里的 Bean 和 Java 中的 Bean 并非一个意思,而是 DTO 的意思,就是承载数据的对象。

use Zenith\LaravelPlus\Bean;

/**
 * @method getUsername()
 * @method setUsername()
 * @method getPassword()
 * @method setPassword()
 */
class RegisterParams extends Bean
{
    protected string $username;

    protected string $password;
}

new RegisterParams(['username' => 'bob', 'password' => 'passw0rd'])

除了自动实现 Getter/Setter 外,还支持 toArray() 以及 toJson()equal() 等方法, 还可以无限级嵌套。

use Zenith\LaravelPlus\Bean;

class User extends Bean
{
    protected Company $company;
}

class Company extends Bean
{
    protected string $name;
}

支持数组 Bean 的嵌套:

use Zenith\LaravelPlus\Bean;
use Zenith\LaravelPlus\Attributes\BeanList;

/**
 * @method Company[] getCompanies()
 */
class User extends Bean
{
    /**
     * @var Company[]
     */
    #[BeanList(value: Company::class)]
    protected array $companies;
}

支持类型转换:

use Zenith\LaravelPlus\Bean::
use Zenith\LaravelPlus\Attributes\TypeConverter;

class User extends Bean
{

    #[TypeConverter(value: BoolConverter::class)]
    protected BoolEnum $isGuest;
}

class BoolConverter
{
    public function convert(bool|string $value): BoolEnum
    {
        if ($value === 'YES' || $value === 'yes' || $value === 'y' || $value === 'Y') {
            return BoolEnum::TRUE;
        }
        if ($value === 'NO' || $value === 'no' || $value === 'N' || $value === 'n') {
            return BoolEnum::FALSE;
        }

        return $value ? BoolEnum::TRUE : BoolEnum::FALSE;
    }
}

依赖注入

Laravel 只支持通过构造函数注入,下面的示例演示了使用注解讲依赖注入到成员属性:

use Zenith\LaravelPlus\Traits\Injectable;

class UserController
{
    use Injectable;

    #[Autowired]
    private UserService $userService;

    public function register()
    {
        $this->userService->register(); 
    }
}

除此之外,还支持 #[Service]#[Component]#[Logic] 等注入的注解。

总结

总而言之,还有更多其他的功能,比如简化了 CRUD,通过 #[Value] 注入配置到类属性…自动生成接口文档、Mock 数据….

Github 任意门,欢迎点 Star 或者使用、或者参与开发、或者交流讨论。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 3周前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 62

请问下楼主是自由开发者吗?像这种四个月做开源项目没有收益全靠爱心发电吗?

3周前 评论
苏近之 (楼主) 3周前
JaguarJack

fpm 吗?如果是的话,那么注解是每次请求都解析一次吗

3周前 评论
JaguarJack (作者) 3周前
苏近之 (楼主) 3周前
苏近之 (楼主) 3周前
苏近之 (楼主) 3周前

终于有人开始做注解这件事了 :+1: 已 star

3周前 评论
苏近之 (楼主) 3周前

很好 但是我不用,你这个适合自己折腾, 不适合生产环境 :flushed:

3周前 评论
苏近之 (楼主) 3周前

感觉路由注解对于稍微复杂点的项目来说,后期维护成本比定义在 router.php 中的要高很多!

3周前 评论
苏近之 (楼主) 3周前

注解的大量使用,还可以直接通过扫描注解生成接口或者 SDK 或者其他文档,代码即文档。

file

这是通过 Laravel-Plus 直接生成的接口文档,你可以在命令行下输入 php artisan docs:build 命令即可,支持 VitePress。

3周前 评论

其他的都实践过了,佬,这是什么操作:public function queryList(#[RequestBody] QueryParams $params) 给个关键字~

3周前 评论
苏近之 (楼主) 3周前
yangweijie

支持 事务注解吗 嵌套事务?

3周前 评论
苏近之 (楼主) 3周前
Cooper

面向 Spring Boot 编程 :smiley:

3周前 评论
苏近之 (楼主) 3周前

有错别字修改下 讲 ->将:

然后就可以使用注解来讲(将) Request Body 中的...

除了 JavaC#, 还能在 Rust 项目看到这种写法,不过叫法不一样,Rust 叫宏编程。

注解能提升开发效率,就是存在一点学习成本,团队采用的话,大家都按照注释的思路去写代码,这提升不是一点半点,算是一种编程范式。下个项目就试试这个工具,支持! :+1:

3周前 评论
苏近之 (楼主) 3周前
苏近之 (楼主) 3周前
bigbug-gg (作者) 3周前
苏近之 (楼主) 3周前

:+1:

3周前 评论
苏近之 (楼主) 3周前

问个问题,如果使用#[Param] 注解来校验参数,message参数可以支持多语言吗?

3周前 评论
苏近之 (楼主) 3周前

你做的非常好,可是PHP越做越垃圾,忘了自己的初衷,也没跟上多线程并发时代,只会搞些语法糖

3周前 评论
苏近之 (楼主) 3周前
梦想星辰大海 3周前
苏近之 (楼主) 3周前
梦想星辰大海

支持在参数类中配置校验规则的注解吗?

class RegisterParams
{
    #[Param(rules: 'required|string|max:32', message: 'Username is required.')]
    protected string $username;

    protected string $password;
}
3周前 评论
苏近之 (楼主) 3周前
苏近之 (楼主) 3周前
梦想星辰大海 (作者) 3周前

本人一直在写nodejs的nest和PHP laravel、webman和go的gin等,最喜欢的还是laravel,也很喜欢nestjs的@注解,Hyperf其实是支持注解的,这套aop思想给代码整洁和易用性提供了很大的便利,希望楼主这个项目越来越好,会持续关注。

3周前 评论
苏近之 (楼主) 3周前

支持 octane吗

3周前 评论
苏近之 (楼主) 3周前

:joy:这样做的目的是啥呢?能加快开发速度还是能够让项目运行更快?

2周前 评论
苏近之 (楼主) 2周前
leirhy

看完还觉得还是直接用laravel吧,这个还得重学一遍,适合自己团队折腾 :joy:

2周前 评论
苏近之 (楼主) 2周前
panqihuan 2周前
苏近之 (楼主) 2周前
苏近之 (楼主) 2周前
还不出来 2周前
Cooper

file 他来了 :grin:

2周前 评论
苏近之 (楼主) 2周前
Cooper (作者) 2周前
苏近之 (楼主) 2周前

和 hyperf 框架很像了

2周前 评论
苏近之 (楼主) 2周前

牛逼,尽然能够全部自己实现。
不过我是利用现成的去改的
路由注解:spatie/laravel-route-attributes
数据对象:spatie/laravel-data,比如Dto,Vo等等,写了一些工具直接从数据库生成Dto和Vo,并自动添加一些基础的注解验证。还有FromCurrentUser和FromCurrentUserProperty(注入当前用户)等注解
文档的话(openapi):zircote/swagger-php和darkaonline/l5-swagger,zircote/swagger-php这个开源项目的代码很难去改,历史悠久了。为了省时间,还是硬着头皮去看了。自己写了一些zircote/swagger-php的processor去解析laravel中的路由,数据传输对象(Dto)等等,可以少写许多zircote/swagger-php注解。UI使用的github.com/Redocly/redoc
和Swagger UI

file

file

Laravel

Laravel

2周前 评论
苏近之 (楼主) 2周前
soulqq (作者) 1周前

我也在做这个事情 实现了校验和文档生成的功能 不过没有做路由通过注解实现 主要是实现swagger文档和自动化测试 不知道用人想要这个不 我在考虑要不要开源

file

file

file

3天前 评论
苏近之 (楼主) 2天前

好东西 :+1:

2天前 评论

laravel本身就够慢了,注解反射解析比较适合常驻内存的运行模式。

1天前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!