TIGERB / Easy Php
Projects that are alternatives of or similar to Easy Php
A Faster Lightweight Full-Stack PHP Framework
ไธญๆ็ใ
Docker env
Just one command to build all env for the easy-php
How to build a PHP framework by ourself ?
Why do we need to build a PHP framework by ourself? Maybe the most of people will say "There have so many PHP frameworks be provided, but we still made a wheel?". My point is "Made a wheel is not our purpose, we will get a few of knowledge when making a wheel which is our really purpose".
Then, how to build a PHP framework by ourself? General process as follows:
Entry file ----> Register autoload function
----> Register error(and exception) function
----> Load config file
----> Request
----> Router
----> (Controller <----> Model)
----> Response
----> Json
----> View
In addition, unit test, nosql support, api documents and some auxiliary scripts, e.g. Finnally, My framework directory as follows:
Project Directory Structure
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]
Life Cycle
Framework Module Description:
Entrance file
Defined a entrance file that provide a uniform file for user visit, which hide the complex logic like the enterprise service bus.
// require the application run file
require('../framework/run.php');
Autoload Module
Register a autoload function in the __autoload queue by used spl_autoload_register, after that, we can use a class by namespace and keyword 'use'.
Error&Exception Handle Module
- Catch error:
Register a function by used set_error_handler to handle error, but it can't handle the following error, E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING and the E_STRICT produced by the file which called set_error_handler function. So, we need use register_shutdown_function and error_get_last to handle this finally error which set_error_handler can't handle. When the framework running, we can handle the error by ourself, such as, give a friendly error messge for client.
[file: framework/hanles/ErrorHandle.php]
- Catch exception:
Register a function by used set_exception_handler to handle the exception which is not be catched, which can give a friendly error messge for client.
[file: framework/hanles/ExceptionHandle.php]
Config Handle Module
Loading framework-defined and user-defined config files.
For example๏ผthe master-salve database config๏ผ
[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
[file: framework/hanles/ConfigHandle.php]
Request&Response Module
- Request Object: contains all the requested information.
- Response Object: contains all the response information.
All output is json in the framework, neithor framework's core error or business logic's output, beacuse I think is friendly.
Request param check, Support require/length/number check at present. Use as follows:
$request = App::$container->get('request');
$request->check('username', 'require');
$request->check('password', 'length', 12);
$request->check('code', 'number');
[file: framework/Response.php]
Route Handle Module
โโโ 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]
Execute the target controller's function by the router parse the url information.Is composed of four types of:
tradition router
domain/index.php?module=Demo&contoller=Index&action=test&username=test
pathinfo router
domain/demo/index/modelExample
user-defined router
// config/moduleName/route.php, this 'this' point to RouterHandle instance
$this->get('v1/user/info', function (Framework\App $app) {
return 'Hello Get Router';
});
micro monolith router
What's the micro monolith router? There are a lot of teams are moving in the SOA service structure or micro service structure, I think it is difficult for a small team. So the micro monolith was born, what's this? In my opinion, this is a SOA process for a monolith application.For example:
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]
As above, we implemented a easy micro monolith structure.But how these module to communicate with each other? As follows:
App::$app->get('demo/index/hello', [
'user' => 'TIGERB'
]);
So we can resolve this problem loose coupling. In the meantime, we can exchange our application to the SOA structure easily, beacuse we only need to change the method get implementing way in the App class, the way contain RPC, REST. etc.
[file: framework/hanles/RouterHandle.php]
MVC To MCL
The tradition MVC pattern includes the model,view,controller layer. In general, you always write the business logic in the controller or model layer. But you will feel the code is difficult to read, maintain, expand after a long time. So I add a logic layer in the framework forcefully where you can implement the business logic by yourself. You can not only implement a tool class but also implement your business logic in a new subfolder, what's more, you can implement a gateway based on the pattern of responsibility (I provided a example).
In the end, the structure as follows:
- M: models, the map of database's table where define the curd operation.
- C: controllers, where expose the business resourse
- L: logics,ใwhere implement the business logic flexiblly
Logics layer
A gateway example๏ผ
I built a gateway in the logics folder, structure as follows๏ผ
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]
The gateway entrance class code as follows:
// 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')
);
After the gateway be implemented, how to use this in the framework?I provide a user-defined's class, we just register this in the UserDefinedCase class. for example:
/**
* register user-defined behavior
*
* @var array
*/
private $map = [
//ใfor example, loading user-defined gateway
'App\Demo\Logics\Gateway\Entrance'
];
So, the gateway is running.But what's the UserDefinedCase that can be loading before RouterHandle.
Where is the view layer?I abandon it, beacuse I chose the SPA for frontend, detail as follows.
Using Vue For View
source code folder
The separate-frontend-and-backend and two-way data binding, modular is so popular.In the meantime, I moved the project easy-vue that built by myself to the framework as the view layer. The frontend source code folder as follows:
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]
Build Step
yarn install
DOMAIN=http://yourdomain npm run dev
After build
After built success, there made dist folder and index.html in the public. This file will be ignore when this branch is not the release branch.
public [this is a resource directory to expose service resource]
โโโ dist [frontend source file after build]
โ โโโ ...
โโโ index.html [entrance html file]
ORM
What's the ORM(Object Relation Map)? In my opinion, ORM is a thought that build a relationship of object and the abstract things.The model is the database's table and the model's instance is a operation for the table."Why do you do that, use the sql directly is not good?", my answer:you can do what you like to do, everything is flexable, but it's not be suggested from a perspective of a framework's reusable, maintainable and extensible.
On the market for the implemention of the ORM, such as: Active Record in thinkphp and yii, Eloquent in laravel, then we call the ORM here is "ORM" simply. The "ORM" structure in the framework as follows:
โโโ orm
โ โโโ Interpreter.php [sql Interpreter]
โ โโโ DB.php [database operate class]
โ โโโ Model.php [base model class]
โ โโโ db
โ โโโ Mysql.php [mysql class]
DB example
/**
* 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;
}
Model example
// 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;
}
Service Container
What's the service container?
Service container is difficultly understand, I think it just a third party class, which can inject the class and instance. we can get the instance in the container very simple.
The meaning of the service container?
According to the design patterns: we need make our code "highly cohesive, loosely coupled". As the result of "highly cohesive" is "single principle", As the result of "single principle" is the class rely on each other. General way that handle the dependency as follows:
class Demo
{
public function __construct ()
{
// the demo directly dependent on RelyClassName
$instance = new RelyClassName ();
}
}
The above code is no problem, but is not conform to the design pattern of "The least kown principle", beacuse it has a direct dependence. We bring a third class in the framework, which can new a class or get a instance. So, the third party class is the service container, which like the role of 'middleware' in the architecture of the system.
After implements a service container, I put the Rquest, Config and other instances are injected into service in the singleton container, when we need to use can be obtained from the container, is very convenient.Use the following:
// 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 Support
Inject the nosql's single instance in service container when the framework loading, you can decide what nosql you need use whit the configuration. At present we support redis/memcahed/mongodb.
Some example:
// get redis instance
App::$container->getSingle('redis');
// get memcahed instance
App::$container->getSingle('memcahed');
// get mongodb instance
App::$container->getSingle('mongodb');
Log
I make the log class like a third part module that be used by composer, the project link https://github.com/easy-framework/easy-log
How to use? as follows:
// 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');
[file: framework/handles/LogHandle.php]
Swoole Support
This framework support swoole mode with the php extension swoole, just:
cd public && php server.php
Job Support
You can do some job in the jobs folder directly as follows:
jobs [Jobs folder, where write you business script]
โโโ demo [Module folder]
โ โโโ Demo.php [Job script example file]
โ โโโ ...
Job demo 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';
}
}
So, just run the command:
php cli --job=demo.demo.test
Api Docs
Usually after we write an api, the api documentation is a problem, we use the Api Blueprint protocol to write the api document and mock. At the same time, we can request the api real-timely by used Swaggerใ(unavailable).
I chose the Api Blueprint's tool snowboard, detail as follows๏ผ
Api doc generate instruction
cd docs/apib
./snowboard html -i demo.apib -o demo.html -s
open the website, http://localhost:8088/
Api mock instruction
cd docs/apib
./snowboard mock -i demo.apib
open the website, http://localhost:8087/demo/index/hello
PHPunit
Based on the phpunit, I think write unit tests is a good habit.
How to make a test๏ผ
Write test file in the folder testsใby referenced the file DemoTest.php, then run:
vendor/bin/phpunit
The assertion example:
/**
*ใ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 Hooks
- The standard of coding: verify the coding forcefully before commit by used php_codesniffer
- The standard of commit-msg: verify the commit-msg forcefully before commit by used the script commit-msg wrote by Treri, which can enhance the git log readability and debugging, log analysis usefully, etc.
Script
cli script
Run the framework with cli mode, detail in the instruction.
build script
Build the application in the runtime/build folder, such as:
runtime/build/App.20170505085503.phar
<?php
// require the phar file in index.php file
require('runtime/build/App.20170505085503.phar');
Command:
php cli --build
How to use ?
Run๏ผ
composer create-project tigerb/easy-php easy --prefer-dist && cd easy
Web Server Mode:
Quick Start:
cd bin && php cli --run
demo as follows๏ผ
Cli Mode:
php cli --method=<module.controller.action> --<arguments>=<value> ...
For example, php cli --method=demo.index.get --username=easy-php
Swoole Mode:
cd public && php server.php
Get Help:
Use php cli OR php cli --help
Docker env
Docker env is support by this framework, you will build the env just by one command quickly. Get more please click easy-env.
Performance with 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)
Performance with 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)
Question&Contribution
If you find some question๏ผplease launch a issue or PRใ
How to Contribute๏ผ
cp ./.git-hooks/* ./git/hooks
After that, launch a PR as usual.
project address: https://github.com/TIGERB/easy-php
TODO
- Add database sql helper
- Integrate swagger
- Provide much friendly help for user
- Module's config support module-defined mysql and nosql configuration
- ORM provide more apis
- Resolve config problem when publish our project
- implement auto deploy by used phar
- ...
DONE
-
v0.8.6(2019/04/21)
- fix core error data strcut
- fix phpunit
-
v0.8.5(2019/01/06)
- fix error_report
- fix when __coreError is occur the response is output 200 but it also out put __coreError
-
v0.8.1(2018/06/24)
- use easy log
- add folder bin
-
v0.8.0(2017/12/29)
- use swoole
- fix infinite recursion for micromonomer router
-
v0.7.1(2017/08/29)
- refactor router by the strategy design pattern
-
v0.7.0(2017/06/18)
- implement ci by travis-ci
- add jobs script folder
-
v0.6.9(2017/05/22)
- more friendly for api develop process
- request param check๏ผrequire/length/number
- support master-salve config for db
- more friendly for api develop process
-
v0.6.7(2017/05/14)
- fix not set default time zone
- The performance test and optimize
- Use the lazy load thought to optimize the framework
- Change Helper's method to the framework's function
APPRECIATION
Contributors
This project exists thanks to all the people who contribute. [Contribute].
Backers
Thank you to all our backers! ๐ [Become a backer]
Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]