Welcome to Diabolo’s documentation!¶
简介¶
介绍¶
Diabolo 是一个微核心的PHP框架,框架本身仅包含基础的资源管理功能,其他的功能由外部的服务或模块实现。
核心¶
- 事件管理
- 服务管理
- 模块管理
服务¶
- 数据库 Mysql/Sqlite/ActiveRecord
- 缓存 Redis/数据库
- 会话管理 自定义存储/自动启动
- 云存储管理 七牛/阿里云/又拍云/S3
- 动作管理 Web/命令行 动作处理/视图输出
- OAuth2.0 接口动作/参数验证/文档生成
- 错误处理 邮件通知/自定义错误界面
- 国际化
- 日志 自定义格式/存储方式
- 资源管理 发布
模块¶
- 代码生成 动作/表单/数据库模型
- 接口测试/文档生成
其他¶
- 数据验证
- 表单模型
开始使用¶
准备¶
进入到项目根目录
创建Demo模块:
$ mkdir Module # 创建用于存放模块的目录 $ mkdir Module/Demo # 创建Demo模块目录 $ vi Module/Demo/Module.php # 编辑模块类
文件内容:
<?php namespace X\Module\Demo; # 模块命名空间 use X\Core\Module\XModule; # 引入模块基础类 class Module extends XModule { # 当该模块运行的时候,将会调用该方法 public function run($parameters = array()) { echo "This is a demo module."; } }
创建启动文件,例如index.php, 然后在该文件中初始化并运行:
<?php define("DIABOLO_PATH", __DIR__); # 定义框架路径 require DIABOLO_PATH.'/Core/X.php'; # 导入框架 $config = array( # 配置运行信息 'document_root' => __DIR__, # 项目根路径 'module_path' => array(), # 模块搜索路径 'service_path' => array(), # 服务搜索路径 'library_path' => array(), # 公共库搜索路径 'modules' => array( # 引用模块列表 'Demo' => array( # 定义模块 'enable' => true, # 启用模块 'default' => true, # 设置为默认模块 'params' => array(), # 模块配置参数 ), ), 'services' => array(), # 引用服务列表 'params' => array(), # 其他自定义参数 ); X\Core\X::start($config)->run(); # 启动并运行
最终的目录结构:
project | -- Core # Diabolo存放目录 ` -- Module # 模块存放目录 ` - Demo # Demo 模块目录 ` - Module.php # 模块主文件
全局配置¶
范例¶
基础配置
array( # 配置运行信息
'document_root' => __DIR__, # 项目根路径
'module_path' => array(), # 模块搜索路径
'service_path' => array(), # 服务搜索路径
'library_path' => array(), # 公共库搜索路径
'modules' => array(), # 引用模块列表
'services' => array(), # 引用服务列表
'params' => array(), # 其他自定义参数
);
项目说明¶
document_root
项目根目录路径, 当使用
X::system()->getPath()
的时候会依据该目录计算出绝对路径。 例如,设置document_root
为 /myproject/demo/echo X\Core\X::system()->getPath("View/Index.php");
将会输出
/myproject/demo/View/Index.php
module_path
设置模块搜索路径,当加载模块的时候将在这些目录中寻找模块。 列表中的路径地址必须是绝对路径, 并且可以不在当前项目中。 例如:
'module_path' => array( '/myproject/demo/Module', '/usr/local/diabolo/Module', )
这样, 框架将会按照上面的顺序寻找模块,找到后将停止寻找。
service_path
设置服务搜索路径,当加载服务的时候将在这些目录中寻找服务。 列表中的路径地址必须是绝对路径, 并且可以不在当前项目中。 例如:
'service_path' => array( '/myproject/demo/Service', '/usr/local/diabolo/Service', )
这样, 框架将会按照上面的顺序寻找服务,找到后将停止寻找。
library_path
设置第三方库的搜索路径,当加载lib的时候将在这些目录中寻找。 列表中的路径地址必须是绝对路径, 并且可以不在当前项目中。 例如:
'library_path' => array( '/myproject/demo/Library', '/usr/local/diabolo/Library', )
这样, 框架将会按照上面的顺序寻找库,找到后将停止寻找。
modules
配置项目运行所需的模块列表。 例如:
'modules' => array( 'Demo' => array( 'enable' => true, 'default' => true, 'params' => array( 'param001' => 'value of param001', ), ), )
模块数组的键名是模块名
enable
: true|false 配置是否启用该模块default
: true|false 配置是否为默认模块,当参数没有指定运行参数的时候,默认的模块将会被运行params
: 模块的配置项目, 在模块的代码中可以使用\X\Module\Demo::getModule()->getConfiguration()->get("param001")
获取值 “value of param001”
services
配置项目运行所需的服务列表 例如:
'services' => array( 'Demo' => array( 'class' => '\\X\\Service\\Demo\\Service', 'enable' => true, 'delay' => true, 'params' => array( 'param001' => 'value of param001', ), ), )
服务数组的键名是服务名
class
服务类名称enable
true|false 配置是否启用该服务delay
true|false 是否延迟加载, 延迟加载将在仅仅服务调用的时候加载服务,如果没有调用,则服务不会被启动。如果为false,则当项目启动的时候自动加载该服务。params
: 服务的配置项目, 在服务的代码中可以使用\X\Service\Demo::getService()->getConfiguration()->get("param001")
获取值 “value of param001”
params
全局配置, 在代码中可以使用
\X\Core\X::system()->getConfiguration()->get("params")->get("param001")
获取值 “value of param001” 例如:'params' => array( 'param001' => 'value of param001', ),
模块¶
创建模块¶
模块目录结构
project `- Module |- ModuleName | `- Module.php `- Example `- Module.php
创建存放模块的目录,并将该目录添加到配置文件的
module_path
中。创建模块文件夹,文件夹名称与模块名相同。
创建模块类文件,文件名固定为
Module.php
, 其内容如下:<?php namespace X\Module\Example; use X\Core\Module\XModule; class Module extends XModule { /** * {@inheritDoc} * @see \X\Core\Module\XModule::run() */ public function run($parameters = array()) { echo "This is an example module."; } }
模块的实现类必须要定义
run()
方法,该方法是模块运行时执行实际的请求处理。 比如根据参数调用操作处理方法,然后渲染视图输出等。注册模块,将模块信息写入
modules
'modules' => array( 'Example' => array( 'enable' => true, 'default' => true, 'params' => array( 'pretty_name' => '测试模块', ), ), ),
params
可以为空,这里是方便演示所以加了一个参数。
运行模块¶
框架通过获取
module
参数的值来加载指定的模块并运行, 例如web 方式 : http://example.com/index.php?module=example cli 方式 : php index.php --module=example上面两种方式最终都会输出
This is an example module.
.
获取模块信息¶
获取模块下文件路径
# 在 模块类 中 echo $this->getPath("Resource/Template.txt"); # 在其他文件中 \X\Module\Example\Module::getModule()->getPath("Resource/Template.txt");
假如项目地址为
/home/project/example
, 那么输出的路径为/home/project/example/Module/Resource/Template.txt
获取模块的配置信息
# 在 模块类 中 echo $this->getConfiguration()->get("pretty_name"); # 在其他文件中 \X\Module\Example\Module::getModule()->getConfiguration()->get("pretty_name");
将输出
测试模块
服务¶
服务创建¶
服务目录结构
project `- Service |- ServiceName | `- Service.php `- Example `- Service.php
创建存放服务的目录,并将该目录添加到配置文件的
service_path
中。创建模块文件夹,文件夹名称与模块名称相同。
创建模块类文件,文件名固定为
Service.php
,其内容如下<?php namespace X\Service\Example; class Service extends \X\Core\Service\XService { /** 定义服务名称 */ protected static $serviceName = 'Example'; /** 定义服务接口 */ public function doSomething() { return "something"; } }
注册服务,将服务信息写入
services
'services' => array( 'Example' => array( 'class' => '\\X\\Service\\Example\\Service', 'enable' => true, 'delay' => true, 'params' => array( 'param001' => 'value of param001', ), ), )
params
可以为空,这里是方便演示所以加了一个参数。
调用服务¶
在项目运行时,服务只会被实例化一次,可以通过
getService()
方法获取服务实例。echo X\Service\Example\Service::getService()->doSomething();将输出
something
。
start()
与 stop()
方法¶
如果希望在服务启动/停止的时候执行一些操作, 则需要重写
start()
或者stop()
方法。 例如, 如果我们想知道请求大概运行了多久。class Service extends \X\Core\Service\XService { /** 记录开始时间 */ private $startTime = null; /** 服务启动时记录开始时间 */ public function start() { parent::start(); $this->startTime = time(); } /** 服务结束的时候输出花费的时间 */ public function stop() { echo time() - $this->startTime(); parent::stop(); } }当然, 这需要在注册服务的时候,将
delay
设置为false
来禁用掉延迟加载。
事件¶
事件注册¶
目前事件注册只能通过代码手动注册,不支持配置的方式注册, 例如为事件 event001
注册处理器
$eventManager = \X\Core\X::system()->getEventManager();
# 注册事件处理器
$eventManager->registerHandler('event001', function( $param1 ) {
return "THIS IS CALLBACK RESPONSE";
});
# 注册事件结果
$eventManager->registerHandler('event001', "THIS IS A TEST RESULT");
当事件触发的时候,如果处理器是一个可调用的处理器,那么该处理器将会被执行,其返回结果将会返回给事件触发者,如果处理器不可以执行,那么,将直接返回该数据给触发者。
事件触发¶
事件需要手动触发, 例如触发上面的 event001
$eventManager = \X\Core\X::system()->getEventManager();
$result = $eventManager->trigger('event001', 'par001');
则 $result
为
array(
"THIS IS CALLBACK RESPONSE",
"THIS IS A TEST RESULT",
);
事件触发时能够传递任意多个参数,参数将会传递给事件处理器,以上面这个例子来说,
事件触发的时候传递一个参数值为 "par001"
, 那么, 第一个事件处理器中的 $param1
的值就是 "par001"
。
事件响应¶
事件触发完成后,返回的数据是包含每个处理器的返回结果数组。如果处理器没有返回,则对应未知的返回值为 null
。
动作服务¶
动作服务用于从请求参数中获取动作名称,然后从一组动作中实例化动作并执行的服务。
注册服务¶
配置内容:
'services' => array(
'XAction' => array(
'class' => '\\X\\Service\\XAction\\Service',
'enable' => true,
'delay' => true,
'params' => array(
'ActionParamName' => 'action',
'Error404Handler' => array(Error404::class, 'handle404Error'),
'Error500Handler' => array(Error500::class, 'handle500Error'),
'CommonViewPath' => 'View',
),
),
),
ActionParamName
动作参数名,用来告诉服务从哪里获取动作名称, 例如这里为action
, 那么,当web请求的参数中包含action
的时候, 服务将从取该参数的值作为动作名称。 当请求来自命令行时, 参数来自--action
。 例如GET http://example.com/index.php?module=example&action=show/hello
或者
php index.php --module=example --action=show/hello
都将调用
show/hello
动作。Error404Handler
404错误处理器, 取值可以为普通字符串或者回调函数, 如果配置为字符串,那么当发生404错误的时候,则显示该字符串; 如果该值能够被调用, 则404错误发生的时候执行该回调。 404能够在任何地方触发,例如\X\Service\XAction\\Service::getService()->triggerErrorHandler('404');
不过,推荐仅仅在动作中触发, 例如
$this->throw404IfTrue(true);
Error500Handler
500错误处理器, 取值可以为普通字符串或者回调函数, 如果需要禁用该功能,则赋值为null
。 当该值不为null
时,服务将注册一个错误处理器,并在发生错误的时候调用该值。CommonViewPath
视图放置路径,该路径是相对于模块或者整个项目的。 这里配置为View
, 则项目目录如下project |- Module | `- Example | `- View | |- Particle | | `- Index.php | `- Layout | `- View | - Particle | `- Header.php ` - Layout `- Default.php
初始化服务¶
动作服务的初始化主要是向服务中添加分组,一般该操作在模块类中进行, 例如
<?php
namespace X\Module\Example;
use X\Core\Module\XModule;
use X\Service\XAction\Service as XActionService;
/**
* 模块基类, 当前项目的所有模块都应当继承该类。
* <li>动作类都放到 Action 文件夹</li>
* <li>模块私有视图文件都放到 View 文件夹, 布局存放在 View/Layout, 片段存放在 View/Particle 下面</li>
*/
class Module extends XModule {
/**
* 使用 XAction 服务来分发动作。
*/
public function run($parameters = array()) {
$actionService = XActionService::getService();
# 组名必须唯一
$group = 'example';
# 注册分组
$actionService->addGroup($group, '\\X\\Module\\Example\\Action');
# 设置该分组的默认动作
$actionService->setGroupOption($group, 'defaultAction', 'index');
# 设置分组的视图路径
$actionService->setGroupOption($group, 'viewPath', $this->getPath('View/'));
# 设置运行参数
$actionService->getParameterManager()->merge($parameters);
# 运行分组
return $actionService->runGroup($group);
}
}
- 组名 组名在动作分组中需要唯一
- 注册分组时的命名空间名称是动作的基础命名空间名称, 例如动作为
user/login
,则最终的动作类为\X\Module\Example\Action\User\Login
。 - 分组的默认动作是当参数中没有指定动作名称的时候执行的动作。
- 视图路径是指该分组的视图路径。
- 当运行分组时,服务将在指定的分组中寻找动作并执行。
实现动作¶
动作类是实际用来处理请求的操作类。 例如
<?php
namespace X\Module\Example\Action\User;
use X\Service\XAction\Handler\WebPageAction;
class Index extends WebPageAction {
/** 布局 */
protected $layout = '/SingleColumn';
/** 标题 */
protected $title = '用户登录';
# 该方法用于接收参数并执行动作处理
protected function runAction( $userName ) {
$this->addParticle('/header');
$this->addParticle('login', array(
'default' => $userName, # 视图数据
));
$this->addParticle('/footer');
$this->display();
}
}
该动作继承
WebPageAction
用于处理一个网页请求并渲染界面。 目前支持WebPageAction
,CommandAction
和AjaxAction
这三类动作。对于视图的名称, 例如
/SingleColumn
或者/header
,login
之类, 如果以/
开头,则将会使用服务中配置的CommonViewPath
来获取, 否则 使用该动作所在分组的视图路径来获取。runAction()
方法用于最终的动作处理, 该方法的参数值将在初始化动作服务时获取。 以之前的初始化方式为例, 如果要获取$userName
的值, 则请求应该如下GET http://example.com/index.php?module=example&action=user/login&userName=example
当方法的参数没有默认值的时候,表示该参数必传否则将会出错。 有默认值的时候,如果 请求中不带该参数,则取默认值。
Web视图¶
web视图为php文件,视图分为布局和片段,布局用来展示网页的大体概貌, 片段是整个视图的一小部分,
并且由布局视图来管理在哪里显示。
web视图最终输出的内容是 html
中的 body
部分, head
部分由视图类来管理。
在布局文件中, $this
是 X\Service\XAction\Component\WebView\Html
的实例。
在片段文件中, $this
是 X\Service\XAction\Component\WebView\ParticleView
的实例。
普通视图文件
<div>
当前时间为:<?php echo date('Y-m-d H:i:s'); ?>
</div>
在布局文件中,通过以下方式增加资源
<?php
/* @var $this \X\Service\XAction\Component\WebView\Html */
/* @var $link \X\Service\XAction\Component\WebView\LinkManager */
$link = $this->getLinkManager();
# 注册CSS文件
$link->addCSS('bootstrap', '/lib/bootstrap/dist/css/bootstrap.css');
/* @var $script \X\Service\XAction\Component\WebView\ScriptManager */
$script = $this->getScriptManager();
# 注册 Js 文件
$script->add('jquery', '/lib/jquery/dist/jquery.min.js');
在片段文件中,通过以下方式增加资源
<?php
/* @var $this \X\Service\XAction\Component\WebView\ParticleView */
$html = $this->getManager()->getParent();
/* @var $link \X\Service\XAction\Component\WebView\LinkManager */
$link = $html->getLinkManager();
# 注册CSS文件
$link->addCSS('bootstrap', '/lib/bootstrap/dist/css/bootstrap.css');
/* @var $script \X\Service\XAction\Component\WebView\ScriptManager */
$script = $html->getScriptManager();
# 注册 Js 文件
$script->add('jquery', '/lib/jquery/dist/jquery.min.js');
数据库服务¶
注册服务¶
配置内容
'services' => array(
'XDatabase' => array(
'class' => '\\X\\Service\\XDatabase\\Service',
'enable' => true,
'delay' => true,
'params' => array(
'migration_table_name' => 'xdatabase_dbmigration_histories',
'databases' => array (
'example' => array (
'dsn' => 'mysql:host=localhost;dbname=suanhetao-service-view',
'username' => 'username',
'password' => 'password',
'charset' => 'UTF8',
),
),
),
),
),
初始化服务¶
数据库服务不用初始化,当配置成功后即可直接使用。
SQL 创建器¶
服务允许直接以字符串的形式执行SQL语句, 例如
$sql = "DROP TABLE example";
$isSuccess = X\Service\XDatabase\Service::getService()->get()->exec($sql);
在不考虑更换数据库类型的情况下,这种方式是最好的。 但是如果后期数据库从 mysql
迁移到 sqlite 后,就会存在sql语句不兼用的问题,这个时候就推荐使用 SQLBuilder
来创建SQL语句。 虽然 SQLBuilder
不能完全解决SQL不兼容的问题,但是能够减轻
一部分的迁移工作, 尤其是字段,表名的转义等。 例如
Mysql : SELECT `from` FROM `table`
SQLITE : SELECT [from] FROM [table]
SQLBuilder 的目的不仅仅是解决上面的问题, 对于部分内置函数, SQLBuilder提供了统一 的调用方式,不用再关心切换数据库类型后的迁移方式。
使用SQLBuilder创建查询语句时,和普通的字符串方式不相同的是可变部分作为参数来实现, 例如
$dbname = 'example';
$sql = \X\Service\XDatabase\Core\SQL\Builder::build($dbname)->select()
->expression('name', 'nickname')
->expression('age', 'realage')
->expression('sex')
->from('example_students')
->where(array(
'name' => 'michael',
'sex' => 'm',
))
->offset(10)
->limit(10)
->orderBy('age', 'DESC')
->toString();
最终输出的SQL语句为
SELECT
`name` AS `nickname`,
`age` AS `realage`,
`sex`
FROM `example_students`
WHERE
`name`='michael'
AND `sex` = 'm'
ORDER BY `age` DESC
OFFSET 10
LIMIT 10
SQLBuilder 在生成SQL语句时传入的数据库名 $dbname
是用来告诉builder使用哪个
数据来生成, 因为不同的数据库引擎最终生成的SQL语句是不同的,默认情况下SQLBuilder
使用当前的数据库来生成。
Active Record¶
AR 是将数据表中的数据映射成一个对象,对该对象修改后将会同步到数据库中。 例如
use X\Module\Example\Model\User;
# 通过主键查询
$user = User::model()->findByPrimaryKey(1);
# 通过属性查询
$user = User::model()->find(array('name'=>'michael'));
# 通过属性查询多条
$users = User::model()->findAll(array('sex'=>'m'));
# 修改
$user->name = 'new-name';
$user->save();
# 创建
$user = new User();
$user->name = 'another michael';
$user->save();
# 删除
$user = User::model()->findByPrimaryKey(1);
$user->delete();
AR类的定义
<?php
namespace X\Module\Example\Model;
use X\Service\XDatabase\Core\ActiveRecord\XActiveRecord;
/**
* @property string $name
* @property string sex
*/
class User extends XActiveRecord {
/**
* 设置数据库配置名称,表明该AR类操作时使用的数据库链接
*/
public function getDatabaseName() {
return 'example';
}
/**
* 配置属性描述信息
*/
protected function describe() {
return array(
'name' => 'VARCHAR (256)',
'sex' => 'VARCHAR (1)',
);
}
/**
* 配置数据表名称
*/
protected function getTableName() {
return 'users';
}
}
表管理¶
Table Manager 是进行表管理的快捷方式, 可以在不用写SQL语句的情况下对表数据或者结构进行操作。 例如
use X\Service\XDatabase\Core\Table\Manager;
$tableName = 'example_table';
# 删除表
Manager::open($tableName)->drop();
# 更新表数据
Manager::open($tableName)->truncate();
# 更新表数据
Manager::open($tableName)->update('new-value', array('sex'=>'m'), 10, 5);
# 解锁表
Manager::open($tableName)->unlock();
# 重命名数据表
Manager::open($tableName)->rename('new_table_name');
# 锁表
Manager::open($tableName)->lock('READ');
# 插入一条数据
Manager::open($tableName)->insert(array('id'=>1,'name'=>'michael'));
# 获取表信息
$tableInfo = Manager::open($tableName)->getInformation();
# 创建表
Manager::create(
'another_table', # 表名
array('id'=>'int', 'age'=>'int'), # 表列定义
'id' # 主键
);
在操作表的时候, 如果表不存在, 则只能调用 create()
方法创建表之后继续操作,
如果表存在, 则需要调用 open()
方法打开表后再操作。
邮件服务¶
注册服务¶
配置内容
'services' => array( 'XMail'=>array ( 'enable' => true, 'class' => 'X\\Service\\XMail\\Service', 'delay' => true, 'params' => array( # 配置邮件服务器列表 'handlers' => array( 'default' => array( 'handler' => 'smtp', 'host' => 'smtp.163.com', 'port' => '25', 'from' => 'user@example.com', 'from_name' => 'Example', 'auth_required' => true, 'username' => 'user@example.com', 'password' => 'password', ), 'another' => array( 'handler' => 'smtp', 'host' => 'smtp.163.com', 'port' => '25', 'from' => 'another@example.com', 'from_name' => 'Example', 'auth_required' => true, 'username' => 'another@example.com', 'password' => 'password', ), ), ), ), ),
- 邮件服务支持配置多个邮件服务器。
- 当发送邮件时,如果没有指定服务器名称, 则会使用名称为
default
的邮件服务器。
发送邮件¶
配置完成后,服务不再需要初始化, 可以直接使用, 例如
use X\Service\XMail\Service;
$mailService = Service::getService();
# 创建一个 Mail 实例
$mail = $mailService->create('Demo Mail');
# 设置邮件发件人, ‘res-name’ 是收件人的名称, 可以不填。
$mail->addAddress('res@example.com', 'res-name');
# 设置邮件内容
$mail->setContent("This is a demo email");
# 发送邮件,成功将返回true.
if ( $mail->send() ) {
echo "成功";
} else {
echo $mail->ErrorInfo;
}
$mail = $mailService->create('Demo Mail');
# 使用名称为 another 的邮件服务器发送邮件。
$mail->setHandler('another');
$mail->addAddress('568109749@qq.com', 'GGGG');
$mail->setContent("This is a demo email");
$mail->send();
OAuth 2.0 服务¶
注册服务¶
配置内容
'services' => array( 'OAuth2' => array( 'class' => '\\X\\Service\\OAuth2\\Service', 'enable' => true, 'delay' => true, 'params' => array( # 存储方式 'storage_handler' => 'Pdo', # 存储配置 'storage_params'=>array( 'dsn'=>'mysql:dbname=demo_db_name;host=db_host', 'username'=>'db_user', 'password'=>'db_password' ), # 授权方式 'grant_types' => array( 'AuthorizationCode', 'ClientCredentials', 'RefreshToken', 'UserCredentials' ), # oauth 2.0 配置 'option' => array( 'use_jwt_access_tokens' => false, 'store_encrypted_token_string' => true, 'use_openid_connect' => false, 'id_lifetime' => 3600, 'access_lifetime' => 3600, 'www_realm' => 'Service', 'token_param_name' => 'access_token', 'token_bearer_header_name' => 'Bearer', 'enforce_state' => true, 'require_exact_redirect_uri' => true, 'allow_implicit' => false, 'allow_credentials_in_request_body' => true, 'allow_public_clients' => true, 'always_issue_new_refresh_token' => false, 'unset_refresh_token_after_use' => true, ), ), ), ),
初始化¶
初始化数据库,SQL
CREATE TABLE oauth_clients ( client_id VARCHAR(80) NOT NULL, client_secret VARCHAR(80), redirect_uri VARCHAR(2000), grant_types VARCHAR(80), scope VARCHAR(4000), user_id VARCHAR(80), PRIMARY KEY (client_id) ); CREATE TABLE oauth_access_tokens ( access_token VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(80), expires TIMESTAMP NOT NULL, scope VARCHAR(4000), PRIMARY KEY (access_token) ); CREATE TABLE oauth_authorization_codes ( authorization_code VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(80), redirect_uri VARCHAR(2000), expires TIMESTAMP NOT NULL, scope VARCHAR(4000), id_token VARCHAR(1000), PRIMARY KEY (authorization_code) ); CREATE TABLE oauth_refresh_tokens ( refresh_token VARCHAR(40) NOT NULL, client_id VARCHAR(80) NOT NULL, user_id VARCHAR(80), expires TIMESTAMP NOT NULL, scope VARCHAR(4000), PRIMARY KEY (refresh_token) ); CREATE TABLE oauth_users ( username VARCHAR(80), password VARCHAR(80), first_name VARCHAR(80), last_name VARCHAR(80), email VARCHAR(80), email_verified BOOLEAN, scope VARCHAR(4000), PRIMARY KEY (username) ); CREATE TABLE oauth_scopes ( scope VARCHAR(80) NOT NULL, is_default BOOLEAN, PRIMARY KEY (scope) ); CREATE TABLE oauth_jwt ( client_id VARCHAR(80) NOT NULL, subject VARCHAR(80), public_key VARCHAR(2000) NOT NULL );
插入演示数据
INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://fake/");
获取 Access Token¶
在请求资源之前,需要获取一个 access token, 然后才能够调用资源接口
$service = \X\Service\OAuth2\Service::getService(); $service->generateAccessToken()->send();假设调用该接口的url为
http://example.com/module=oauth2&action=token
, 则结果将会输出{ "access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35", "expires_in":3600, "token_type":"bearer", "scope":null }
处理资源请求¶
在请求资源接口时, 需要将上一步请求获取的access token放入请求的参数里面, 并且在处理请求的时候需要判断请求是否已经有效
$service = \X\Service\OAuth2\Service::getService(); if ( !$service->verifyResourceRequest() ) { echo json_encode(array( 'success' => false, 'message' => 'authoriation required', )); } echo json_encode(array( 'success' => true, 'message' => 'You accessed my APIs!', 'data'=>array('ver'=>'1.0.0') ));假设调用该接口的URL为
http://example.com/module=api&action=version
, 并且将access_token
作为POST参数传入, 调用成功后输出{ "success" : true, "message" : "You accessed my APIs!", "data" : { "ver" : "1.0.0" } }
错误处理服务¶
服务配置¶
配置内容
'services' => arrary( 'XError'=>array ( 'enable' => true, 'class' => 'X\\Service\\XError\\Service', 'delay' => false, 'params' => array( 'types' => E_ALL, # 错误类型 'handlers' => array( # 错误处理器 array( 'handler' => 'Email', # 发生错误时发送邮件 'mail_handler' => 'default', # 邮件处理名称 'subject' => 'Example Subject', # 邮件标题 'recipients' => array( # 收件人列表 'NameOfRecipient'=>'address@example.com' ), 'template' => 'default', # 邮件模板 'isHtml' => false, # 是否启用HTML格式发送 ), array( 'handler' => 'FunctionCall', # 函数调用 'callback' => array(ErrorHandler::class, 'handle'), # 错误回调函数 ), array( 'handler' => 'Url', # 发生错误时请求URL 'url' => 'http://www.example.com?action=handleError', # URL地址 'parameters' => array( # 请求参数 'code' => '?', # 错误代码 'message' => '?', # 错误消息 'file' => '?', # 错误文件 'line' => '?', # 错误行号 'user' => 'user', # 自定义参数 'password'=>'password', # 自定义参数 ), 'gotoUrl' => true, # 是否跳转到该URL 'method' => 'post', # 请求方法 ), array( 'handler' => 'View', # 发生错误时渲染该视图 'path' => 'View/Error.php', # 视图路径 ), ), ), ), );
types
错误类型,用于配置该服务处理的错误类型,默认为E_ALL
, 取值参考 : http://php.net/manual/zh/errorfunc.constants.phphandlers
错误处理器列表, 该服务支持配置多个错误处理器, 当错误发生时, 处理器将会被按照所配置的顺序进行执行。目前支持的处理类型包括 :Email
,FunctionCall
,Url
,View
这四种。Email
邮件处理, 邮件处理需要启用XMail
服务来进行邮件的发送。mail_handler
在XMail
中配置的handler
名称。template
邮件模板, 默认为default
, 如果不使用默认模板, 则配置的路径需要以项目的根目录来配置视图路径, 例如 ::View\Error.php
。
FunctionCall
回调处理, 回调处理的回调函数接受一个 $error 参数用于获取错误信息, 例如public static function handle( $error ) { var_dump($error); }
Url
URL处理, URL处理是在错误发生后调用该URL进行处理的操作。parameters
请求URL时传递的参数列表, 参数列表中对应的code
,message
,file
,line
, 将会被赋值为错误信息中的对应值。gotoUrl
是不是要跳转到对应的url, 如果该参数为true
, 则处理器将会输出一个界面并 然后跳转到指定url, 否则将直接请求该url,不做跳转。- 在
cli
模式下, 不能够使用gotoUrl
功能。
View
视图处理, 当错误发生后, 将渲染该视图。
错误处理¶
该服务不需要从外部调用,配置完成后将会自动处理错误。
会话管理服务¶
注册服务¶
配置内容
'services' => array( 'XSession' => array( 'enable' => true, 'class' => 'X\\Service\\XSession\\Service', 'delay' => false, 'params' => array( 'autoStart' => true, 'name' => 'My-Custom-Session-Name', 'holders' => array('cookie', 'get', 'post', 'request'), 'cookie' => array( 'lifetime'=>3600, 'path'=>'/', 'domain'=>'', 'secure'=> false, 'httponly'=>false ), 'storage' => null, ), ), ),
autoStart
是否自动启动,默认为true
,自动启动时将在服务启动的时候自动启动会话, 不用手动调用seesion_start()
或者$service->startSession()
来启动。name
会话参数名称,说明会话在初始化时从哪个参数获取会话id。如果你需要获取上传文件的进度信息, 你需要将该名称同步到php.ini或者apache或者其他web服务器的配置文件中,否则无法获取到文件上传信息(@see http://php.net/manual/en/session.upload-progress.php#119631)。holders
这个指明会话ID从哪里获取去,如果没有设置将采用PHP.ini中的设置, 在配置中的变量中, 第一个找到的会话ID将会被使用。cookie
配置cookie信息。storage
配置会话存储方式,如果为null, 将使用默认的存储方式。 目前支持以下的存储方式 :mongodb 使用 mongodb 存储, 配置如下
# mongodb 'storage' => array( 'type' => 'mongodb', # 数据库链接 'uri' => 'mongodb://127.0.0.1:27017', #'uri' => 'mongodb://username:password@host:port' # 数据库名称 'database' => 'diabolo', # collection 名称 'collection' => 'sessions', # 有效时间秒数 'lifetime' => 3600, ),
radis 使用 redis 存储, 配置如下
# redis 'storage' => array( 'type' => 'redis', # 服务器地址 'host' => '127.0.0.1', # 端口号 'port' => 6379, # 数据库索引号 'database' => 1, # 链接密码 'password' => 'redis-password', # 有效期 'lifetime' => 3600, # 键名前缀 'prefix' => 'SESSION:', ),
memcached 使用memcached 存储, 配置如下
# memcached 'storage' => array( 'type' => 'memcached', # memcached地址 'host' => '127.0.0.1', # 端口 'port' => 11211, # 键名前缀 'prefix' => 'SESSION:', # 过期时间秒数 'lifetime' => 3600, ),
database 使用数据库存储, 配置如下
'storage' => array( 'type' => 'database', # 数据库链接 'dsn' => 'mysql:host=database.host;port=3306;dbname=databasename', # 会话存储使用的表名 'table' => 'sessions', # 数据库用户名 'user' => 'username', # 数据库密码 'password' => 'password', # 在写入表时,处理额外的存储字段 'serializeHandler' => array(SessionSerializeHandler::class, 'serialize'), # 会话有效期的秒数 'lifetime' => 3600 ),
数据库表结构
CREATE TABLE `sessions` ( `ID` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL , -- 必须字段, 用于存储会话id `EXPIRED_AT` datetime NOT NULL , -- 必须字段,用于存储会话过期时间 `RAW` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL , -- 必须字段, 用于存储会话内容 `USER_ID` int(11) NULL DEFAULT NULL , -- 自定义字段, 由 serializeHandler 处理后的数据信息 PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC;
serializeHandler
用于在存储会话的时候为额外的字段赋值class SessionSerializeHandler { public static function serialize( ) { return array( 'USER_ID' => 100, ); } }
会话管理¶
startSession()
启动会话close($save=ture)
关闭当前会话,默认保存变更的会话信息destory()
销毁当前会话clean($lifetime=0)
清理过期的会话数据
Flash 使用¶
flash 在存储方式上和普通会话变量没有区别,但是,在获取指定flash之后, 该flash将会被自动删除。
$service = Service::getService();
$service->flashAdd('demo-flash', "This is a demo flash");
$service->flashHas('demo-flash'); # true
$content = $service->flashGet('demo-flash');
echo $content; # "This is a demo flash"
$service->flashHas('demo-flash'); # false
日志服务¶
注册服务¶
配置内容
'services' => array( 'XLog' => array( 'enable' => true, 'class' => XLogService::class, 'delay' => false, 'params' => array( 'level' => 'trace', 'cache' => true, 'handler' => 'file', /* 其他配置 */ ), ), ),
level
日志记录级别,当程序中记录的日志级别低于该配置项目的值时,日志内容将直接丢弃。目前支持以下几种, 以取值从低到高的顺序排列trace
当设置该级别的时候, 所有级别的日志都将记录。debug
调试info
信息warn
警告error
错误fatal
致命错误
cache
是否缓存日志,当缓存日志开启的时候,日志仅仅会在请求结束后写入存储设备, 否则将会在调用记录日志时立即写入。handler
日志记录方式,目前支持以下几种 :file
将日志写入文件,扩展参数如下'handler' => 'file', # 使用文件记录 'path' => '/tmp/diabolo.demo.log', # 日志存储地址 'enableDailyFile' => true, # 是否每天创建一个日志文件
database
将日志写入数据库, 扩展参数如下'handler' => 'database', # 使用数据库记录 'dsn' => 'mysql:host=localhost;dbname=test', # 数据库链接 'user' => 'user', # 数据库用户名,如果为空则为null 'password' => 'pass', # 数据库密码 'table' => 'runtimelog', # 日志表名 'attrs' => array( # 日志表列映射 'time' => '$time', 'type' => '$type', 'content' => '$content', ),
日志表列映射时,如果值以
$
开头, 则表明使用日志环境变量, 否则将会直接赋值给该列。 目前日志环境变量支持以下取值$time 日志记录时间,使用"时间戳.毫秒"的格式 $type 日志记录类型, 例如:info, error 等 $content 日志记录内容。
记录日志¶
在服务启动后,日志记录可以直接使用 X\Service\XLog\Logger
类来进行操作。
例如
use X\Service\XLog\Logger;
Logger::debug("This is a debug message");
Logger::error("This is an error message");
Logger::fatal("This is a fatal message");
Logger::info("This is an info message");
Logger::trace("This is a trace message");
Logger::warn("This is a warning message");
路由服务¶
注册服务¶
配置内容
'XRouter' => array(
'class' => '\\X\\Service\\XRouter\\Service',
'enable' => true,
'delay' => false,
'params' => array(
'router' => XActionRouter::class,
'fakeExt' => 'html',
),
),
fakeExt
虚拟扩展名,用于在生成URL或者路由的时候添加或者去掉后缀。router
路由处理类名称,目前支持以下几种路由 :X\Service\XRouter\Router\MapUrlRouter
URL映射方式, 配置如下'router' => X\Service\XRouter\Router\MapUrlRouter::class, 'regexAlias' => array( 'module' => '\w*?', 'action'=>'\w*?', 'id' => '[a-z0-9]{16}', 'example' => '.*?', ), 'rules' => array( # 请求URL => 路由后的URL '/{module}_{id}/{action}$' => 'module={module}&action={action}&{module}={id}', '/{module:$example}/' => 'module={module}&action=index', '/{module:@\w}/{target:$id}' => 'module={mdoule}&action=detail&{module}={id}', '/help'=>'index.php?module=dionysos&action=help', ),
regexAlias
正则表达式匹配列表, 用于在规则中使用rules
匹配规则, 在规则中,使用{
和}
包含起来的将会被转换为正则表达式用于匹配url, 格式为:{anytext}
,{anytext:$alias}
,{anytext:@regex}
这几种。在格式
{anytext}
中, 如果anytext
是regexAlias
中的键名, 则该格式效果同{anytext:$alias}
X\Service\XRouter\Router\XActionRouter
XAction服务方式, 配置如下'router' => X\Service\XRouter\Router\XActionRouter::class, 'fakeExt' => 'html', 'mainModuleName' => 'mainmodule', 'hideMainModuleName' => true, 'defaultAction' => 'index',
mainModuleName
主模块名称hideMainModuleName
是否隐藏主模块名称,例如/mainmodule/index
将会变为/index
, 隐藏了主模块的名称。defaultAction
默认的动作名称,当解析不到动作的时候将会被使用。
XAction路由不强制要求启用XAction服务, 该路由器匹配和生成URL基于以下规则
- 模块和动作将会组成请求的路径, 例如 :
/module/action/path
- 如果在参数列表中包含与路径相同名称的参数, 则会被链接到路径中,
例如 :
index.php?module=module&action=action/path&module=001
=>/module-001/action/path
- 不在请求路径的参数将会原封不动的添加到url中,
例如 :
index.php?module=main&action=search&text=123
=>/main/search?text=123
生成URL¶
当路由器使用的是XActionRouter时,该路由支持根据原生url生成格式化后的url。 例如
# fakeExt = html
use X\Service\XRouter\Router\XActionRouter;
XActionRouter::generate("/index.php?module=food&action=user/partner/edit&food=123&partner=426&from=team")
# 输出 : food-123/user/partner-426/edit.html?from=team
XActionRouter::action('food','user/partner/edit', array('food'=>123,'partner'=>4564,'xxx'=>222))
# 输出 : food-123/user/partner-4564/edit.html?xxx=222