DuckPhp 1.2.4 发布,终极架构,文档完善了

2020-05-16 10:14:21 +08:00
 dvaknheo
项目地址: https://github.com/dvaknheo/duckphp
作者 QQ: 85811616
官方 QQ 群: 714610448

phpstan 第 7 级检查通过。php-cs-fixer 代码风格通过。phpunit 全覆盖测试通过。

名称统一成了 DuckPhp,和命名空间一样。

重复一下 DuckPhp 的特性:
+ 可以在不改动库文件下,替换所有实现。
我用 CodeIgniter 的时候,基本上没见到个 CodeIgniter 系统代码是官方原版的。都是各种魔改。
Composer 时代之后,直接改 vendor 的文件确实是不可取的
所以满足在不改动库文件下,替换所有实现这是对现代框架的基本要求。不实现这个目标的框架没存在意义。

+ 可以非固定全站使用的框架。
很多框架都要要求你配置整站才能使用,DuckPhp 很灵活,不需要配置成整站。
甚至启用自带扩展能做到连 Web 服务器都不用配置的单一入口文件模式。
还有一种玩法是 A ,B 两个项目独立开发,然后合在同一个服务器上。
其他框架很麻烦,命名空间都是用 App,路由处理还要折腾很多。
DuckPhp 不同项目不同命名空间,B 项目作为 A 项目的插件使用。

+ DuckPhp 秉着 代码和 DuckPhp 关联越少越好的原则
核心工程师写的非业务核心代码才会和 DuckPhp 耦合。
应用工程师写的业务代码是不和 DuckPhp 耦合的。

+ DuckPhp 是无第三方依赖的单一的 Composer Library
这点很重要么? 很重要。引用的第三方组件出了 Bug 人家升级了怎么办。
Library 则很方便的插入第三方框架里使用。
DuckPhp 工程的初始化就是 复制 template 目录,修改相应模板。
甚至可以直接运行 template 目录。

--

从 CURD 角度的应用工程师角度来看,基本不需要动什么。

使用 DuckPhp 的感觉就是,默认的就已经足够了。

应用工程里那些高级的东西,让核心工程师来做。
而核心工程师呢,如果不满足,那么就调选项。
如果选项还不行,那就入口类里调整。
更高级的是自己做组件和替换默认组件。
对于应用工程师,就看那些 Helper 函数多出来什么就够了。
var_dump(ControllerHelper::GetExtendStaticMethodList());

最高级的玩法是把当前工程 A 作为扩展给 B 工程复用。
也没什么难度,入口类里加额外代码就是。(引入 AppPluginTrait)



1.2.3 版本,我在 yii3 demo 上内嵌了个 DuckPhp 作为实现版本

https://github.com/dvaknheo/yii-demo

在入口 index.php 处拦截如果 DuckPhp 能实现,则用 DuckPhp 的实现版本,否则继续
所有 DuckPhp 版本的实现的文件,都在一个目录下,可以对比一下。
https://github.com/dvaknheo/yii-demo/tree/for_duckphp/app
之前也折腾过 laravel 的 demo, 结果丫自己的 auth demo 我看的都绕半天,何况其他小白。

用 phpunit-codecoverage 看了一下 thinkphp 5.1 hello world 。1052 / 19241 行代码。
也就是不干什么事情都至少有 1000 行代码空跑了。DuckPhp 是 339/5252 行,所以 DuckPhp 是高效的。
当然,这只是对性能的粗略估计。比如 一个 autoloader 的 file_exists 判断性能上就能顶很多行
(尤其是我在 wsl1 环境下 IO 性能惨不忍睹。
至于 Laravel,我有空看一下是不是跑了一万行代码。

DuckPhp 的 swoole 支持是有的,只不过现在不作为重点了,workerman 有空的话我再看看。
DuckPhp 切换成这些模式是无缝的。 只是我不觉得 web 不是 swoole/workerman 该做的主战场。

现在想找个能很好的演示 DuckPhp 的工程来做。演示一下 DuckPhp 的魅力。
6028 次点击
所在节点    PHP
41 条回复
ruoge3s
2020-05-16 21:33:08 +08:00
# 20 楼那个,在构造器里加权限判断,耦合太严重了。😄
ruoge3s
2020-05-16 21:35:43 +08:00
1. 建议楼主可以多看看一看设计模式。
2. 自己写一些框架组件没什么问题的。有些东西自己写了,才会更能感受到别人写的一部分东西确实很优秀。借鉴人家优秀的东西,让自己的框架、组件更优秀,让自己更优秀吧。
dvaknheo
2020-05-16 22:53:29 +08:00
@ruoge3s 控制器的构造器就是干这样的活的,还觉得耦合严重。。。
控制器又不需要继承系统基本类,一看就明白。
给你看个耦合严重的控制器例子吧。

```
namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
```
这时候,你就的去看 BaseController 里有什么,会影响到什么。
还有 AuthorizesRequests DispatchesJobs ValidatesRequests 都有什么功能。
似乎这些不管也无所谓。

控制器基本不会复用,复用的是 service,业务逻辑层。

keep it simple stupid.
dvaknheo
2020-05-16 23:10:07 +08:00
DuckPhp 1.2.3 到 1.2.4 版本有个显著改变是之前组件都没继承基类,实现什么接口。只使用 SingletonEx trait 做可变单例。

按 MyComponent::G()->init(array $optioins, $context=null): this;
这样的约定初始化(可以想象成 ServiceProvider.

1.2.4 之后,各组件使用 MyComponent extends DuckPhp\Core\ComponentBase , ComponentBase implements ComponentInterface,这样的方式,多了一个继承,内部实现一个接口。

实际上你编写扩展,也只需要满足约束 MyComponent::G()->init(array $options, $context=null): this; 就行了,没必要 实现 ComponentInterface 的所有接口

ComponentBase 帮你过滤你只需要的 $options 选项。 拆分 init() 为 initOptions($options) initContext() 两个空方法以便于你的继承。

以上这些,只是核心工程师需要了解的。应用工程师用不到。
影响应用工程师的是少量选项,方法名又因为名称不满意,细调了,不兼容了。

核心工程师有没有会被版本升级恶心的事呢? 有,继承系统类的时候,或许系统类的类型会调整成强类型,签名不同了。

最后,还可能会有什么问题呢?
Helper 的函数名字冲突,比如你在旧版本的 Helper 加了个函数, 升级后函数名相同,但签名不同,这也是不太可能出现的情况。
beastk
2020-05-16 23:54:06 +08:00
其实我还在用 php5.2
jqh
2020-05-17 10:52:19 +08:00
@dvaknheo

不是说核心工程师造轮子,是核心工程师调轮子。应用工程师一个 DuckPhp 命名空间的东西也用不到。
DuckPhp 用 phpstan 检查过规范,phpunit 单元覆盖测试 100% 。
--------------------------------------------------------
“应用工程师一个 DuckPhp 命名空间的东西也用不到”,你这句话翻译一下就是:这个框架基本什么基础功能都不提供。

那你让“核心工程师”调什么?你有提供 HTTP 请求和响应处理功能有吗?配置文件功能有吗?文件处理功能有吗?缓存工具有吗?参数表单验证工具有吗?还有更多基础功能你都没有,那你怎么让“核心工程师”不造轮子?

最终的结果就是各种所谓的“核心工程师”拿到你的框架就是群魔乱舞各种瞎写,最后出来的就是惨不忍睹、很难维护的糟糕代码,这也是很多所谓自研框架公司的现状。


+ blade 模板 违背了 PHP 代码就是视图的原则
+ 滥用 ArrayIterator foreach, 使得没法 dump
+ orm 使得调试更麻烦了
+ 退化到路由表了。有简单的文件路由不用。
+ 中间件使得调用关系复杂化。
--------------------------------------------------------
PHP 代码是视图的原则??这都 2020 年了,您还崇尚 HTML 和 PHP 混编呢?这写出来的代码能维护?大清早就灭亡了亲;
orm 使得调试更麻烦了,laravel 提供了多种方式可以让 ORM 转化成 sql 调试,调试虽然麻烦了一点,但 ORM 带来的便利程度远远大于麻烦程度;
退化到路由表了。有简单的文件路由不用,中间件使得调用关系复杂化,从这个描述就说明,你根本没理解 laravel 路由和中间件的好用之处。

中间件最大的好处就是能把各种与业务无直接关联的代码抽象出来,放在中间件里面,每个业务控制器 action 就可以通过配置轻松增减、切换各种不同的中间件,而不需要改动业务的一行代码。比如日志收集、登陆验证、接口权限判断等等,我甚至可以实现各种逻辑不同的登陆中间件,随意切换,如果你把这些跟业务无关的代码放在控制器中,那最终只会造成你代码杂乱、臃肿和难以维护。
那么 laravel 的路由结合中间件的配置,简直不要太好用,我可以轻松给各种路由进行分组;尤其是方便了开发者写扩展包,可以以最大的自由度定义扩展包路由,以及其实用的中间件,简直太牛逼了。其他的大部分框架,都没有这么好用的路由,也难怪 laravel 的生态如此强大,可以说 laravel 的高质量的第三包扩展包的数量可以吊打任何一款 PHP 框架。

可见一个成熟的设计是多么重要,细节和生态才是一个框架的根本。laravel 的方方面面的设计,其实都是兼顾了普通开发者和第三方扩展包开发者的需求的,但很可惜,很多人并不理解其中的意义。

而楼主说的中间件使得调用关系复杂化,这完全可以说是你的水平不够,中间件的配置入口就是公共配置和路由配置两种,能复杂到哪去?堆栈调用复杂?我宁愿看多几十行堆栈调用,也不愿写业务代码和与业务无关代码混杂在一起像乱麻一样的代码。


之前在我本机弄了 laravel 自带的 auth 的版本,发现 laravel auth 连我都没能弄清楚,怎么可能会有国内项目用他那套做验证
--------------------------------------------------------
你这个把我看笑了,你是看不起其他程序员,还是太看得起自己?? laravel auth 你都整不明白,还好意思大言不惭说这个不行那个不行?建议楼主保持谦逊的态度,对不懂的东西不要乱评判,看了楼主的发帖纪录,就是一直在评判自己没搞懂的东西
dvaknheo
2020-05-17 11:33:29 +08:00
@jqh
“应用工程师一个 DuckPhp 命名空间的东西也用不到”,你这句话翻译一下就是:这个框架基本什么基础功能都不提供。
----
是有这么一个基本用不到的功能:是你可以无缝替换成另一个框架。 翻译就是无耦合。

HTTP 请求, $_GET,$_POST 还没死绝呢。PHP 官方又没出一个自己的 psr 接口。PHP 为什么比 Java 容易明白没?
Framework 和 Library 的区别知道么。 非得所有的都要要你 framework 里的么?
没关系,核心工程师怎么偷换实现都不会乱, 反正应用工程师看不到。

PHP 代码是视图的原则??这都 2020 年了,您还崇尚 HTML 和 PHP 混编呢?这写出来的代码能维护?大清早就灭亡了亲;
那么非 PHP 的视图模板里写 sql 你见过没。Widget 里一个超级对象你见过没。PHP 视图的原则是不做计算,只输出,而不是混编。
[Smarty 出了这么多年,PHPer 又退化到用 PHP 来写视图模板了]

orm 使得调试更麻烦了,laravel 提供了多种方式可以让 ORM 转化成 sql 调试,调试虽然麻烦了一点,但 ORM 带来的便利程度远远大于麻烦程度;
调试是重要工作。而且 orm 需要多学一层,所以又过滤了一部分懒人。

PHP 最大的好处之一就是随时改业务代码。
DuckPhp 好处之一就是你可以把你的中间件做成独立的工程独立使用。应该说是可以把独立工程变成“中间件”,之前回答里都有了,我上面没演示的是 view 和 配置,也可以 重载。
DuckPhp 好处之二就是这些东西都是显式表达的,你不会看到“哇靠,这东西从哪里冒出来的,究竟错在哪里”。

如果你把业务代码放在控制器里,那等到 10k 行控制器的时候维护起来就痛苦死了。

日志收集,登陆验证,接口权限判断这些,都是核心工程师的职责。所以他们会告诉应用工程师:继承这个控制器基类就够了。想改这些,在基类里改就是。

我水平不够,所以只能用 PHP 这种语言。现在高水平的都去用 Java ,Go 了。
所以我写的框架也只是适用于那些想用 PHP 快速开发的人。
[Laravel 是很优雅的框架,不应该使用 PHP 这种不优雅的语言,应该使用更好的语言来写。]

Laravel 默认的 auth 代码的流程我后来是弄清楚了。搞那么复杂的目的是为了有效的过滤智力低下的懒惰的 CURD 程序员。
nicoljiang
2020-05-17 11:39:34 +08:00
Laravel 是很优雅的框架,不应该使用 PHP 这种不优雅的语言,应该使用更好的语言来写。

+1
Laravel 的作者继续耕耘 .NET 方向多好
jqh
2020-05-17 12:37:47 +08:00
@dvaknheo

```php
public function postLogin(Request $request)
{
$vaptcha = Vaptcha::make();

// 验证验证码是否正确
if (! $vaptcha->validate()) {
// 验证不通过,返回错误提示信息到前端
return $this->error($vaptcha->getError());
}

$credentials = $request->only([$this->username(), 'password']);
$remember = (bool) $request->input('remember', false);

// 验证参数
$validator = Validator::make($credentials, [
$this->username() => 'required',
'password' => 'required',
]);

if ($validator->fails()) {
return $this->validationErrorsResponse($validator);
}

// 登陆并返回成功信息
if ($this->guard()->attempt($credentials, $remember)) {
return $this->sendLoginResponse($request);
}

// 返回失败信息
return $this->validationErrorsResponse([
$this->username() => $this->getFailedLoginMessage(),
]);
}
```

这里我截取一段使用 laravel auth 实现的登陆功能,换成你的说法,就是你口中“应用工程师”该写业务代码,多简洁易懂,这代码不好维护吗?跟业务无关吗?

而其中$this->guard()->attempt 这样的 auth 底层实现,就是你口中“核心工程师”写的登陆的轮子,框架内置的轮子不比大部分“核心”工程师写的优质、好用许多吗?

laravel 这样设计的更强大之处在于,“应用工程师”甚至不必关心登陆验证的数据存储细节,例如你是想使用 session 登陆、还是 auth2.0 登陆、还是缓存 token 之类的等等,都可以通过配置文件和中间件随意切换,不需要改动一行业务代码,这不是更简单了吗?而登陆到底是要用 session 还是 auth2,这个可以交给“核心工程师”去实现。

而且不仅如此,laravel 的几乎所有组件都是这样的模式,系统把大部分功能都抽象成了一个统一的简单易用的功能接口,“应用工程师”写业务代码并不需要关心这些功能的具体实现,只需要简单的调用数行代码就行,可以把专注点放在业务上,而这些底层组件可以采用第三方扩展包也可以由“核心工程师”自己编写,然后通过配置文件切换而不用影响业务代码。

你所考虑的,laravel 早就想到了。
dvaknheo
2020-05-17 14:02:54 +08:00
@jqh 这是一个例子恰好演示了其糟糕性。
单是返回就有四种可能

return $this->error($vaptcha->getError());
return $this->validationErrorsResponse($validator);
return $this->sendLoginResponse($request);
return $this->validationErrorsResponse([$this->username() => $this->getFailedLoginMessage(),]);

而且注意到的是,按常理,最后一个才是正常返回。取反放最后就是。
但是这样还没完。
validationErrorsResponse 接受的参数又是 Validator ,又是 array 。还好 php 是弱类型。
可是 $this->getFailedLoginMessage() 又从哪里来? 是否前面的调用会影响状态。
$this->guard()->attempt($credentials, $remember)

这个 guard() 也是怎么冒出来的,为什么要和控制器耦合,这个 attepm 词也很少见。
为什么不是 this->attempt($credentials, $remember)

下面是我重构的结果。

```
class MyController
{
public function postLogin(Request $request)
{
// 输入处理,放前面,不必节省性能。
$remember = (bool) $request->input('remember', false);
$credentials = $request->only([$key_username, 'password']);
//这也算输入吧
$key_username= $this->username();

//验证码服务
$error = VaptchaService::make()->validate();
if ($error) {
// 验证不通过
return $this->error($error);
}

//兼容,如果 service 有方式获得这个 $guard,则不需要输入。
$guard = $this->guard();

//登录服务
$error = LoginService::make()->login($guard,$key_username,$credentials,$remeber);

if($error){
if(is_array($error)){
$error[$key_username] = $this->getFailedLoginMessage();
}
return $this->validationErrorsResponse($error);
}
return $this->sendLoginResponse($request);
}
}
class VaptchaService
{
// 省略 make 静态方法。
public function validate()
{
// 验证验证码是否正确
if (! Vaptcha::make()->validate()) {
return $vaptcha->getError();
}
return null;
}
}
class LoginService
{
// 省略 make 静态方法
public function login($guard,$key_username,$credentials,$remember)
{
// 验证参数
$validator = Validator::make($credentials, [
$key_username => 'required',
'password' => 'required',
]);
if ($validator->fails()) {
return $validator;
}
// 登录验证
$flag=$guard->attempt($credentials, $remember);
if (!$flag) {
return [ $key_username => "something wrong"];
}
return null;
}
}
```

如果使用 DuckPhp 你大概会这么写。

```
class MyController
{
// 登录页面
public function login()
{
C::Show([],'login');
}
//处理 post
public function do_login()
{
// 输入处理,放前面,不必节省性能。
$remember = (bool) C::POST('remember', false);
$credentials = C::SG()->_POST();

$error=null;
try{
//验证码服务
VaptchaService::G()->validate();
//登录服务,返回用户信息。
$info = LoginService::G()->login($credentials,$remeber);
//保存 session 数据
SessionService::G()->setLoginUserInfo($info);

C::Show(get_defined_vars(),'login-done');
}catch(\Throwable $ex){
$error = $ex->getMessage();
C::Show(get_defined_vars(),'login');
}
}
}
```
为什么要调 3 个服务解决问题,而不是一个,
第一个是处理验证码的,和 web 平台有关的特殊服务
第二个是主要业务,测试的时候,可以命令行运行,获得信息,不必通过 web
第三个是 session 系统管理,管控所有 session 系统,也是和 web 平台有关的特殊服务
jqh
2020-05-17 14:39:34 +08:00
@dvaknheo 你暴露了,就凭你不知道 guard 和 attempt 方法还说自己弄懂了 laravel auth....,guard 和 attempt 就是 auth 的组成部分。
jqh
2020-05-17 14:42:29 +08:00
return $this->error($vaptcha->getError());
return $this->validationErrorsResponse($validator);
return $this->sendLoginResponse($request);
return $this->validationErrorsResponse([$this->username() => $this->getFailedLoginMessage(),]);

这些只是响应数据的方法,没必要展示出来而已。

而你这些 service 只是业务代码的结构约定划分,跟框架没有半毛钱关系,我如果愿意也可以这样划分。而你这个 sessionservice 就是画蛇添足,使用了 laravel auth,业务层根本不必关心登陆验证是要使用 session 还是 token,这些根本不必跟业务代码耦合。
dvaknheo
2020-05-17 15:05:19 +08:00
@jqh 问题是为什么一个简单的登录,会搞出那么多东西出来? 真的会有人重写这个 guard 的实现么?
控制器的 $this->guard() 要查找多少代码理解。这也就是 Laravel 不能用 类名 /方法 文件路由的原因。
我刚才查了手头的 laravel 6 的 例子,没找到你这段代码,不知道你这是 laravel 几的版本。

业务层必须能用命令行运行,最好是无状态的。Web 控制器层不做业务。 控制器不能 dump 起来一堆方法都不知道干什么的。

不要指望应用工程师通读了 Illuminate\Auth 和 Illuminate\Foundation\Auth 之后才开始工作。
love51money
2020-05-18 08:55:14 +08:00
关注。楼主辛苦。
yxn1910
2020-05-18 10:02:17 +08:00
我一直有个很巨大的疑问,都已经用上 laravel 的人,相比于直接上 spring boot 有哪些非用不可的理由?
1.相较其他框架更大开发效率?
2.相较其他框架更快代码执行效率?
3.巨大的投入产出比?
4.投入时间进行学习后更利于找到工作?
5.更加优雅?
DavidNineRoc
2020-05-18 11:39:50 +08:00
@yxn1910
你说的都没用,最直接的公司用 PHP
iidestiny
2020-05-21 09:55:49 +08:00
@dvaknheo 看来楼主是 TP 用得比较多么吗?
iidestiny
2020-05-21 10:02:42 +08:00
@jqh 赞👍 评价得很到位。laravel auth 那条我确实是笑了。。。。
iidestiny
2020-05-21 10:09:17 +08:00
还是给楼主一个赞,坚持自己。
dvaknheo
2020-05-21 10:51:51 +08:00
@iidestiny 我是 CodeIgniter 出身的,
一条原则就是,能不使用系统自带的就不用系统自带的。
也就是我的想法:框架只做最少的事情。
CodeIgniter 的优点是你不会怕:这东西哪里来的。
缺点嘛,太多了没必要说了。

TP 我用得不如 Laravel 多。但 Laravel 没有我能感觉到的优点
如果说有,那就是有人说的,现在给 laravel 做的第三方包比较多。
拿来就用,不必关心细节。
而之前 TP 之类,都是给项目而不能直接使用。
DuckPhp 一个卖点就是你可以直接把你的项目做为第三方包。

laravel 的 auth 真的很差啊
而且我不认为 auth 这些东西应该放在默认的框架里。

其他框架的用户系统 demo 都很容易懂,很容易改。
laravel 的用户系统 demo 是你们水平不够不要改。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/672263

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX