PhpBoot¶
PhpBoot 是为快速开发 微服务 / RESTful API 设计的PHP框架。它可以帮助开发者更聚焦在业务本身, 而将原来开发中不得不做, 但又重复枯燥的事情丢给框架, 比如编写接口文档、参数校验和远程调用代码等。
特色¶
PhpBoot 框架提供许多主流的特性, 如IOC、AOP、ORM等。 这些特性都经过精心设计和选择(有些是第三方开源代码,如 PHP-DI),但和其他框架相比较, PhpBoot 最显著的特色是:
1. 以面向对象的方式编写接口
你肯定看到过这样的代码:
// **不用** PhpBoot 的代码
class BookController
{
public function findBooks(Request $request)
{
$name = $request->get('name');
$offset = $request->get('offset', 0);
$limit = $request->get('limit', 10);
...
return new Response(['total'=>$total, 'data'=>$books]);
}
public function createBook(Request $request)
...
}
很多主流框架都需要用类似代码编写接口。但这种代码的一个问题是, 方法的输入输出隐藏在实现里, 这不是通常我们提倡的编码方式。如果你对代码要求更高, 你可能还会实现一层 Service 接口, 而在 Controller 里只是简单的去调用 Service 接口。而使用 PhpBoot, 你可以用更自然的方式去定义和实现接口。上面的例子, 在 PhpBoot 框架中实现是这样的:
/**
* @path /books/
*/
class Books
{
/**
* @route GET /
* @return Book[]
*/
public function findBooks($name, &$total=null, $offset=0, $limit=10)
{
$total = ...
...
return $books;
}
/**
* @route POST /
* @param Book $book {@bind request.request} bind $book with http body
* @return string id of created book
*/
public function createBook(Book $book)
{
$id = ...
return $id;
}
}
上面两份代码执行的效果是一样的。可以看到 PhpBoot 编写的代码更符合面向对象编程的原则, 以上代码完整版本请见 phpboot-example 。
2. 轻松支持 Swagger
Swagger 是目前最流行的接口文档框架。虽然很多框架都可以通过扩展支持Swagger, 但一般不是需要编写很多额外的注释, 就是只能导出基本的路由信息, 而不能导出详细的输入输出参数。而 PhpBoot 可以在不增加额外编码负担的情况下, 轻松去完成上述任务,下图为findBooks对应的文档。更多内容请见 文档 和 在线 Demo 。

3. 简单易用的分布式支持
使用 PhpBoot 可以很简单的构建分布式应用。通过如下代码, 即可轻松远程访问上面示例中的 Books 接口:
$books = $app->make(RpcProxy::class, [
'interface'=>Books::class,
'prefix'=>'http://x.x.x.x/'
]);
$books->findBooks(...);
同时还可以方便的发起并发请求, 如:
$res = MultiRpc::run([
function()use($service1){
return $service1->doSomething();
},
function()use($service2){
return $service2->doSomething();
},
]);
更多内容请查看 文档
4. IDE 友好
IDE 的代码提示功能可以让开发者轻松不少, 但很多框架在这方面做的并不好, 你必须看文档或者代码, 才能知道某个功能的用法。PhpBoot 在一开始就非常注重框架的 IDE 友好性, 尽可能让框架保持准确的代码提示。比如下图是 DB 库在 PhpStorm 下的使用:

可以看到, IDE 的提示是符合 SQL 语法规则的, 并不是简单罗列所有 SQL 指令。
帮助¶
- QQ 交流群:185193529
- 本人邮箱 caoyangmin@gmail.com
文档¶
安装¶
1. 安装 Composer¶
PhpBoot 框架使用 Composer 来管理其依赖包。所以,在你使用 PhpBoot 之前,你必须确认在你电脑上是否安装了 Composer。
curl -s http://getcomposer.org/installer | php
2. 安装 PhpBoot¶
完成 Composer 安装后,在你的项目目录下执行 composer,即可添加 PhpBoot 依赖。
composer require "caoym/phpboot"
环境要求¶
PhpBoot 框架有一些系统上的需求:
- PHP 版本 >= 5.5.9
- APC 扩展启用
apc.enable=1
- 如果启用了OPcache,应同时配置以下选项:
opcache.save_comments=1
opcache.load_comments=1
WebServer 配置¶
为了使用PhpBoot,你需要配置 WebServer,将所有动态请求指向 index.php
1. Nginx¶
若使用 Nginx ,修改你的项目对应的配置:
server {
listen 80;
server_name example.com;
index index.php;
error_log /path/to/example.error.log;
access_log /path/to/example.access.log;
root /path/to/public;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;
}
}
2. Apache¶
Apache 的配置稍微复杂,首先你需要启 mod_rewrite 模块,然后在 index.php 目录下添加 .htaccess 文件:
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
另外还需要修改虚拟主机的AllowOverride配置
AllowOverride All
注意:由于 WebServer 版本的差异, 以上配置可能不能按预期工作,但这是使用多数 PHP 框架第一步需要解决的问题, 网上有会有很多解决方案,用好搜索引擎即可
示例¶
下面将通过编写一个简单的图书管理系统接口,演示 PhpBoot 的使用。完整的示例可在这里下载。
1. 目录结构¶
- app
- Controllers
- Books.php 接口实现
- Entities
- Book.php 数据实体定义
- Controllers
- config
- config.php 配置
- db
- phpboot-example.db 示例用到的 sqlite 数据库实例
- public
- index.php 入口
- vendor 依赖包
2. 入口¶
index.php 作为项目入口, 通常只需要指定配置文件和 Controllers 目录的路径即可。最终项目对外提供的接口, 由不同的 Controllers 的实现类提供。
<?php
require __DIR__.'/../vendor/autoload.php';
use PhpBoot\Application;
$app = Application::createByDefault(__DIR__.'/../config/config.php');
//扫描 Controllers 目录,自动加载所有路由
$app->loadRoutesFromPath( __DIR__.'/../App/Controllers', 'App\\Controllers');
//执行请求
$app->dispatch();
3. 接口实现¶
示例对外提供GET /books/
接口,用于查找图书,返回图书列表。形式如下:
$ curl "http://localhost/books/?name=PHP&offset=0&limit=10"
[
{
"id": 1,
"name": "PHP",
"brief": "PHP 从入门到嫌弃",
"pictures": []
}
]
其对应的代码为:
namespace App/Controllers;
/**
* 图书管理接口示例
*
* @path /books
*/
class Books
{
/**
* 查找图书
*
* @route GET /
*
* @param string $name 查找书名
* @param int $offset 结果集偏移 {@v min|0}
* @param int $limit 返回结果最大条数 {@v max|1000}
*
* @return Book[] 图书列表
*/
public function findBooks($name, $offset=0, $limit=100)
{
return \PhpBoot\models($this->db, Book::class)
->where(['name'=>['LIKE'=>"%$name%"]])
->limit($offset, $limit)
->get();
}
}
以上代码演示了 PhpBoot 的几项基本功能:
- 路由定义 通过 @path 和@route 定义路由。
- 参数绑定 通过 @param(如果没有注释,默认通过反射从方法定义里获取参数信息)绑定输入,当前示例中,@param 绑定了请求的 querystring 和实现方法的输入参数。
- 参数校验
通过 @v 申明参数的取值范围。
- ORM
\PhpBoot\models()
方法根据实体类 Book()实例化 Model 对象,并提供基本的如 find、create、update、delete 等方法。
4. 项目配置¶
示例中用到了数据库, 通过 config.php 配置:
<?php
return [
'DB.connection'=> 'sqlite:/tmp/phpboot-example.db'
];
Annotation¶
PhpBoot 框架较多的使用了 Annotation。当然原生 PHP 语言并不支持此项特性,所以实际是通过Reflection提取注释并解析实现,类似很多主流 PHP 框架的做法(如 symfony、doctrine 等)。但又有所不同的是,主流的Annotation 语法基本沿用了 java 中的形式,如:
/**
* @Route("/books/{id}", name="book_info")
* @Method("GET")
*/
public function getBook($id)...
语法严谨,易于扩展,但稍显啰嗦(PhpBoot 1.x 版本也使用此语法)。特别是PHP 由于先天不足(原生不支持Annotation),通过注释,在没有IDE语法提示和运行时检查机制的情况下。如果写 Annotation 过于复杂,那还不然直接写原生代码。所以 PhpBoot 使用了更简单的 Annotation 语法。如:
/**
* @route GET /books/{id}
*/
public function getBook($id)...
1. 语法¶
@<name> [param0] [param1] [param2] ...
- name 只能是连续的字母、数字、斜杠’‘、中横杠’-‘ 组成的字符串,建议全小写,单词间用’-‘分割,如
@myapp\my-ann
。 - name和参数,参数和参数见,用空白符(一个或多个连续的空格、制表符)分割。
- 参数中如果包含空格,应将参数用双引号”“包围,包围内的双引号用\转义,如
@my-ann "the param \"0\"" param1
第一个参数为the param "0"
分割参数、转义的语法和linux 命令行的语法类似
2. 嵌套¶
嵌套注释,用{}包围, 比如@param int size {@v min:0|max:10}
路由¶
PhpBoot 支持两种形式的路由定义, 分别是通过加载 Controller 类,分析 Annotation ,自动加载路由,和通过 Application::addRoute 方法手动添加路由。
1. 自动加载路由¶
你可以通过 Application::loadRoutesFromClass 或者 Application::loadRoutesFromPath 添加路由。框架扫描每个类的每个方法,如果方法标记了@route,将被自动添加为路由。被加载类的形式如下:
/**
* @path /books
*/
class Books
{
/**
* @route GET /{id}
*/
public function getBook($id)
}
以上代码表示 http 请求 GET /books/{id}
其实现为 Books::getBook, 其中{id}为url 的可变部分。
1.2. @route¶
语法: @path <method> <uri>
标注在方法的注释里,用于指定接口的路由。method为指定的 http 方法,可以是 GET、HEAD、POST、PUT、PATCH、DELETE、OPTION、DELETE。uri 中可以带变量,用{}包围。
2. 手动加载路由¶
你可以使用 Application::addRoute 手动加载路由,方法如下:
$app->addRoute('GET', '/books/{id}', function(Request $request){
$books = new Books();
return $books->getBook($request->get('id'));
});
需要注意的是,此方法添加的路由,将不能自动生成接口文档。
参数绑定¶
实现接口时,通常需要从 http 请求中提取数据,作为方法的输入参数,并将方法的返回值转换成 http 的输出。参数绑定功能即可以帮你完成上述工作。
1. 输入绑定¶
1.1. 根据方法定义绑定¶
默认情况下,框架会从http请求中提取和方法的参数名同名的变量,作为函数的参数。比如:
/**
* @route GET /books/
*/
public function getBooks($offsit, $limit)
上述代码,对应的 http 请求形式为 GET /books/?offsit=0&limit=10
。在此默认请求下:
- 如果路由 uri 中定义了变量,参数将优先选取 uri 变量。如:
/**
* @route GET /books/{id}
*/
public function getBook($id)
其中 $id 取自 uri。
- 对于没有 BODY 的 http 请求(GET、HEAD、OPTION、DELETE),参数来自 querystring 。
- 其他请求(POST、PUT、OPTION),参数先取 querystring,如果没有,再取 BODY。
1.2. @param¶
如果在方法的注释中,标注了 @param,就会有用 @param 的绑定信息覆盖默认来自函数定义的绑定信息。@param 可以指定变量的类型,而原函数定义中只能在参数是数组或者对象时才能指定类型。@param 的语法为标准 PHP Document 的语法。
/**
* @route GET /books/
* @param int $offsit
* @param int $limit
*/
public function getBooks($offsit, $limit)
以上代码,除了绑定变量外,还指定了变量类型,即如果输入值无法转换成 int,将返回 400 BadRequest 错误。未指定@param 时,参数的类型默认为 mixed。
1.3. 输入对象参数¶
输入参数除了是原生类型外,还可以是对象(这里我们把只有属性和 get、set 方法的对象,称为实体(Entity))。如:
/**
* @route POST /books/
* @param Book $book {@bind request.request} 将$_POST 内容转换成Book实例
*/
public function createBook(Book $bok)
其中 Book 的的定义:
/**
* 图书信息
*/
class Book
{
/**
* @var int
* @v optional
*/
public $id;
/**
* 书名
* @var string
*/
public $name='';
/**
* 简介
* @var string
* @v lengthMax:200
*/
public $brief='';
/**
* 图片url
* @var string[]
*/
public $pictures=[];
}
框架对 http 请求到实体的转换,有一套自己的逻辑:
- @var 指定属性的类型,如果类型不匹配,实例化将抛出 InvalidArgumentException 异常
- 如果不标注 @var,则默认类型为mixed
- 如果属性有默认值,表示此属性可选,否则认为此属性必选
- 支持 @v 定义校验规则
- 实体可以嵌套
1.4. 参数默认值¶
如果想指定某个输入参数可选,只需给方法参数设置一个默认值。比如:
/**
* @route GET /books/
* @param int $offsit
* @param int $limit
*/
public function getBooks($offsit=0, $limit=10)
注意:php 方法的默认参数, 必须放在方法的最后
2. 输出绑定¶
2.1. 绑定return¶
默认情况下,函数的返回值将 jsonencode 后,作为 body 输出。如
/**
* @route GET /books/{id}
*/
public function getBook($id)
{
return ['name'=>'PhpBook', 'desc'=>'PhpBook Document'];
}
curl 请求将得到以下结果
$ curl "http://localhost/books/1"
{
"name": "PhpBook",
"desc": "PhpBook Document"
}
注意,这里为便于演示,直接在方法中返回了数组(其实这在其他语言里算对象),但你应该为这种返回定义一个类,首先,有很多改善代码质量的理由鼓励使用对象替代这类数组,其次在自动生成文档时,这类数组无发被结构化描述。
2.2. 绑定引用参数¶
如果方法的参数是引用类型,则这个参数将不会从请求中获取,而是作为输出。比如:
/**
* @route GET /books/
* @param int $offsit
* @param int $limit
* @return Books[]
*/
public function getBooks($offsit=0, $limit=10, &$total)
{
$total = 1;
return [new Books()];
}
curl 请求将得到以下结果
$ curl "http://localhost/books"
{
"total": 1,
"data": [
{
"name":null,
"desc":null
}
]
}
可以看到,$total 输出到了 http body 中。 注意:当接口存在引用参数时,接口的返回值将会被默认绑定到response.content.data,效果和声明{@bind response.content.data}一致。
3. @bind¶
通过@bind,可以改变默认的绑定关系,将参数与其他输入项绑定,如:
/**
* @route GET /books/
* @return Books[] {@bind response.content.books}
*/
public function getBooks($offsit=0, $limit=10, &$total)
表示将返回绑定到响应 body 的 books 变量(响应默认是 json)。
3.1. 绑定输入¶
- 请求Body: request.request
- Query String: request.query
- Cookie: request.cookies
- 请求Header:request.headers
- 文件:request.files
3.2. 绑定输出¶
- 响应Body: response.content
- Cookie: response.cookies
- 请求Header:response.headers
参数校验¶
在”参数绑定”时,起始已经支持了两项基本的校验(类型和是否必选),如果要支持更复杂的校验规则,可以通过 @v 指定,如:
/**
* @route GET /books/
* @param int $offsit {@v min:0}
* @param int $limit {@v min:1|max:100}
*/
public function getBooks($offsit=0, $limit=10)
2. 支持的规则¶
- required - Required field
- equals - Field must match another field (email/password confirmation)
- different - Field must be different than another field
- accepted - Checkbox or Radio must be accepted (yes, on, 1, true)
- numeric - Must be numeric
- integer - Must be integer number
- boolean - Must be boolean
- array - Must be array
- length - String must be certain length
- lengthBetween - String must be between given lengths
- lengthMin - String must be greater than given length
- lengthMax - String must be less than given length
- min - Minimum
- max - Maximum
- in - Performs in_array check on given array values
- notIn - Negation of in rule (not in array of values)
- ip - Valid IP address
- email - Valid email address
- url - Valid URL
- urlActive - Valid URL with active DNS record
- alpha - Alphabetic characters only
- alphaNum - Alphabetic and numeric characters only
- slug - URL slug characters (a-z, 0-9, -, _)
- regex - Field matches given regex pattern
- date - Field is a valid date
- dateFormat - Field is a valid date in the given format
- dateBefore - Field is a valid date and is before the given date
- dateAfter - Field is a valid date and is after the given date
- contains - Field is a string and contains the given string
- creditCard - Field is a valid credit card number
- optional - Value does not need to be included in data array. If it is however, it must pass validation.
依赖注入¶
PhpBoot 使用开源项目 PHP-DI 作为依赖注入的基础实现。
2. 自动注入¶
2.1. 构造函数注入¶
class Books
{
/**
* @param LoggerInterface $logger 通过依赖注入传入
*/
public function __construct(LoggerInterface $logger)
{
$this->logger;
}
...
}
2.2. 属性注入¶
class Books
{
use EnableDIAnnotations; //启用通过@inject标记注入依赖
/**
* @inject
* @var DB
*/
private $db;
}
注意:PhpBoot 禁用了PHP-DI的 Annotation 注入方式,@inject 方式是 PhpBoot 实现的
数据库¶
1. 配置¶
可以通过依赖注入的方式,对数据库进行配置。
在需要数据库的类中加入依赖注入代码:
use PhpBoot\DB\DB; use PhpBoot\DI\Traits\EnableDIAnnotations; class Books { use EnableDIAnnotations; //启用通过@inject标记注入依赖 /** * @inject * @var DB */ private $db; public function getBooks()... }
框架在实例化
Books
后,根据@inject
注释, 自动给属性$db
赋值,其逻辑等价于:$books->db = $app->get(DB::class);
修改数据库配置
在 config.php 中加入以下配置(数据库地址等需根据实际情况修改):
'DB.connection'=> 'mysql:dbname=phpboot-example;host=127.0.0.1', 'DB.username'=> 'root', 'DB.password'=> 'root', 'DB.options' => [],
2. 编写 SQL¶
下面将通过实现 createBook、deleteBook、updateBook、findBooks 方法,演示insert、delete、update、select 的使用。
2.1 INSERT¶
public function createBook(Book $book)
{
$newId = $this->db->insertInto('books')
->values([
'name'=>$book->name,
'brief'=>$book->brief,
...
])
->exec()
->lastInsertId();
return $newId;
}
2.2 DELETE¶
public function deleteBook($id)
{
$this->db->deleteFrom('books')
->where(['id'=>$id])
->exec();
}
2.3 UPDATE¶
public function updateBook(Book $book)
{
$this->db->update('books')
->set([
'name'=>$book->name,
'brief'=>$book->brief,
...
])
->where(['id'=>$book->id])
->exec();
}
2.4 SELECT¶
public function findBooks($name, $offsit, $limit)
{
$books = $this->db->select('*')
->from('books')
->where('name LIKE ?', "%$name%")
->orderBy('id')
->limit($offsit, $limit)->get();
return $books;
}
3. 高级用法¶
上述示例展示了PhpBoot\DB
的基础用法,PhpBoot\DB
同时也支持更复杂的SQL。
3.1 复杂 WHERE¶
类似 SQL WHERE a=1 OR (b=2 and c=3)
, 可以以下代码实现:
->where(['a'=>1])
->orWhere(function(ScopedQuery $query){
$query->where(['b'=>2, 'c'=>3])
})
上面例子中,ScopedQuery
中还能再嵌套 ScopedQuery
。
3.2 JOIN¶
$db->select('books.*', DB::raw('authors.name as author'))
->from('books')
->where(['books.id'=>1])
->leftJoin('authors')->on('books.authorId = authors.id')
->get()
3.3 WHERE ... IN ...¶
使用PDO
时,WHERE IN
的预处理方式很不方便,需要为IN
的元素预留数量相等的?
, 比如:
$pdo->prepare(
'SELECT * FROM table WHERE a IN (?,?,?)'
)->execute([1,2,3])
而使用PhpBoot\DB
可以解决这个问题:
$db->select()->from('table')->where('a IN (?)', [1,2,3]);
3.4 使用 SQL 函数¶
默认情况下,框架会对输入做转换, 如会在表名和列名外加上``
,会把变量作为绑定处理,比如下面的语句
$db->select('count(*) AS count')
->from('table')
->where(['time'=>['>'=>'now()']]);
等价 的 SQL:
SELECT `count(*) AS count` FROM `table` where `time` > 'now()'
如果希望框架不做转换,需要使用DB::raw()
,比如:
$db->select(DB::raw('count(*) AS count'))
->from('table')
->where(['time'=>['>'=>DB::raw('now()')]]);
与下面 SQL 等价
SELECT count(*) AS count FROM `table` where `time` > now()
3.5 子查询¶
下面代码演示子查询用法:
$parent = $db->select()->from('table1')->where('a=1');
$child = $db->select()->from($parent);
与下面 SQL 等价
SELECT * FROM (SELECT * FROM `table1` WHERE a=1)
3.6 事务¶
$db->transaction(
function(DB $db){
$db->update('table1')->...
$db->update('table1')->...
}
)
事务允许嵌套,但只有最外层的事务起作用,内部嵌套的事务与最外层事务将被当做同一个事务。
4. 使用多个数据库¶
PhpBoot 为DB
类定义了默认的构造方式,形式如下:
DB::class => \DI\factory([DB::class, 'connect'])
->parameter('dsn', \DI\get('DB.connection'))
->parameter('username', \DI\get('DB.username'))
->parameter('password', \DI\get('DB.password'))
->parameter('options', \DI\get('DB.options')),
所以如果你的业务只使用连接一个数据库,只需要对DB.connection, DB.username ,DB.password, DB.options
进行配置即可。但有的时候可能需要对在应用中连接不同的数据库,这时可以通过依赖注入配置多个库,如:
先配置另一个数据库连接
'another_db' => \DI\factory([DB::class, 'connect']) ->parameter('dsn', 'mysql:dbname=phpboot-example;host=127.0.0.1') ->parameter('username', 'root') ->parameter('password', 'root') ->parameter('options', [])
在需要的地方注入此连接
use PhpBoot\DB; class Books { use EnableDIAnnotations; //启用通过@inject标记注入依赖 /** * @inject another_db * @var DB */ private $db2; }
文档输出¶
1. Swagger 文档¶
Swagger 是流行的 HTTP API 描述规范,同时 Swagger 官方还提供了丰富的工具,比如用于文档展示和接口测试的 Swagger UI, 相关资料请阅读官方文档。
以 phpboot-example 为例,生成的文档如下。文档中除了描述了接口的路由、参数定义、参数校验,还提供了接口测试工具。点击这里查看在线 Demo
PhpBoot 项目可以很方便的生成 Swagger 文档,无需添加额外的 Annotation(很多框架为支持 Swagger,通常需要增加很多额外的注释,而这些注释只用于 Swagger。PhpBoot 生成 Swagger 的信息来自路由的标准注释,包括@route, @param, @return,@throws 等)
如需开启 Swagger 文档,只需在在 Application 初始化时 添加以下代码:
PhpBoot\Docgen\Swagger\SwaggerProvider::register($app , function(Swagger $swagger){
$swagger->host = 'example.com';
$swagger->info->description = 'this is the description of the apis';
...
});
然后访问你的项目 url+/docs/swagger.json如( http://localhost/docs/swagger.json)
,即可获取 json 格式的 Swagger 文档。
2. MarkDown 文档¶
开发中...
ORM¶
目前 PhpBoot 提供基本的 ORM 支持,包括:
1. 定义实体¶
实体对应数据库的表, 实体的属性名和数据库的列名一致。下面是一个典型的实体定义:
/**
* 图书信息
* @table books
* @pk id
*/
class Book
{
/**
* @var int
*/
public $id;
/**
* 书名
* @var string
*/
public $name='';
/**
* 图片url
* @var string[]
*/
public $pictures=[];
}
其中:
- @table 指定表名
- @pk 指定表的主键
- @var 定义列的类型,如果类型为对象或者数组,则保存到数据库是将被序列化为 json
可以看到,ORM 中的实体和接口中的实体很类似,事实上,我们鼓励在 ORM 和接口中复用实体类。
2. 操作数据库¶
PhpBoot 提供量个组方法,model
和 models
, 分别用于操作实体“实例”和实体“类”。
2.1. model 方法¶
model()
方法用于操作实体“实例”,或者说操作单个实体对象。
2.1.1 create¶
存储指定实体实例(对应 SQL 的 insert)
$book = new Book();
$book->name = ...
...
\PhpBoot\model($this->db, $book)->create();
echo $book->id; //获取自增主键的值
2.1.2 update¶
更新实体对应的数据库记录(对应 SQL 的 update)
$book = new Book();
$book->id = ...
...
\PhpBoot\model($this->db, $book)->update();
2.1.3 delete¶
删除实体对应的数据库记录(对应 SQL 的 delete )
$book = new Book();
$book->id = ...
\PhpBoot\model($this->db, book)->delete();
2.2 models 方法¶
models()
方法用于操作实体“类”,或者说操作一组实体。
2.2.2. findWhere¶
根据组合查询条件查找(对应 SQL 的 select )
$books = \PhpBoot\models($this->db, Book::class)
->findWhere(['name'=>'abc'])
->get();
2.2.3. update¶
根据主键更新(对应 SQL 的 update )
\PhpBoot\models($this->db, Book::class)
->update(1, ['name'=>'abc']);
2.2.4. updateWhere¶
根据组合查询条件更新(对应 SQL 的 update )
\PhpBoot\models($this->db, Book::class)
->updateWhere(['name'=>'abc'], ['id'=>1])
->exec();
2.2.7. deleteWhere¶
根据组合查询条件删除(对应 SQL 的 delete )
\PhpBoot\models($this->db, Book::class)
->deleteWhere(['id'=>1])
->exec();
AOP(Hook)¶
PhpBoot 通过 Hook 的方式实现 AOP(面向切面编程),说明待补充... 以下演示如果定义和使用 Hook
1. 定义 Hook¶
演示如何通过 Hook 实现 Basic Authorization 登录校验
/**
* 简单登录校验
*
* 实现了 Basic Authorization
* @package App\Hooks
*/
class BasicAuth implements HookInterface
{
/**
* @param Request $request
* @param callable $next
* @return Response
*/
public function handle(Request $request, callable $next)
{
$auth = $request->headers->get('Authorization');
$auth or \PhpBoot\abort(new UnauthorizedHttpException('Basic realm="PhpBoot Example"', 'Please login...'));
$auth = explode(' ', $auth);
$auth[1] == md5("{$this->username}:{$this->password}") or fail(new UnauthorizedHttpException('Basic realm="PhpBoot Example", "Invalid username or password!"'));
return $next($request);
}
/**
* @var string
*/
public $username;
/**
* @var string
*/
public $password;
}
可以看到,Hook 只需要继承HookInterface,实现 handle 方法。
2. 使用 Hook¶
为需要的接口添加此 Hook
2.1. 通过 @hook 添加 Hook¶
/**
* @route POST /books/
* @param Book $book {@bind request.request}
* @hook \App\Hooks\BasicAuth 指定此接口需要BasicAuth校验
*/
public function createBook(Book $bok)
一个接口可以指定多个 Hook,执行的顺序依照@hook 定义的顺序。
2.2. 添加路由时指定 Hook¶
Application::addRoute()、Application::loadRoutes*() 方法添加路由时,可以指定 Hook ,如:
$app->loadRoutesFromPath($path, [BaseAuth::class]);
2.3. 设置全局 Hook¶
Application::setGlobalHooks 用于设置全局 Hook, 如:
Application::setGlobalHooks([BaseAuth::class]);
全局 Hook 不依赖于是否存在路由,即就算没有请求对应的路由,全局 Hook 还是会被执行。
关于 Hook 的更多细节, 可以参考\PhpBoot\Controller\Hooks\Cors的实现。
RPC¶
PRC即远程过程调用,是一种常用的分布式系统间访问接口的方式。PhpBoot 提供强大又简单易用的 RPC 支持,可以让你像使用本地接口一样,方便的使用远程接口。
1. 示例¶
下面将通过实现一个订单服务的示例,演示 PhpBoot RPC 的使用。
1.1. 定义接口¶
为保持示例尽量简单,这里我们只实现“创建订单”这一个接口。
/**
* @path /orders
*/
interface OrderServiceInterface
{
/**
* @route POST /
* @param ProductInfo $product 商品快照
* @return string 返回订单号
*/
public function createOrder(ProductInfo $product);
}
1.2. 实现接口¶
接口定义好以后, 我们需要在服务端,实现该服务接口,以便可以对外提供访问。
/**
* @path /orders
*/
class OrderService implements OrderServiceInterface
{
/**
* @route POST /
* @param ProductInfo $product 商品快照
* @return string 返回订单号
*/
public function createOrder(ProductInfo $product)
{
// create the order
return $orderId;
}
}
1.3. 远程调用接口¶
在客户端,可以通过下面方法调用远程的接口。
$orderService = $app->make(
RpcProxy::class,
[
'interface'=>OrderServiceInterface::class,
'prefix'=>'http://10.x.x.1/'
]
);
/**@var OrderServiceInterface $orderService*/
$orderId = $orderService->createOrder($product);
另一种推荐的方法是通过依赖注入创建代理类。如
//配置依赖
return [
OrderServiceInterface::class
=> \DI\objet(RpcProxy::class)
->constructorParameter('interface', OrderServiceInterface::class)
->constructorParameter('prefix', 'http://10.x.x.1/')
]
// 注入依赖
class AnotherService
{
...
/**
* @inject
* @var OrderServiceInterface
*/
private $orderService;
public function doSomething()
{
$orderId = $this->orderService->createOrder($product)
}
}
2. 注意¶
**由于 RpcProxy 默认通过 __call 实现远程方法的调用,所以无法传递引用参数。当接口参数中存在引用参数时,应该针对接口实现一个RpcProxy的子类,并重写包含引用参数的方法。以下是示例**
// 这是个典型的例子,接口的方法中有引用类型参数
/**
* @path /orders
*/
interface OrderServiceInterface
{
/**
* @route GET /
* @param int $offset
* @param int $limit
* @param int $total 此为引用类型参数, 用于返回查询的总条数
* @return Order[] 返回订单列表
*/
public function getOrders($offset, $limit, &$total);
}
// 这是个典型的例子,接口的方法中有引用类型参数
/**
* @path /orders
*/
class OrderServiceProxy extends RpcProxy implements OrderServiceInterface
//如果不想实现OrderServiceInterface的所有方法,也可以不继承OrderServiceInterface
{
/**
* @route GET /
* @param int $offset
* @param int $limit
* @param int $total 此为引用类型参数, 用于返回查询的总条数
* @return Order[] 返回订单列表
*/
public function getOrders($offset, $limit, &$total)
{
return $this->__call(__FUNCTION__, [$offset, $limit, &$total]);
}
}
//接下来可以通过OrderServiceProxy 访问远程接口了
$orderService = $app->make(
OrderServiceProxy::class,
[
'interface'=>OrderServiceInterface::class,
'prefix'=>'http://10.x.x.1/'
]
);
$orderService->getOrders...
3. 并发访问¶
在使用远程服务时,有时可能需要同时访问多个远程接口。如果能并行执行,在一些情况下可以大大减少接口执行时间。PhpBoot RPC 提供了并发执行的功能。使用方法如下:
$orderService = $app->make ...
$bookService = $app->make ...
$rpcRes = MultiRpc::run([
function()use(orderService){
return orderService->getOrders(...);
},
function(){
return bookService->getBooks(...);
}
])
$res = []
foreach($rpcRes as $i){
list($success, $error) = $i
if($error){
//执行失败的原因
}else{
//执行成功, 处理$success
}
}
return $res;
注意,MultiRpc 内部是将需并发执行的操作,调用转换为递归调用,并在递归的最后,等待所有异步操作完成。 所以实际上,真正并发执行的只是网络请求,所有网络请求结束后,后续代码执行还是串行的
工作流¶
这将是一个简单、轻量、健壮、可扩展、适用于自动化为主的、支持主要的BPMN要素(活动、网关、事件)、可持久化、但不准备支持所有BPMN2特性, 的工作流引擎,目前还在 Workflow 分支上开发...
1. 将可以通过如下方式定义流程¶
$engine = new ProcessEngine();
$process = new Process();
$builder = new ProcessBuilder($process);
//定义流程
$builder
->begin
->task(null, CreateOrderTask::class, '创建订单')
->eFork('eFork1', '事件网关')
->listener(null, 'paid', '等待支付')
->task(null, ShipTask::cass, '发货')
->xJoin('xJoin1', '排他网关')
->end;
$builder
->eFork1
->timer(null, 3600, '支付超时')
->task(null, CloseOrderTask::class, '关闭订单')
->xJoin1
//执行流程
$process->run($engine);
FAQ¶
是否必须使用 APC 扩展¶
PhpBoot 框架为提高性能, 会将路由及Annotation 分析后的其他元信息进行缓存。生产环境建议使用 APC 扩展, 开发环境可以用文件缓存代替 apc, 方法是在 config.php 里加一个配置
Cache::class => \DI\object(FilesystemCache::class)
->constructorParameter('directory', sys_get_temp_dir())
composer 更新失败怎么办¶
packagist.org 国内访问不稳定,可以翻墙试试,或者用国内的镜像phpcomposer, 执行下面命令
composer config repo.packagist composer https://packagist.phpcomposer.com