花了四个月打磨的 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 协议》,转载必须注明作者和本文链接
本帖由系统于 6个月前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 96

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

6个月前 评论
苏近之 (楼主) 6个月前
JaguarJack

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

6个月前 评论
JaguarJack (作者) 6个月前
苏近之 (楼主) 6个月前
苏近之 (楼主) 6个月前
苏近之 (楼主) 6个月前

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

6个月前 评论
苏近之 (楼主) 6个月前

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

6个月前 评论
苏近之 (楼主) 6个月前

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

6个月前 评论
苏近之 (楼主) 6个月前
xujinhui 2周前

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

file

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

6个月前 评论
鲜橙多 4个月前

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

6个月前 评论
苏近之 (楼主) 6个月前
yangweijie

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

6个月前 评论
苏近之 (楼主) 6个月前
Cooper

面向 Spring Boot 编程 :smiley:

6个月前 评论
苏近之 (楼主) 6个月前

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

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

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

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

6个月前 评论
苏近之 (楼主) 6个月前
苏近之 (楼主) 6个月前
bigbug-gg (作者) 6个月前
苏近之 (楼主) 6个月前

:+1:

6个月前 评论
苏近之 (楼主) 6个月前

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

6个月前 评论
苏近之 (楼主) 6个月前
苏近之 (楼主) 3个月前

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

6个月前 评论
苏近之 (楼主) 6个月前
梦想星辰大海 6个月前
苏近之 (楼主) 6个月前
梦想星辰大海

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

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

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

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

6个月前 评论
苏近之 (楼主) 6个月前

支持 octane吗

6个月前 评论
苏近之 (楼主) 6个月前

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

6个月前 评论
苏近之 (楼主) 6个月前
leirhy

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

6个月前 评论
苏近之 (楼主) 6个月前
panqihuan 6个月前
苏近之 (楼主) 6个月前
苏近之 (楼主) 6个月前
还不出来 6个月前
Cooper

file 他来了 :grin:

6个月前 评论
苏近之 (楼主) 6个月前
Cooper (作者) 6个月前
苏近之 (楼主) 6个月前

和 hyperf 框架很像了

6个月前 评论
苏近之 (楼主) 6个月前

牛逼,尽然能够全部自己实现。
不过我是利用现成的去改的
路由注解: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

6个月前 评论
苏近之 (楼主) 6个月前
soulqq (作者) 6个月前

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

file

file

file

6个月前 评论
苏近之 (楼主) 6个月前
siyue007 (作者) 1个月前

好东西 :+1:

6个月前 评论
苏近之 (楼主) 6个月前

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

6个月前 评论
苏近之 (楼主) 6个月前

任何语言、框架只要使用了反射,性能都会大打折扣,说实话 spring boot 也很慢,好在 java 注解提升了开发效率。应用在内部后台其实无所谓,慢点不影响啥。
如果是对外的,只能说是buff叠满了。数据库的io已经很低了,基本是走的内网连接。这里慢一点那里慢一点,接口耗时就能到一两秒。前端也这么搞,一个页面加载下来一两分钟,点一个加载转半天圈

6个月前 评论
苏近之 (楼主) 6个月前
zulien (作者) 6个月前
苏近之 (楼主) 6个月前
zulien (作者) 6个月前

今天闲暇之余观摩了老哥的作品,对代码质量和严谨性深感钦佩。
这绝对是一个极具潜力的作品。如果能持续不断地进行精细打磨未来的可能性不可限量。

在此基础上,是否可以考虑增加一些可能性?

  • 统一注解接口,提供类似中间件的处理机制,允许用户自定义注解。
    注解实体不仅限于存储注解信息,还可以包含生命周期管理以及更多的元数据(何时被扫描, 被附加注解bind于什么单位, 运行时所处容器沙箱, 触发事件, 销毁时机等)。

  • 优雅的路由注册方式,设计一种更优雅的方法来切入路由注册流程,确保在不对现有业务逻辑造成侵扰的前提下,能够更加细致地干涉业务流程。

  • 结构化缓存机制,将扫描的结果结构化并缓存至内存或外部存储中,并考虑在进程重启或重载时的数据同步问题。

以上仅是我对该项目当前架构的一些设想,我很想知道作者对于这些想法的看法如何

5个月前 评论
苏近之 (楼主) 5个月前
苏近之 (楼主) 5个月前
苏近之 (楼主) 5个月前
苏近之 (楼主) 5个月前
苏近之 (楼主) 5个月前
月光

虽然我觉得以 Laravel 目前的状态强行上这套 AOP 没有什么优势,但我仍然觉得很牛,star 了有机会看看各种实现。

5个月前 评论
苏近之 (楼主) 3个月前

github.com/next-laboratory/aop 之前写过一款aop包,兼容过laravel,但是需要生成缓存,就放弃了。后面也没怎么修改了。

4个月前 评论
苏近之 (楼主) 3个月前

注解其实挺好用的,不然Java Spring 也不会在注解上大行其道。如果只是简单的路由注解,其它很多框架都有自己的官方注解路由实现,比如说TP。或者做个像TP那样稍微配置一下自己识别路由,无须要写路由就能自动识别调用控制器。还有其它PHP的框架还实现了注解注入类似java的功能,不过按照PHP的运行效率只能算作鸡肋。laravel已经够重了,真没必要这样做

4个月前 评论
苏近之 (楼主) 3个月前
黑将军

要不叫 laravel boot 吧 :blush:

4个月前 评论
苏近之 (楼主) 3个月前
黑将军 (作者) 3个月前

发电的心去做,php真不行了

4个月前 评论
苏近之 (楼主) 3个月前

序列化有一个包可以用 github.com/spatie/laravel-data 这东西比自己写完善了很多 不用自己重复造轮子了

4个月前 评论
苏近之 (楼主) 3个月前

注解的方式要么像phalcon一样做缓存,要么常驻内存,否则就么有必要

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

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