tigerb / easy-php
更快的轻量级全栈PHP框架
Requires
- easy-framework/easy-log: ^0.1.0
Requires (Dev)
- dev-master
- 0.8.6
- 0.8.5
- 0.8.4
- 0.8.3
- 0.8.2
- 0.8.1
- 0.7.1
- 0.7.0
- 0.6.9
- 0.6.8
- 0.6.7
- dev-dependabot/npm_and_yarn/frontend/qs-6.4.1
- dev-dependabot/npm_and_yarn/frontend/express-4.18.2
- dev-dependabot/npm_and_yarn/frontend/decode-uri-component-0.2.2
- dev-dependabot/npm_and_yarn/frontend/css-what-2.1.3
- dev-dependabot/npm_and_yarn/frontend/follow-redirects-1.14.8
- dev-dependabot/npm_and_yarn/frontend/chownr-1.1.4
- dev-dependabot/npm_and_yarn/frontend/object-path-0.11.8
- dev-dependabot/npm_and_yarn/frontend/path-parse-1.0.7
- dev-dependabot/npm_and_yarn/frontend/tar-2.2.2
- dev-dependabot/npm_and_yarn/frontend/set-getter-0.1.1
- dev-dependabot/npm_and_yarn/frontend/merge-deep-3.0.3
- dev-dependabot/npm_and_yarn/frontend/dns-packet-1.3.4
- dev-dependabot/npm_and_yarn/frontend/hosted-git-info-2.8.9
- dev-dependabot/npm_and_yarn/frontend/lodash-4.17.21
- dev-dependabot/npm_and_yarn/frontend/y18n-3.2.2
- dev-dependabot/npm_and_yarn/frontend/elliptic-6.5.4
- dev-dependabot/npm_and_yarn/frontend/ini-1.3.7
- dev-dependabot/npm_and_yarn/frontend/http-proxy-1.18.1
- dev-dependabot/npm_and_yarn/frontend/websocket-extensions-0.1.4
- dev-dependabot/npm_and_yarn/frontend/yarn-1.22.0
- dev-dependabot/npm_and_yarn/frontend/url-parse-1.4.7
- dev-dependabot/npm_and_yarn/frontend/extend-3.0.2
- dev-dependabot/npm_and_yarn/frontend/webpack-dev-server-3.1.11
- dev-dependabot/npm_and_yarn/frontend/assign-deep-0.4.8
- dev-develop
This package is auto-updated.
Last update: 2024-09-05 13:39:18 UTC
README
更快的轻量级全栈PHP框架
Docker环境
只需一条命令即可构建easy-php的所有环境
如何自己构建PHP框架?
为什么我们需要自己构建PHP框架?或许大多数人会说:“已经提供了这么多PHP框架,我们为什么还要造轮子?”我的观点是:“造轮子不是我们的目的,在造轮子的过程中,我们会获得一些知识,这才是我们的真正目的。”
那么,如何自己构建PHP框架呢?一般流程如下
Entry file ----> Register autoload function
----> Register error(and exception) function
----> Load config file
----> Request
----> Router
----> (Controller <----> Model)
----> Response
----> Json
----> View
此外,单元测试、NoSQL支持、API文档和一些辅助脚本,例如,我的框架目录如下
项目目录结构
app [application backend directory]
├── demo [module directory]
│ ├── controllers [controller directory]
│ │ └── Index.php [default controller class file]
│ ├── logics [logic directory]
│ │ ├── exceptions [exception directory]
│ │ ├── gateway [a gateway example]
│ │ ├── tools [tool class directory]
│ │ └── UserDefinedCase.php [register user defined handle before framework loading router]
│ └── models [model directory]
│ └── TestTable.php [model class file]
├── config [config folder]
│ ├── demo [module config folder]
│ │ ├── config.php [module-defined config]
│ │ └── route.php [module-defined router]
│ ├── common.php [common config]
│ ├── database.php [database config]
│ ├── swoole.php [swoole config]
│ └── nosql.php [nosql config]
docs [api document directory]
├── apib [Api Blueprint]
│ └── demo.apib [api doc example file]
├── swagger [swagger]
framework [easy-php framework directory]
├── exceptions [core exception class]
│ ├── CoreHttpException.php[http exception]
├── handles [handle class file be used by app run]
│ ├── Handle.php [handle interface]
│ ├── ErrorHandle.php [error handle class]
│ ├── ExceptionHandle.php [exception handle class]
│ ├── ConfigHandle.php [config handle class]
│ ├── NosqlHandle.php [nosql handle class]
│ ├── LogHandle.php [log handle class]
│ ├── UserDefinedHandle.php[user defined handle class]
│ ├── RouterSwooleHan... [router handle class for swoole mode]
│ └── RouterHandle.php [router handle class]
├── orm [datebase object relation map class directory]
│ ├── Interpreter.php [sql Interpreter class]
│ ├── DB.php [database operation class]
│ ├── Model.php [data model]
│ └── db [db type directory]
│ └── Mysql.php [mysql class file]
├── router [router strategy]
│ ├── RouterInterface.php [router strategy interface]
│ ├── General.php [general strategy class]
│ ├── Pathinfo.php [pathinfo strategy class]
│ ├── Userdefined.php [userdefined strategy class]
│ ├── Micromonomer.php [micromonomer strategy class]
│ ├── Job.php [job strategy class]
│ ├── EasySwooleRouter.php [router strategy entrance class for swoole mode]
│ └── EasyRouter.php [router strategy entrance class]
├── nosql [nosql directory]
│ ├── Memcahed.php [memcahed class file]
│ ├── MongoDB.php [mongoDB class file]
│ └── Redis.php [redis class file]
├── App.php [this application class file]
├── Container.php [container class file]
├── Helper.php [helper class file]
├── Load.php [autoload class file]
├── Request.php [request object class file]
├── Response.php [response object class file]
├── run.php [run this application script file]
├── swoole.php [init the framework && swoole server]
frontend [application frontend source code directory]
├── src [source folder]
│ ├── components [vue components]
│ ├── views [vue views]
│ ├── images [images folder]
│ ├── ...
├── app.js [vue root js]
├── app.vue [vue root component]
├── index.template.html [frontend entrance template file]
├── store.js [vuex store file]
├── .babelrc [babel config file]
├── webpack.config.js [webpack config file]
├── yarn.lock [yarn lock file]
jobs [Jobs folder, where write you business script]
├── demo [Module folder]
│ ├── Demo.php [Job script example file]
│ ├── ...
public [this is a resource directory to expose service resource]
├── dist [frontend source file after build]
│ └── ...
├── index.html [entrance html file]
├── index.php [entrance php script file]
├── server.php [init the server with swoole]
runtime [temporary file such as log]
├── logs [log directory]
├── build [phar directory build by build script]
tests [unit test directory]
├── demo [module name]
│ └── DemoTest.php [test class file]
├── TestCase.php [phpunit test case class file]
vendor [composer vendor directory]
.git-hooks [git hooks directory]
├── pre-commit [git pre-commit example file]
├── commit-msg [git commit-msg example file]
bin [the auto script folder]
├── build [build php code to phar file script]
├── cli [run this framework with the php cli mode]
├── run [quick start script]
.env.example [the environment variables example file]
.gitignore [git ignore config file]
.travis.yml [travis-ci config file]
LICENSE [lincese file]
logo.png [logo picture]
composer.json [composer file]
composer.lock [composer lock file]
package.json [dependence file for frontend]
phpunit.xml [phpunit config file]
README-CN.md [readme file chinese]
README.md [readme file]
生命周期
框架模块描述
入口文件
定义一个入口文件,为用户提供统一的访问文件,隐藏像企业服务总线这样的复杂逻辑。
// require the application run file
require('../framework/run.php');
自动加载模块
通过使用spl_autoload_register在__autoload队列中注册一个自动加载函数,之后,我们可以使用命名空间和'使用'关键字来使用一个类。
错误和异常处理模块
- 捕获错误
通过使用set_error_handler注册一个函数来处理错误,但它不能处理以下错误:E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING和由调用set_error_handler函数的文件产生的E_STRICT。因此,我们需要使用register_shutdown_function和error_get_last来处理这个set_error_handler无法处理的最终错误。当框架运行时,我们可以自己处理错误,例如,为客户端提供一个友好的错误信息。
[文件:framework/handles/ErrorHandle.php]
- 捕获异常
通过使用set_exception_handler注册一个函数来处理未被捕获的异常,这可以为客户端提供一个友好的错误信息。
[文件:framework/handles/ExceptionHandle.php]
配置处理模块
加载框架定义的和用户定义的配置文件。
例如,主从数据库配置:
[database]
dbtype = mysqldb
dbprefix = easy
dbname = easyphp
dbhost = localhost
username = easyphp
password = easyphp
slave = 0,1
[database-slave-0]
dbname = easyphp
dbhost = localhost
username = easyphp
password = easyphp
[database-slave-1]
dbname = easyphp
dbhost = localhost
username = easyphp
password = easyphp
[文件:framework/handles/ConfigHandle.php]
请求和响应模块
- 请求对象:包含所有请求信息。
- 响应对象:包含所有响应信息。
框架中所有输出都是JSON,无论是框架核心错误还是业务逻辑的输出,因为我认为这是友好的。
请求参数检查,目前支持require/length/number检查。用法如下
$request = App::$container->get('request');
$request->check('username', 'require');
$request->check('password', 'length', 12);
$request->check('code', 'number');
路由处理模块
├── router [datebase object relation map class directory]
├── RouterInterface.php [router strategy interface]
├── General.php [general strategy class]
├── Pathinfo.php [pathinfo strategy class]
├── Userdefined.php [userdefined strategy class]
├── Micromonomer.php [micromonomer strategy class]
├── Job.php [job strategy class]
└── EasyRouter.php [router strategy entrance class]
通过路由解析URL信息来执行目标控制器的函数。由以下四种类型组成
传统路由
domain/index.php?module=Demo&contoller=Index&action=test&username=test
pathinfo路由
domain/demo/index/modelExample
用户自定义路由
// config/moduleName/route.php, this 'this' point to RouterHandle instance
$this->get('v1/user/info', function (Framework\App $app) {
return 'Hello Get Router';
});
微单体路由
什么是微单体路由器?许多团队正在向SOA服务结构或微服务结构转型,我认为这对小团队来说很困难。因此,微单体应运而生,这是什么?在我看来,这实际上是对单体应用的SOA流程。例如
app
├── UserService [user service module]
├── ContentService [content service module]
├── OrderService [order service module]
├── CartService [cart service module]
├── PayService [pay service module]
├── GoodsService [goods service module]
└── CustomService [custom service module]
如上所述,我们实现了一个简单的微单体结构。但是这些模块如何相互通信呢?如下
App::$app->get('demo/index/hello', [
'user' => 'TIGERB'
]);
因此,我们可以通过解耦来解决此问题。同时,我们可以轻松地将我们的应用程序转换为SOA结构,因为我们只需要更改App类中get实现方式的方法,包括RPC、REST等方式。
[文件:框架/handles/RouterHandle.php]
MVC到MCL
传统的MVC模式包括模型、视图、控制器层。通常,您总是在控制器或模型层编写业务逻辑。但是,经过长时间的使用,您会发现代码难以阅读、维护和扩展。因此,我在框架中强制添加了一个逻辑层,您可以自行实现业务逻辑。您不仅可以实现工具类,还可以在新的子文件夹中实现业务逻辑,更重要的是,您还可以基于责任模式实现网关(我提供了一个示例)。
最终结构如下
- M:模型,定义数据库表的映射,其中定义了CRUD操作。
- C:控制器,公开业务资源
- L:逻辑,灵活实现业务逻辑
逻辑层
网关示例:
我在逻辑文件夹中构建了一个网关,结构如下:
gateway [gateway directory in logics]
├── Check.php [interface]
├── CheckAppkey.php [check app key]
├── CheckArguments.php [check require arguments]
├── CheckAuthority.php [check auth]
├── CheckFrequent.php [check call frequent]
├── CheckRouter.php [router]
├── CheckSign.php [check sign]
└── Entrance.php [entrance file]
网关入口类代码如下
// init:gateway common arguments must be not empty check
$checkArguments = new CheckArguments();
// init:app key check
$checkAppkey = new CheckAppkey();
// init:call frequent check
$checkFrequent = new CheckFrequent();
// init:sign check
$checkSign = new CheckSign();
// init:auth check
$checkAuthority = new CheckAuthority();
// init:gateway's router
$checkRouter = new CheckRouter();
// build object chain
$checkArguments->setNext($checkAppkey)
->setNext($checkFrequent)
->setNext($checkSign)
->setNext($checkAuthority)
->setNext($checkRouter);
// start gateway
$checkArguments->start(
APP::$container->get('request')
);
网关实现后,如何在框架中使用它?我提供了一个用户自定义的类,我们只需在UserDefinedCase类中注册它。例如
/**
* register user-defined behavior
*
* @var array
*/
private $map = [
// for example, loading user-defined gateway
'App\Demo\Logics\Gateway\Entrance'
];
因此,网关正在运行。但是,在RouterHandle之前可以加载哪些UserDefinedCase?
视图层在哪里?我放弃了它,因为我选择了SPA作为前端,详情如下。
[文件:app/*]
使用Vue进行视图
源代码文件夹
分离前端和后端以及双向数据绑定,模块化非常流行。同时,我将我自己构建的项目easy-vue添加到框架中作为视图层。前端源代码文件夹如下
frontend [application frontend source code directory]
├── src [source folder]
│ ├── components [vue components]
│ ├── views [vue views]
│ ├── images [images folder]
│ ├── ...
├── app.js [vue root js]
├── app.vue [vue root component]
├── index.template.html [frontend entrance template file]
├── store.js [vuex store file]
构建步骤
yarn install
DOMAIN=http://yourdomain npm run dev
构建后
构建成功后,在public中创建了dist文件夹和index.html。当此分支不是发布分支时,此文件将被忽略。
public [this is a resource directory to expose service resource]
├── dist [frontend source file after build]
│ └── ...
├── index.html [entrance html file]
[文件:前端/*]
ORM
ORM(对象关系映射)是什么?在我看来,ORM是一种建立对象和抽象事物之间关系的思想。模型是数据库的表,模型的实例是对表的操作。“你为什么要这样做,直接使用SQL不是更好吗?”我的回答:你可以做你想做的事情,一切都很灵活,但从框架的可重用性、可维护性和可扩展性的角度来看,并不建议这样做。
在市场上,ORM的实现,如:thinkphp和yii中的Active Record、laravel中的Eloquent,然后我们在这里简单地称之为“ORM”。框架中的“ORM”结构如下
├── orm
│ ├── Interpreter.php [sql Interpreter]
│ ├── DB.php [database operate class]
│ ├── Model.php [base model class]
│ └── db
│ └── Mysql.php [mysql class]
DB示例
/**
* DB operation example
*
* findAll
*
* @return void
*/
public function dbFindAllDemo()
{
$where = [
'id' => ['>=', 2],
];
$instance = DB::table('user');
$res = $instance->where($where)
->orderBy('id asc')
->limit(5)
->findAll(['id','create_at']);
$sql = $instance->sql;
return $res;
}
模型示例
// controller
/**
* model example
*
* @return mixed
*/
public function modelExample()
{
try {
DB::beginTransaction();
$testTableModel = new TestTable();
// find one data
$testTableModel->modelFindOneDemo();
// find all data
$testTableModel->modelFindAllDemo();
// save data
$testTableModel->modelSaveDemo();
// delete data
$testTableModel->modelDeleteDemo();
// update data
$testTableModel->modelUpdateDemo([
'nickname' => 'easy-php'
]);
// count data
$testTableModel->modelCountDemo();
DB::commit();
return 'success';
} catch (Exception $e) {
DB::rollBack();
return 'fail';
}
}
//TestTable model
/**
* Model example
*
* findAll
*
* @return void
*/
public function modelFindAllDemo()
{
$where = [
'id' => ['>=', 2],
];
$res = $this->where($where)
->orderBy('id asc')
->limit(5)
->findAll(['id','create_at']);
$sql = $this->sql;
return $res;
}
服务容器
服务容器是什么?
服务容器很难理解,我认为它只是一个第三方类,可以注入类和实例。我们可以在容器中非常简单地获取实例。
服务容器的意义?
根据设计模式:我们需要使我们的代码“高度内聚,松散耦合”。高度内聚的结果是“单一原则”,单一原则的结果是类相互依赖。处理依赖的一般方式如下
class Demo
{
public function __construct ()
{
// the demo directly dependent on RelyClassName
$instance = new RelyClassName ();
}
}
上述代码没有问题,但不符合“最少知道原则”的设计模式,因为它有直接依赖。我们在框架中引入了一个第三方类,可以创建类或获取实例。因此,第三方类是服务容器,它在系统架构中类似于“中间件”的角色。
实现服务容器后,我将请求、配置等实例注入到单例容器中的服务中,需要使用时可以从容器中获取,非常方便。使用如下
// Inject the single instance
App::$container->setSingle('alias', 'object/closure/class name');
// Such as,Inject Request instance
App::$container->setSingle('request', function () {
// closure function lazy load
return new Request();
});
// get Request instance
App::$container->get('request');
支持 Nosql
在框架加载时将 nosql 的单例注入到服务容器中,您可以通过配置来决定使用哪种 nosql。目前我们支持 redis/memcached/mongodb。
一些示例
// get redis instance
App::$container->getSingle('redis');
// get memcahed instance
App::$container->getSingle('memcahed');
// get mongodb instance
App::$container->getSingle('mongodb');
日志
我将日志类做成一个类似于 composer 的第三方模块,项目链接 https://github.com/easy-framework/easy-log
如何使用?如下
// env config
[log]
path = /runtime/logs/
name = easy-php
size = 512
level= debug
// How to use in your logic
Log::debug('EASY PHP');
Log::notice('EASY PHP');
Log::warning('EASY PHP');
Log::error('EASY PHP');
[文件:framework/handles/LogHandle.php]
支持 Swoole
此框架支持使用 PHP 扩展 swoole 的 swoole 模式,只需
cd public && php server.php
支持任务
您可以直接在 jobs 文件夹中执行一些任务,如下
jobs [Jobs folder, where write you business script]
├── demo [Module folder]
│ ├── Demo.php [Job script example file]
│ ├── ...
任务示例文件
<?php
namespace Jobs\Demo;
/**
* Demo Jobs
*
* @author TIERGB <https://github.com/TIGERB>
*/
class Demo
{
/**
* job
*
* @example php cli --jobs=demo.demo.test
*/
public function test()
{
echo 'Hello Easy PHP Jobs';
}
}
因此,只需运行以下命令
php cli --job=demo.demo.test
API 文档
通常,我们在编写 API 后,API 文档是一个问题,我们使用 Api Blueprint 协议编写 API 文档和模拟。同时,我们可以通过使用 Swagger(不可用)实时请求 API。
我选择了 Api Blueprint 的工具 snowboard,详细说明如下:
API 文档生成说明
cd docs/apib
./snowboard html -i demo.apib -o demo.html -s
open the website, https://:8088/
API 模拟说明
cd docs/apib
./snowboard mock -i demo.apib
open the website, https://:8087/demo/index/hello
PHPUnit
基于 PHPUnit,我认为编写单元测试是一个好习惯。
如何进行测试?
在 tests 文件夹中编写测试文件,参考 DemoTest.php 文件,然后运行
vendor/bin/phpunit
断言示例
/**
* test assertion example
*/
public function testDemo()
{
$this->assertEquals(
'Hello Easy PHP',
// assert the result by run hello function in demo/Index controller
App::$app->get('demo/index/hello')
);
}
Git 钩子
- 编码标准:在提交之前强制使用 php_codesniffer 验证编码
- 提交消息标准:在提交之前强制使用 Treri 编写的脚本验证提交消息,这可以提高 git 日志的可读性和调试,使日志分析更有用等。
脚本
cli 脚本
以 cli 模式运行框架,详细信息见说明。
构建脚本
在 runtime/build 文件夹中构建应用程序,例如
runtime/build/App.20170505085503.phar
<?php
// require the phar file in index.php file
require('runtime/build/App.20170505085503.phar');
命令
php cli --build
如何使用?
运行:
composer create-project tigerb/easy-php easy --prefer-dist && cd easy
Web 服务器模式
快速入门
cd bin && php cli --run
示例如下:
命令行模式
php cli --method=<module.controller.action> --<arguments>=<value> ...
For example, php cli --method=demo.index.get --username=easy-php
Swoole 模式
cd public && php server.php
获取帮助
使用 php cli 或 php cli --help
Docker环境
该框架支持 Docker 环境配置,您只需一个命令即可快速构建环境。更多信息请点击 easy-env。
使用 php-fpm 的性能
ab -c 100 -n 10000 "http://easy-php.local/Demo/Index/hello"
Concurrency Level: 100
Time taken for tests: 3.259 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1970000 bytes
HTML transferred: 530000 bytes
Requests per second: 3068.87 [#/sec] (mean)
Time per request: 32.585 [ms] (mean)
Time per request: 0.326 [ms] (mean, across all concurrent requests)
Transfer rate: 590.40 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.3 0 4
Processing: 6 32 4.0 31 68
Waiting: 6 32 4.0 31 68
Total: 8 32 4.0 31 68
Percentage of the requests served within a certain time (ms)
50% 31
66% 32
75% 33
80% 34
90% 39
95% 41
98% 43
99% 46
100% 68 (longest request)
使用 Swoole 的性能
ab -c 100 -n 10000 "http://easy-php.local/Demo/Index/hello"
Concurrency Level: 100
Time taken for tests: 1.319 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1870000 bytes
HTML transferred: 160000 bytes
Requests per second: 7580.84 [#/sec] (mean)
Time per request: 13.191 [ms] (mean)
Time per request: 0.132 [ms] (mean, across all concurrent requests)
Transfer rate: 1384.39 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 5 10.6 3 172
Processing: 1 9 13.4 7 177
Waiting: 0 7 11.7 6 173
Total: 3 13 16.9 11 179
Percentage of the requests served within a certain time (ms)
50% 11
66% 12
75% 13
80% 14
90% 15
95% 17
98% 28
99% 39
100% 179 (longest request)
问题&贡献
如果您发现任何问题,请发起一个 issue 或 PR。
如何贡献?
cp ./.git-hooks/* ./git/hooks
然后,按常规发起一个 PR。
项目地址: https://github.com/TIGERB/easy-php
TODO
- 添加数据库 sql 辅助器
- 集成 swagger
- 为用户提供更多友好的帮助
- 模块的配置支持模块定义的 mysql 和 nosql 配置
- ORM 提供更多 api
- 解决发布我们的项目时的配置问题
- 通过使用 phar 实现 自动部署
- ...
DONE
-
v0.8.6(2019/04/21)
- 修复核心错误数据结构
- 修复 phpunit
-
v0.8.5(2019/01/06)
- 修复 error_report
- 修复当 __coreError 发生时,响应输出 200 但也输出了 __coreError
-
v0.8.1(2018/06/24)
- 使用 easy log
- 添加 bin 文件夹
-
v0.8.0(2017/12/29)
- 使用 swoole
- 修复 micromonomer 路由的无限递归问题
-
v0.7.1(2017/08/29)
- 通过策略设计模式重构路由
-
v0.7.0(2017/06/18)
- 通过 travis-ci 实现 ci
- 添加 jobs 脚本文件夹
-
v0.6.9(2017/05/22)
- 对 API 开发过程更友好
- 请求参数检查:require/length/number
- 支持数据库的主从配置
- 对 API 开发过程更友好
-
v0.6.7(2017/05/14)
- 修复未设置默认时区
- 性能测试和优化
- 使用惰性加载思想优化框架
- 将 Helper 的方法改为框架的功能
感谢
贡献者
这个项目的存在离不开所有贡献者。[贡献].
赞助者
感谢所有赞助者!🙏 [成为赞助者]
赞助商
通过成为赞助商来支持此项目。您的标志将显示在此处,并带有链接到您的网站。[成为赞助商]