花了四个月打磨的 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 协议》,转载必须注明作者和本文链接
请问下楼主是自由开发者吗?像这种四个月做开源项目没有收益全靠爱心发电吗?
fpm 吗?如果是的话,那么注解是每次请求都解析一次吗
终于有人开始做注解这件事了 :+1: 已 star
很好 但是我不用,你这个适合自己折腾, 不适合生产环境 :flushed:
感觉路由注解对于稍微复杂点的项目来说,后期维护成本比定义在 router.php 中的要高很多!
注解的大量使用,还可以直接通过扫描注解生成接口或者 SDK 或者其他文档,代码即文档。
这是通过 Laravel-Plus 直接生成的接口文档,你可以在命令行下输入
php artisan docs:build
命令即可,支持 VitePress。其他的都实践过了,佬,这是什么操作:public function queryList(#[RequestBody] QueryParams $params) 给个关键字~
支持 事务注解吗 嵌套事务?
面向 Spring Boot 编程 :smiley:
有错别字修改下 讲 ->将:
除了
Java
、C#
, 还能在Rust
项目看到这种写法,不过叫法不一样,Rust
叫宏编程。注解能提升开发效率,就是存在一点学习成本,团队采用的话,大家都按照注释的思路去写代码,这提升不是一点半点,算是一种编程范式。下个项目就试试这个工具,支持! :+1:
:+1:
问个问题,如果使用#[Param] 注解来校验参数,message参数可以支持多语言吗?
你做的非常好,可是PHP越做越垃圾,忘了自己的初衷,也没跟上多线程并发时代,只会搞些语法糖
支持在参数类中配置校验规则的注解吗?
本人一直在写nodejs的nest和PHP laravel、webman和go的gin等,最喜欢的还是laravel,也很喜欢nestjs的@注解,Hyperf其实是支持注解的,这套aop思想给代码整洁和易用性提供了很大的便利,希望楼主这个项目越来越好,会持续关注。
支持 octane吗
:joy:这样做的目的是啥呢?能加快开发速度还是能够让项目运行更快?
看完还觉得还是直接用laravel吧,这个还得重学一遍,适合自己团队折腾 :joy:
和 hyperf 框架很像了
牛逼,尽然能够全部自己实现。
不过我是利用现成的去改的
路由注解: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
我也在做这个事情 实现了校验和文档生成的功能 不过没有做路由通过注解实现 主要是实现swagger文档和自动化测试 不知道用人想要这个不 我在考虑要不要开源
好东西 :+1:
laravel本身就够慢了,注解反射解析比较适合常驻内存的运行模式。
任何语言、框架只要使用了反射,性能都会大打折扣,说实话 spring boot 也很慢,好在 java 注解提升了开发效率。应用在内部后台其实无所谓,慢点不影响啥。
如果是对外的,只能说是buff叠满了。数据库的io已经很低了,基本是走的内网连接。这里慢一点那里慢一点,接口耗时就能到一两秒。前端也这么搞,一个页面加载下来一两分钟,点一个加载转半天圈
今天闲暇之余观摩了老哥的作品,对代码质量和严谨性深感钦佩。
这绝对是一个极具潜力的作品。如果能持续不断地进行精细打磨未来的可能性不可限量。
在此基础上,是否可以考虑增加一些可能性?
统一注解接口,提供类似中间件的处理机制,允许用户自定义注解。
注解实体不仅限于存储注解信息,还可以包含生命周期管理以及更多的元数据(何时被扫描, 被附加注解bind于什么单位, 运行时所处容器沙箱, 触发事件, 销毁时机等)。
优雅的路由注册方式,设计一种更优雅的方法来切入路由注册流程,确保在不对现有业务逻辑造成侵扰的前提下,能够更加细致地干涉业务流程。
结构化缓存机制,将扫描的结果结构化并缓存至内存或外部存储中,并考虑在进程重启或重载时的数据同步问题。
以上仅是我对该项目当前架构的一些设想,我很想知道作者对于这些想法的看法如何
虽然我觉得以 Laravel 目前的状态强行上这套 AOP 没有什么优势,但我仍然觉得很牛,star 了有机会看看各种实现。
github.com/next-laboratory/aop 之前写过一款aop包,兼容过laravel,但是需要生成缓存,就放弃了。后面也没怎么修改了。
注解其实挺好用的,不然Java Spring 也不会在注解上大行其道。如果只是简单的路由注解,其它很多框架都有自己的官方注解路由实现,比如说TP。或者做个像TP那样稍微配置一下自己识别路由,无须要写路由就能自动识别调用控制器。还有其它PHP的框架还实现了注解注入类似java的功能,不过按照PHP的运行效率只能算作鸡肋。laravel已经够重了,真没必要这样做
要不叫 laravel boot 吧 :blush:
发电的心去做,php真不行了
序列化有一个包可以用 github.com/spatie/laravel-data 这东西比自己写完善了很多 不用自己重复造轮子了
注解的方式要么像phalcon一样做缓存,要么常驻内存,否则就么有必要
注解对于,未接触项目是福音,性能上我现在没有测试,暂时还没有使用这个功能。不过学习了,感谢楼主。