代码: https://github.com/dvaknheo/duckphp 作者 QQ: 85811616 官方 QQ 群: 714610448
安装
composer require dvaknheo/duckphp # 用 require
./vendor/bin/duckphp --help # 查看有什么指令
./vendor/bin/duckphp --create # --full # 创建工程
为什么叫 DuckPHP ? 取名鸭子类型的典故。 动态语言中经常提到鸭子类型,所谓鸭子类型就是:如果走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子( If it walks like a duck and quacks like a duck, it must be a duck )。鸭子类型是编程语言中动态类型语言中的一种设计风格,一个对象的特征不是由父类决定,而是通过对象的方法决定的。
Web 框架这么多,为什么要用你的框架。 首先,DuckPHP 所有组件都可以替换,这是现代 PHP 框架必备的品质, 出现什么安全问题,替换官方组件就可以。不必等着 composer update。
其次,DuckPHP 本着普通程序员和核心程序员两个层级的使用观点,普通程序员根本不会触及到 DuckPHP 的代码,只要简单的教学就能使用。 核心程序员,可以由浅及深扩展甚至魔改框架。
这也涉及到另一个特点,DuckPHP 更自由开放, 不会让你的 工程代码限制于 App 或 app 命名空间。 你写的 DuckPHP 工程 可以作为 插件用于其他 DuckPHP 工程( 旧框架没这个功能。
DuckPHP 支持 composer,但无引用第三方组件。无引用第三方组件对系统的可靠性很重要。比如某框架用了第三方旧组件,或许会引发安全问题不可控。
DuckPHP 的适应性也是很强的,不像某些框架,只能用于全站。DuckPHP 可以在网站子目录里使用,如果不支持 path_info,也可以调整。 一般的框架的配合是很难处理的。 但是 DuckPHP 很容易插入其他框架里提前使用,你也很容易在 DuckPhp 不处理的时候接其他框架。
DuckPHP 的代码层次分明, 用主类把其他所有独立类连在一起。你很容易可以写 DuckPHP 的扩展。你可以很容易读懂。template 目录,附带了好些 demo,但文档还没跟上。 工程方面,做了 phpunit 全覆盖测试。 用了 psr-2 的标准格式化代码。希望有一小撮人能搞起来, 杜绝我现在这种闭门造车的情况。
末了,贴一份完全 demo 源文件在 template/public/demo.php
<?php declare(strict_types=1);
/**
* DuckPHP
* From this time, you never be alone~
*/
namespace {
require_once(__DIR__.'/../../autoload.php'); // @DUCKPHP_HEADFILE
//头文件可以自行修改。
}
// 以下部分是核心程序员写。
namespace MySpace\Base
{
use \DuckPhp\Core\View;
use \DuckPhp\Ext\CallableView;
// 默认的 View 不支持函数调用,我们这里替换他。
class App extends \DuckPhp\App
{
protected function onInit()
{
// 本例特殊,这里演示函数调用的 CallableView 代替系统的 View
$this->options['callable_view_class'] = 'MySpace\View\Views';
View::G(CallableView::G());
////
return parent::onInit();
}
}
//服务基类, 为了 XXService::G() 可变单例。
class BaseService
{
use \DuckPhp\SingletonEx;
}
// 模型基类, 为了 XXModel::G() 可变单例。
class BaseModel
{
use \DuckPhp\SingletonEx;
}
} // end namespace
// 助手类
namespace MySpace\Base\Helper
{
class ControllerHelper extends \DuckPhp\Helper\ControllerHelper
{
// 一般不需要添加东西,继承就够了
}
class ServiceHelper extends \DuckPhp\Helper\ServiceHelper
{
// 一般不需要添加东西,继承就够了
}
class ModelHelper extends \DuckPhp\Helper\ModelHelper
{
// 一般不需要添加东西,继承就够了
}
class ViewHelper extends \DuckPhp\Helper\ViewHelper
{
// 一般不需要添加东西,继承就够了
}
} // end namespace
// 以下部分是普通程序员写的。不再和 DuckPhp 的类有任何关系。
namespace MySpace\Controller {
use MySpace\Base\Helper\ControllerHelper as C;
use MySpace\Service\MyService;
class Main
{
public function __construct()
{
//设置页眉页脚。
C::setViewWrapper('header', 'footer');
}
public function index()
{
//获取数据
$output = "Hello, now time is " . C::H(MyService::G()->getTimeDesc());
$url_about = C::URL('about/me');
C::Show(get_defined_vars(), 'main_view'); //显示数据
}
}
class about
{
public function me()
{
$url_main = C::URL('');
C::setViewWrapper('header', 'footer');
C::Show(get_defined_vars());
}
}
} // end namespace
namespace MySpace\Service
{
use MySpace\Base\Helper\ServiceHelper as S;
use MySpace\Base\BaseService;
use MySpace\Model\MyModel;
class MyService extends BaseService
{
public function getTimeDesc()
{
return "<" . MyModel::G()->getTimeDesc() . ">";
}
}
} // end namespace
namespace MySpace\Model
{
use MySpace\Base\Helper\ModelHelper as M;
use MySpace\Base\BaseModel;
class MyModel extends BaseModel
{
public function getTimeDesc()
{
return date(DATE_ATOM);
}
}
}
// 把 PHP 代码去掉看,这是可预览的 HTML 结构
namespace MySpace\View {
class Views
{
public function header($data)
{
extract($data); ?>
<html>
<head>
</head>
<body>
<header style="border:1px gray solid;">I am Header</header>
<?php
}
public function main_view($data)
{
extract($data); ?>
<h1><?=$output?></h1>
<a href="<?=$url_about?>">go to "about/me"</a>
<?php
}
public function about_me($data)
{
extract($data); ?>
<h1> OK, go back.</h1>
<a href="<?=$url_main?>">back</a>
<?php
}
public function footer($data)
{
?>
<footer style="border:1px gray solid;">I am footer</footer>
</body>
</html>
<?php
}
}
} // end namespace
// 以下部分是核心程序员写。
// 这里是入口,单一文件下要等前面类声明
namespace {
$options = [];
$options['namespace'] = rtrim('MySpace\\', '\\'); //项目命名空间为 MySpace, 你可以随意命名
$options['is_debug'] = true; // 开启调试模式
$options['skip_app_autoload'] = true; // 本例特殊,跳过 app 用的 autoload 免受干扰
$options['skip_setting_file'] = true; // 本例特殊,跳过设置文件
\DuckPhp\App::RunQuickly($options, function () {
});
} // end namespace
DuckPHP 的前身是 DNMVCS,因为太拗口,所以改名了。这个名字应该可以吧。
1
mokeyjay 2019-12-11 22:16:20 +08:00
大致看完后有 3 点个人的想法:
1、单字母函数 /方法很丑,仿佛回到了 N 年前的 ThinkPHP 2、demo 中还出现了不遵守 PSR 规范的情况 3、php 参杂 html ……你认真的? |
2
dvaknheo OP 因为要一个文件展示所有东西,所以用 php 参杂 html。 正常情况下是写在 view 文件夹里的。
Model 中可以全 static 方法, 反正也不会改变。 Service 中的 G 方法。 在 test/JsonRpcExtTest 里有个例子: TestService::G(JsonRpcExt::Wrap(TestService::class)); $data=TestService::G()->foo(); 把本地调用,一下子变成远程调用。 普通程序员用到的方面。 Service 层必须要用 G 方法而不能用 静态方法的,只有 SessionService 这个特殊的用于管理 Session 的 Service 了。 核心程序员用一个 G 方法 替换掉默认的类,总比配置 超长东西容易记住。 比如容器别名之类,你还得去找这个容器的别名是什么来替换。 use ControllerHelper as C。这个单字母类, 是为了减少输入。 |
3
jfcherng 2019-12-11 22:29:53 +08:00
命名風格太亂了
protected $exceptionHandlers protected $default_exception_handler public function assignExceptionHandler public function on_error_handler public static function Hook public static function RunQuickly public function _Parameters trait Core_Handler trait HelperTrait 我感覺所有能用的風格都用上了... |
4
dvaknheo OP protected $exceptionHandlers // 内部变量,可 override
protected $default_exception_handler // 内部变量,不建议 override。 public function assignExceptionHandler // 对外动态方法 public function on_error_handler // 不得不公开动态方法,不建议外部使用 public static function Hook // 对外静态方法 public static function RunQuickly // 对外静态方法 public function _Parameters // 不得不公开静态方法,不建议外部使用 trait Core_Handler // 下划线本文件内的功能分块。 trait HelperTrait // Trait 后缀 表示是 Trait |
5
agdhole 2019-12-11 23:07:20 +08:00 1
有 minggeJS 内味了
|
6
hefish 2019-12-12 08:06:42 +08:00
看了 demo,不想跟进。很讨厌单字母方法。 另外还分核心程序员,普通程序员,可能是为了展示可替代的特性,但觉着麻烦。
|
8
dvaknheo OP 鉴于单字母方法很令人讨厌。那么 template/public/demo2.php 就把单字母去掉了。
核心程序员,普通程序员。 那就改为核心代码和业务代码吧。 还有,因为有人懒得用设置服务器,我们也加了不用设置服务器就能看到结果的方法。 分出的 四种 Helper 一个人写代码也没必要这么折腾,那就用 App 的静态方法够了。 这样就只有两个地方用到 DuckPhp 命名空间了。 我只改动了 demo,没去改 DuckPhp 的代码。 ```php <?php declare(strict_types=1); /** * DuckPHP * From this time, you never be alone~ */ namespace { require_once(__DIR__.'/../../autoload.php'); // @DUCKPHP_HEADFILE //头文件可以自行修改。 } // 以下部分是核心代码。 namespace MySpace\Base { // 默认的 View 不支持函数调用,我们这里替换他。 class App extends \DuckPhp\App { protected function onInit() { // 本例特殊,这里演示函数调用的 扩展 CallableView 代替系统的 View $this->options['ext']['DuckPhp\Ext\CallableView'] = [ 'callable_view_class' => 'MySpace\View\Views', ]; //// return parent::onInit(); } } } // end namespace // 以下部分是业务代码 namespace MySpace\Controller //控制器 { use MySpace\Base\App; use MySpace\Service\MyService; class Main { public function __construct() { App::setViewWrapper('header', 'footer');//设置页眉页脚。 } public function index() //主页 { //获取数据 $output = "Hello, now time is " . App::H(MyService::getTimeDesc()); $url_about = App::URL('about/me'); App::Show(get_defined_vars(), 'main_view'); //显示数据 } } class about { public function me() //about/me { $url_main = App::URL(''); App::setViewWrapper('header', 'footer'); App::Show(get_defined_vars());// 默认的 view 名称没了。 } } } // end namespace namespace MySpace\Service { use MySpace\Base\App; use MySpace\Model\MyModel; class MyService { public static function getTimeDesc() { return "<" . MyModel::getTimeDesc() . ">"; } } } // end namespace namespace MySpace\Model { use MySpace\Base\App; class MyModel { public static function getTimeDesc() { return date(DATE_ATOM); } } } // 把 PHP 代码去掉看,这是可预览的 HTML 结构 namespace MySpace\View { class Views { public function header($data) { extract($data); ?> <html> <head> </head> <body> <header style="border:1px gray solid;">I am Header</header> <?php } public function main_view($data) { extract($data); ?> <h1><?=$output?></h1> <a href="<?=$url_about?>">go to "about/me"</a> <?php } public function about_me($data) { extract($data); ?> <h1> OK, go back.</h1> <a href="<?=$url_main?>">back</a> <?php } public function footer($data) { ?> <footer style="border:1px gray solid;">I am footer</footer> </body> </html> <?php } } } // end namespace // 以下部分是核心代码 // 入口文件,默认在 public/index.php namespace { $options = []; $options['namespace'] = rtrim('MySpace\\', '\\'); //项目命名空间为 MySpace, 你可以随意命名 $options['is_debug'] = true; // 开启调试模式 $options['skip_app_autoload'] = true; // 本例特殊,跳过 app 用的 autoload 免受干扰 $options['skip_setting_file'] = true; // 本例特殊,跳过设置文件 //没设置服务器,那就用 _r 作为路由吧 $options['ext']['DuckPhp\Ext\RouteHookOneFileMode']=[ 'key_for_action' => '_r', 'key_for_module' => '', ]; \DuckPhp\App::RunQuickly($options); } // end namespace ``` |
9
weirdo 2019-12-12 10:29:27 +08:00
看到 github 页面里那个 和其他框架简单对比 的表格。。
哈哈哈哈 ,楼主加油! |
10
sadfQED2 2019-12-12 14:05:51 +08:00 via Android
我觉得吧,现在 php 的框架没必要造了,足够多了,我们公司的底层框架开源六七年了,就 60 多个 star,还都是我们内部员工。(我们注册用户今年刚刚过亿,全是这套框架撑起来了)
|
11
dvaknheo OP @sadfQED2 求一下你们框架的地址。
现在的国内 PHP7 可用框架并不多。也就只有 Laravel 和 TP 两个可选而已。 Laravel 的厉害之处在于宣传。这也就是在代码之外的功夫。 我现在碰到的也不是代码问题,虽然有些 todo 列表。 文档是最大的问题,我对我写的文档没信心,总觉得写的不知道能看懂。 如果有人问起,我能说有什么缺的。 但目前就是缺乏反馈的状态。 |
12
yxzblue 2019-12-12 15:33:38 +08:00
有点看不懂
|
13
sadfQED2 2019-12-12 16:20:40 +08:00 via Android
@dvaknheo 在 github 上开源了,但是没写任何解释,没写任何 wiki,你不是我们公司的话,或许很难上手
|
14
ben1024 2019-12-12 16:22:47 +08:00
加油 ing
|
15
sadfQED2 2019-12-12 16:53:36 +08:00 via Android
@dvaknheo 地址 https://github.com/zhangchu/ko 真的太难找了,我翻了半天才翻出来
|
16
dvaknheo OP @sadfQED2 过了一下,成不了 CodeIgniter 那样的通用框架。 最别扭的是 方法名都加上了返回类型前缀,和 PHP 的能省则省相冲突。php 的命名空间 5.3 就出来了。如果刚开始没用命名空间还可以接受。
不能替换默认组件。额外好些功能,有些功能甚至要设置数据库表结构才能用。可以拆分到其他项目。 DuckPHP 提供的 Logger ,Pager 都是最小化的,如果有更好解决方案可以替换的,甚至 DB 类都可以替换(教程文档就有替换成 thinphp-orm 的。 说起来我里面也有 facades 的扩展。 DuckPhp\Ext\FacadesAutoLoader。 我宁愿用单字母静态方法而不愿用 facades 的一个原因就是 facades 的东西不好调试。 |
17
sadfQED2 2019-12-12 17:43:31 +08:00 via Android
@dvaknheo 有些问题和我们历史遗留代码有关,业务代码迭代快 10 年了,框架不是随便能改的。而且公司自己的框架,只为自己公司项目考虑,没必要替换组件
|
18
terrywater 2019-12-24 15:02:06 +08:00
请问一下:工具是为了解决问题而生,你的框架,比 Yii2 框架的优势体现在哪里?我在介绍里面没有看出来
对于你的描述里面的框架的优势,yii2 框架也有这些优势,而且实现的非常好,现在的能满足自己所用,为什么要写框架呢? 如果是做技术研究,提升技能,本人还是支持的 如果是做公司的业务,本人是反对的。 |
19
terrywater 2019-12-24 15:05:52 +08:00
看了一下您的介绍: https://github.com/dvaknheo/duckphp/blob/master/tutorial.md
加入了 service 层,关于这个,本人认为可以在 yii2 的基础上,自己加一层就行了,封装起来就可以了,这样比自己造一个框架实用的多 fecmall,基于 yii2 开发的真正开源商城,在 yii2 框架上封装了 services 层: http://www.fecmall.com/doc/fecshop-guide/develop/cn-2.0/guide-fecmall-service-abc.html |
20
ywisax 2019-12-24 23:16:03 +08:00
|
21
dvaknheo OP @ywisax 无第三方依赖,是解决有这样的场景:A 框架 1.1 用到 symfony/routing 3.4,而 symfony/routing 主版本已经升级到 4.3,有些地方和 3.4 不兼容。 这时候回头发现 3.4 有漏洞。 升级到 3.4.1。
那些部署上去的 A 框架,谁没事去更新? |
22
dvaknheo OP @terrywater
yii2 能热更新么? 就是保持 vendor 文件夹的内容不变,更改某个系统类的实现。 我这几天碰到的实力: 装 sdebug 替代 xdebug 之后,phpunit 不认。 我一看 里面代码, 一堆 final class :( 除了自己硬改代码,别无他法。 第三方依赖,我这里补充一下,如果你觉得有更合适的,可以替换之。 比如 Html 编码函数。zend framework3 的比我自己实现的,以及 laravel 实现的,就考虑得更周全。改起来也很容易, 添加 MyProject/Base/App (extends DuckPHP\App) 的_H($str) 方法即可。 其次,yii2 的的初始项目代码里,我看到:login.php <?php $form = ActiveForm::begin([ ... 这,view 里也做计算 ? Laravel 的 view 里也有这个问题。 当然,你可以说,我定规范,view 里的数据只能传进来,不做计算。 use yii\web\Controller; class SiteController extends Controller。yii/web/Controller 多出的是流程无关的助手函数。为什么不抽成助手类更清晰呢。DuckPHP 的原则之一,你个 CURD 程序员,V,C,S,M 助手类 解决不了的,问老大去。不要怀疑系统有问题。不需要学习或折腾其他东西。 配置文件 config/web.php 能精简么,DuckPHP 的理念之一就是默认设置不需要在外面暴露。 最后,我想问各种框架,包括 DuckPHP,为什么框架会有这么多类,这么多文件呢。 这些文件在运行中能用到多少。 不要不行么。 如果现在开始项目,我也不会马上用 DuckPHP。 毕竟公司项目不是实验品。 但如果试验次数够多,我会马上推广使用之。 毕竟我看来是个通用,高性能,优秀的框架。 让 PHP 从 Java 的道路拉回来。 |
23
terrywater 2019-12-25 11:14:19 +08:00
@dvaknheo
1.疑问:ii2 能热更新么? 就是保持 vendor 文件夹的内容不变,更改某个系统类的实现? 不是很明白你的意思,你指的是重写框架底层?(也就是不修改框架文件的前提下,重写框架的功能?)如果是这个意思,我回答,是可以的 2.view 里面做计算,并不是不可以,世界不是严格的黑白分明,八卦里面,阳中有阴呢? 工具为了解决具体的问题,这样可以很快的做出来增删改查 如果你感觉 yii2 的 view 这块孬,你可以不用他的 view 封装函数嘛,按照严格的 view 不做计算就行了,yii2 封装的 form 不用就 OK 了,这个没啥可以说事的 yii2 本身很多功能都是基于组件化,yii2 开发了轮子,你感觉不好用,你可以自己开发组件替换掉 yii2 的,这是 yii2 框架的核心所在,就像 yii2 封装的 session,你感觉不好用,配置重写指向一个你自己的 session 就可以了, 譬如 redis session,mongodb session 等组件,替换掉就 OK 了,composer 下载一下别人封装的包 3.use yii\web\Controller; class SiteController extends Controller。yii/web/Controller 多出的是流程无关的助手函数。为什么不抽成助手类更清晰呢 答:看不懂你说的与流程 无关的助手函数是什么意思,show code ? 4.一个框架有多少文件,多少类,并不是一个评价的优劣,对于一个框架的性能来说,主要看初始化部分加载了哪些 yii2 的组件是懒加载模式,配置注入,容器动态生成的,使用的时候才会生成对象,并且是单例模式, 即使 yii2 做了 1 万个组件,初始化并没有加载,这个有什么问题? 5. 本人发现喜欢搞自研框架的,都是在追究一些无关次要的语法洁癖问题,并没有解决太多实质性的问题,把写框架,当成一个自己喜欢的艺术品,而不是一个工具。 |
24
terrywater 2019-12-25 11:16:35 +08:00
@ywisax 对,composer 加载的时候,都有 github 对应的版本号,对于一个包无论怎么更新,他的历史版本是不变动的,只要制定了具体的版本号,就不存在这个问题。
|
25
terrywater 2019-12-25 11:16:42 +08:00
@ywisax 对,composer 加载的时候,都有 github 对应的版本号,对于一个包无论怎么更新,他的历史版本是不变动的,只要制定了具体的版本号,就不存在这个问题的
|
26
terrywater 2019-12-25 11:21:54 +08:00
@dvaknheo 对于您自研框架,本人没有反对,只是发表一下自己的观点
我认为在 yii2 的基础上,进行一些重构,和封装,基本就满足自己的需要了( Yii2 的框架的很多东西,都可以通过配置重写),不需要自己自研框架了 像 yii2,强哥博士文凭,200 几年就开始维护 php 框架,一路走了这么多年,最后沉淀到 yii2 里面,无论水准,经历,视野,还有花费的时间都比我们多的多,又经过了这么多用户的沉淀。 |
27
dvaknheo OP @terrywater
额,我想了一下,yii2 是可以热更新的 组件确实是框架的粒度问题。组件初始化的时候没加载,为什么要放在 框架的包里,而不是作为附加包呢。 我是从 CodeIgniter 看的,ci 系的代码,本来就落后于时代的,ci4 我更感觉不到有什么存在的意义。 (yii2 的 demo 用了 80/400+ 个 yii 系统 php 文件) XX 善于解决 XX 发明的问题, 那么,如何避免你这也不是重复这一问题呢,这就是我在写 DuckPHP 的时候一直在考虑的问题。 yii 1 早年我就知道,最 java 化的。 导致了后面 yii2 我也没怎么去看。 我手头有个 yii1 的好产品,自己一直看不下去。 推广方面,我更感兴趣的是为什么 Laravel 而不是 Symfony 会更流行。为什么 smarty 还一直有人用到现在。 推广东西比代码真的难。我寄望于一小撮人能用起来。闭门造车产生的问题,不多做沟通就不会被发现,也没会去解决。 经过你这么介绍,我也想有兴趣折腾几个 yii2 的项目看看。 |
28
terrywater 2019-12-25 12:07:27 +08:00
@dvaknheo
1.在包的这个角度上,yii2 是有点重,没有分拆,现在 yii3 在做这个事情了 2.即使没有分拆,但是并不影响性能,组件是懒加载模式,使用的时候才会实例化单例模式对象,不使用不会生成对象, 3.数文件个数,这么没啥意义吧,linux 和 window 的优劣可以用数文件个数对比吗? 你可能说文件个数多,加载慢,不是有 php opchache 扩展吗?个数多也没有影响 |
29
dvaknheo OP v1.2.2 发布。通过 phpstan level7 的检查,php-cs-fixer 的格式化。phpunit 的 100%覆盖测试。
应用层方面,C::Parameters() 改成 C::getParameters() ,这个只用于自定义路由。 其他都是上层的改动。 用于各种自定义的路由 RouteHookRouteMap 拆分成 route_map 和 route_map_important 两选项, 后者在默认文件路由前钩挂,用于比较重要的自定义路由。 前者用于默认文件路由后 404 处理 把当前站点改成组件化用的 AppPluginTrait 重写。如果没特殊使用,不需要改动。 Logger 类 放入核心目录。 Helper 类从 Core 核心目录移出。 核心框架不再包含。 App 类额外方法都咔嚓。 由 Ext 扩展类自行扩充。 Swoole 的支持方式变更,ext 扩展 + core/app 类方式完成。 随行 demo 更新。 要做的: 错误处理现在复杂化了,需要重新简化以更清晰。 需要添加 init 后 缓存,直接读取到 run 运行的 更快速运行方式。 AppPlugin 组件化的初始化默认应该加上 lazyload 模式,使得多加组件不影响启动效率。 需要添加适配各家框架 nginx 配置的插件 。各家 nginx 配置居然都有不同 :( 。 |