添加后台代理代码

This commit is contained in:
2026-03-15 01:27:05 +08:00
parent 11f9ac4dc1
commit ea08c9366a
5254 changed files with 721042 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
<?php
/**
* $Id: AutoLoad.php 56761 2014-12-08 05:17:37Z caoyangmin $
* @author caoyangmin(caoyangmin@gmail.com)
* @brief AutoLoad
*/
namespace phprs;
require __DIR__.'/util/ClassLoader.php';
require __DIR__.'/util/AutoClassLoader.php';
use phprs\util\ClassLoader;
ClassLoader::addInclude(dirname(__DIR__));
spl_autoload_register(array(__NAMESPACE__.'\util\ClassLoader', 'autoLoad'));

View File

@@ -0,0 +1,109 @@
<?php
/**
* $Id: BindParams.php 60686 2015-03-10 10:48:49Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs;
use phprs\util\Verify;
use phprs\util\exceptions\BadRequest;
//TODO: 把绑定参数的各种信息保存在数组中, 已经有点难看了.
/**
*
* @author caoym
* 绑定响应
*/
class BindParams{
/**
* @param string $class_name
* @param string $method_name
*/
public function __construct($class_name, $method_name){
$this->class_name = $class_name;
$this->method_name = $method_name;
}
/**
* 设置绑定
* @param int $id 参数声明的序号
* @param $params 绑定相关参数:[目标变量 , 源变量]
* @param $method_info 方法变量信息 [ [变量名, 是否引用, 是否有默认值, 默认值], ...
*/
public function set($id, $params, $method_info){
Verify::isTrue(is_array($params) && count($params) ==2, "{$this->class_name}::{$this->method_name} invalid @param");
list($to, $from) = $params;
$pos = -1;
$step = 0;
foreach ($method_info as $item){
list($param_name, ) = $item;
if($to === $param_name){
$pos = $step;
break;
}
$step++;
}
Verify::isTrue($pos !== -1, "{$this->class_name}::{$this->method_name} param: $to not found");
Verify::isTrue(!isset($this->params[$pos]),"{$this->class_name}::{$this->method_name} param: $to repeated bound" );
$this->params[$pos] = array(substr($from, 0, 1) !='$', $from, $item,$id,);//[是否常量, 值 , 参数信息]
}
/**
* 绑定到函数调用的参数上去
* @param $req
* @param $res
* @param array $args
*/
public function bind($req, &$res, &$args){
foreach ($this->params as $pos=>$param){
list($is_const, $value, $info) = $param;
if($is_const){// 常量
$args[$pos] = $value;
}else{ //变量
list(, $is_ref, $is_optional, $default) = $info;
$found = $req->find($value, $is_ref, $default);
if(!$found[1]){
Verify::isTrue($is_optional, new BadRequest("{$this->class_name}::{$this->method_name} $value not found in request"));
$args[$pos] = $default;
}else{
if($is_ref){
$args[$pos] = &$found[0];
}else{
$args[$pos] = $found[0];
}
}
}
}
}
/**
* 获取被绑定的参数列表
* 返回的是参数位置
* @return array
*/
public function getBindParamPos(){
$params=array();
foreach ($this->params as $pos=>$param){
$params[] = $pos;
}
return $params;
}
/**
* @return boolean
*/
public function isEmpty(){
return count($this->params) ===0;
}
/**
* 获取绑定参数信息
* @return array
* [ [输入参数位置=>[是否常量, 值, 参数信息]], ...]
* 其中参数信息形式
* [变量名, 是否引用, 是否有默认值, 默认值]
*/
public function getParams(){
return $this->params;
}
private $params= array();
private $class_name;
private $method_name;
}

View File

@@ -0,0 +1,175 @@
<?php
/**
* $Id: BindReturns.php 60686 2015-03-10 10:48:49Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs;
use phprs\util\Verify;
/**
* 绑定@return 变量
* @author caoym
*
*/
class BindReturns
{
/**
*
* @param string $class_name
* @param string $method_name
* @param boolean $auto_bind_return 是否默认绑定返回值
*/
public function __construct($class_name, $method_name, $auto_bind_return=true){
$this->class_name = $class_name;
$this->method_name = $method_name;
$this->auto_bind_return = $auto_bind_return;
//默认return输出到body
if($auto_bind_return){
$this->params['body'][-1] = array(0=>array(false, null, -1, null));
}
}
/**
* 设置绑定
* @param $params 绑定相关参数:[目标变量 , 源变量]
* @param $method_info 方法变量信息 [变量名=>[是否引用, 是否有默认值, 默认值]]
*/
public function set($id, $params, $method_info){
Verify::isTrue(is_array($params)||is_string($params), "{$this->class_name}::{$this->method_name} invalid @return");
if (is_string($params)){
$to = $params;
$from = array();
}else{
$to = $params[0];
$from = array_slice($params, 1);
}
$to_func = &$this->params[$to][$id];
if($this->auto_bind_return && ($to == 'body'||$to == 'res') && isset($this->params['body'][-1])){
unset($this->params['body'][-1]);// 指定了body输出, 去掉默认的body输出
}
if(0 === count($from)){
$to_func[0]=array(false, null, -1, null,);
}
foreach ($from as $index=>$name){ // 输入方法 $index变量序号 $name变量名
$is_const = (substr($name, 0, 1) !='$');
if ($is_const){ // 输出常量, 不需要绑定变量
$to_func[$index] = array(true, $name, 0, null,);//[是否常量, 值 , 参数来源位置, 参数信息]
continue;
}
$name = substr($name, 1);
//变量输出, 需要绑定
$pos = -1;
$step = 0;
//是(输出)方法的第几个参数
foreach ($method_info as $item){
list($param_name, ) = $item;
if($name === $param_name){
$pos = $step;
break;
}
$step++;
}
Verify::isTrue($pos !== -1, "{$this->class_name}::{$this->method_name} param: $name not found");
//只能是引用
list(, $is_ref, ) = $item;
Verify::isTrue($is_ref, "{$this->class_name}::{$this->method_name} param: $name @return must be a reference");
$to_func[$index] = array(false, $name, $pos, $item,);//[是否常量, 值 , 参数位置, 参数信息]
}
}
/**
* 绑定到函数调用的参数上去
* @param $req
* @param $res
* @param array $args
*/
public function bind($req, &$res, &$args)
{
//params保存的数据
// [
// 'body'=>[// 输出方法
// [arg1, arg2, arg3], //调用列表
// [arg1, arg2, arg3]
// ],
// ]
// ]
//没有指定body输出, 默认使用返回值作为输出
foreach ($this->params as $fun_name => $calls) {//
foreach ($calls as $id => $call) {
foreach ($call as $num => $arg) { // 方法
list ($is_const, $value, $pos, $info) = $arg;
if ($is_const) { // 常量,直接输出
$res[$fun_name][$id][$num] = $value;
} else if($pos === -1){ // 返回值作为变量输出
$res[$fun_name][$id][$num] = &$this->return_val;
}else{
if (! array_key_exists($pos, $args)) { // 没有输入,只有输出
list (, $is_ref, $is_optional, $default) = $info;
if ($is_optional) {
$args[$pos] = $default;
} else {
$args[$pos] = null;
}
}
$res[$fun_name][$id][$num] = &$args[$pos];
}
}
}
}
}
/**
* 设置返回值
* @param unknown $var
*/
public function setReturn($var){
$this->return_val = $var;
}
/**
* 获取被绑定的参数列表
* 返回的是参数位置
* @return array
*/
public function getBindParamPos(){
$params=array();
foreach ($this->params as $fun_name => $calls) {
foreach ($calls as $id => $call) {
foreach ($call as $num => $arg) {
list ($is_const, $value, $pos, $info) = $arg;
if($pos !== -1){
$params[] = $pos;
}
}
}
}
return $params;
}
/**
* @return boolean
*/
public function isEmpty(){
return count($this->params) ===0;
}
/**
* 返回绑定的参数信息
* @return array
* [[输出方法=>[[是否常量, 值 , 输出参数位置, 参数信息],..]]
*
*/
public function getParams(){
return $this->params;
}
private $params= array();// [目标=>[[是否常量, 值 , 参数位置, 参数信息],..]]
private $class_name;
private $method_name;
private $return_val;
private $auto_bind_return;
}
?>

View File

@@ -0,0 +1,109 @@
<?php
/**
* $Id: BindThrows.php 57067 2014-12-15 05:39:13Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs;
use phprs\util\Verify;
/**
*
* @author caoym
*
*/
class BindThrows
{
/**
* @param string $class_name
* @param string $method_name
*/
public function __construct($class_name, $method_name){
$this->class_name = $class_name;
$this->method_name = $method_name;
}
/**
* 设置绑定
* @param $params 绑定相关参数:[目标变量 , 源变量]
* @param $method_info 方法变量信息 [变量名=>[是否引用, 是否有默认值, 默认值]]
*/
public function set($id, $params, $method_info){
Verify::isTrue(is_array($params) && count($params) >=2, "{$this->class_name}::{$this->method_name} invalid @throws");
$exception = $params[0];
$to = $params[1];
$from = array_slice($params, 2);
$to_func = &$this->params[$exception][$to][$id];
foreach ($from as $index=>$name){ // 输入方法 $index变量序号 $name变量名
if(is_array($name)){
$is_const = true;
}else{
$is_const = (substr($name, 0, 1) !='$');
}
Verify::isTrue($is_const, "{$this->class_name}::{$this->method_name} dynamic variable not supported by @throws");
$to_func[$index] = $name;
}
}
/**
* 绑定到函数调用的参数上去
* @param $req
* @param $res
* @param $e 异常值
*/
public function bind($req, &$res, $e)
{
if(!isset($this->params['body']) && !isset($this->params['res'])){
$res['body'][0][0] = $e;
}
//先处理完全匹配的
$matched = false;
$funcs = array(); //输出方法
foreach ($this->params as $exce_name => $calls){
if(get_class($e) == $exce_name){
$funcs[] = $calls;
}
}
//没有完全匹配的异常, 尝试匹配父类
if(count($funcs)===0){
foreach ($this->params as $exce_name => $calls){
if(is_a($e, $exce_name) ){
$funcs[] = $calls;
}
}
}
// [
// 'body'=>[
// [0=>[arg1, arg2, arg3]],
// ]
// ]
foreach ($funcs as $id=>$calls) {
foreach ($calls as $fun_name => $call) {
foreach ($call as $arg) {
$res[$fun_name][$id] = $arg;
}
}
}
if(count($funcs) ===0 ){
throw $e;
}
}
/**
* @return boolean
*/
public function isEmpty(){
return count($this->params) ===0;
}
/**
* 返回绑定的变量信息
* @return array
* [异常名=>[输出方法]=>[输出参数位置=>[值 , 参数信息],..]]
*/
public function getParams(){
return $this->params;
}
private $params= array();// [异常=>[目标]=>[位置=>[值 , 参数信息],..]]
private $class_name;
private $method_name;
}

View File

@@ -0,0 +1,54 @@
<?php
namespace phprs;
use phprs\util\IoCFactory;
use phprs\util\exceptions\NotFound;
use phprs\util\exceptions\BadRequest;
use phprs\util\exceptions\Forbidden;
class Bootstrap
{
static public function run($conf_file)
{
$err = null;
$protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
//执行请求
try
{
require_once __DIR__ . '/AutoLoad.php';
$factory = new IoCFactory($conf_file);
$router = $factory->create('phprs\\RouterWithCache');
//var_dump($router);
$router();
}
catch (NotFound $e)
{
header($protocol . ' 404 Not Found');
$err = $e;
}
catch (BadRequest $e)
{
header($protocol . ' 400 Bad Request');
$err = $e;
}
catch (Forbidden $e)
{
header($protocol . ' 403 Forbidden');
$err = $e;
}
catch (\Exception $e)
{
header($protocol . ' 500 Internal Server Error');
$err = $e;
}
if ($err)
{
header("Content-Type: application/json; charset=UTF-8");
$estr = array(
'error' => get_class($err),
'message' => $err->getMessage(),
);
echo json_encode($estr, JSON_UNESCAPED_UNICODE);
}
}
}

View File

@@ -0,0 +1,153 @@
<?php
/**
* $Id: Container.php 58155 2015-01-05 14:45:30Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief RestfulApiContainer
*/
namespace phprs;
use phprs\util\AnnotationReader;
use phprs\util\Verify;
use phprs\util\Logger;
/**
* restful api 容器
* @author caoym
*
*/
class Container{
/**
* @param string $class 类名
* @param string $method 方法名, 如果为空, 则加载此类的所有方法
*/
function __construct($class, $method = null){
$this->load($class, $method);
}
/**
*
* @param string $class 类名
* @param string $method ==null时load所有方法, !==null时load指定方法
*/
public function load($class, $method){
$this->class = $class;
//获取方法
$reflection = new \ReflectionClass($class);
$reader= new AnnotationReader($reflection);
$class_ann = $reader->getClassAnnotations($reflection);
Verify::isTrue(isset($class_ann['path']), $class.' @path not found');
Verify::isTrue(count($class_ann['path'])===1, $class.' @path ambiguity');
$path = $class_ann['path'][0]['value'];
$this->path = $path;
$specified = $method;
foreach ($reflection->getMethods() as $method){
if($specified !== null && $specified !== $method->getName()){
Logger::DEBUG("specified method: $specified, ignore $class::{$method->getName()}");
continue;
}
$anns = $reader->getMethodAnnotations($method, false);
if(!isset($anns['route'])){
Logger::DEBUG("no @route, ignore $class::{$method->getName()}");
continue;
}
//Verify::isTrue(count($anns['route']) == 1, "$class::{$method->getName()} @route repeated set");
$invoker = $this->factory->create('phprs\Invoker', array($this, $method) );
foreach ($anns['route'] as $ann){
$route = $ann['value'];
Verify::isTrue(is_array($route) && (count($route)==2 || count($route)==3),
"$class::{$method->getName()} syntax error @route, example: @route({\"GET\" ,\"/api?a=2\"}) or @route({\"GET\" ,\"/api?a=2\",true})"
);
list($http_method, $uri,$strict) = $route+[null,null,null];
$this->routes[$http_method][] = [$path.'/'.$uri, $invoker,$strict];
}
foreach ($anns as $type =>$v){
if($type == 'route'){
continue;
}
$id = 0;
foreach ($v as $ann){
if(!is_array($ann) || !isset($ann['value'])) {
continue;
}
$invoker->bind($id++, $type, $ann['value']);
continue;
}
}
//检查是否所有必须的参数均已绑定
$invoker->check();
}
//属性注入
/*foreach ($reflection->getProperties() as $property ){
foreach ( $reader->getPropertyAnnotations($property) as $id => $ann){
if($id !== 'inject') { continue;}
$name = $property->getName();
if($name == "ioc_factory"){// ioc_factory由工厂负责注入
//TODO: 用@ioc_factory替代ioc_factory
continue;
}
Verify::isTrue(count($ann) ===1, "$class::$name ambiguity @inject");
Verify::isTrue(isset($ann[0]['value']), "$class::$name invalid @inject");
Verify::isTrue(is_string($ann[0]['value']), "$class::$name invalid @inject");
$this->injectors[] = new Injector($this, $name, $ann[0]['value']);
}
}*/
}
/**
* 获取API实现类的实例
* @param Request $request
* @return object
*/
public function getImpl($request){
Verify::isTrue($request !== null);
if($this->impl === null){
$injected = &$this->injected;
$injected = array();
$this->impl = $this->factory->create($this->class, null, null, function($src, &$succeeded)use($request, &$injected){
list($val, $found) = $request->find($src);
$succeeded = $found;
$injected[$src]=$val;
return $val;
});
asort($injected);
}
return $this->impl;
}
/**
* 获取实例被注入的方法
* 只有实例被创建后才能取到值
* @return array
*/
public function getInjected(){
return $this->injected;
}
///**
// * 从http请求中提取属性
// * @param RestfulApiRequest $request http请求
// */
//public function inject($request){
// foreach ($this->injectors as $inject){
// $inject($request);
// }
//}
///**
// * 获取注入的依赖
// * @return arra:
// */
//public function getInjectors(){
// return $this->injectors;
//}
//每次处理请求时属性被重新注入到API实例
//private $injectors=array();
public $routes=array(); //['GET'=>[Invoker,Invoker,Invoker...],'POST',....];
//API 实现类;
public $class;
//API 实例;
private $impl;
private $injected;// 被注入的属性, 记录下来, 可以作为缓存key的一部分
/** @inject("ioc_factory") */
public $factory;
public $path;
}

View File

@@ -0,0 +1,251 @@
<?php
/**
* $Id: Invoker.php 63816 2015-05-15 11:35:31Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs;
use phprs\util\Verify;
use phprs\util\Logger;
use phprs\util\exceptions\BadRequest;
use phprs\util\CheckableCache;
/**
* api调用包装
* 从请求中提取api所需的参数, 调用API, 并将结果输出到Response对象
*
* @author caoym
*
*/
class Invoker
{
/**
* @param Container $ins
* 被调用的实例容器
* @param string $method
* 被调用的实例方法
*/
public function __construct($ins, $method)
{
$this->ins = $ins;
if($this->cache === null){
$this->checkAbleCache = $this->factory->create('phprs\util\Cache');
}else{
$this->checkAbleCache = new CheckableCache($this->cache);
}
$this->method_name = $method->getName();
foreach ($method->getParameters() as $param) {
$this->method_args[] = array(
$param->getName(), // name
$param->isPassedByReference(), // ref
$param->isOptional(), // isOptional
$param->isOptional() ? $param->getDefaultValue() : null,
) // default
;
}
$this->bind = array(
'param' => new BindParams($this->ins->class, $this->method_name),
'return' => new BindReturns($this->ins->class, $this->method_name),
'throws' => new BindThrows($this->ins->class, $this->method_name),
'cache' => new BindReturns($this->ins->class, $this->method_name, false),
);
}
/**
* 绑定参数,返回值,异常
* @param int $id
* 参数id
* @param string $type 绑定类型 param/return/throws
* @param $param
* param:
* [['arg_name'=>'$._SERVER.REQUEST_URI'],...]
* [[参数名=>jsonpath表达式],...]
* 指定api接口参数从http请求中获取的方式
* returns:
* 指定哪些参数被作为返回值
* [
* ['status','200 OK'], 200 OK 作为常量, 输出到status
* ['cookie','$arg0'], $arg0变量输出到cookie
* ['body'], 未指定变量, 则取返回值输出
* ]
* throws 指定根据不同的异常设置响应信息
* [
* ['MyException','status' , '404 Not Found'],
* ['MyException','body','$msg'],
* ['Exception','status','500 Internal Server Error'],
* ]
*/
public function bind($id, $type, $param)
{
if (! isset($this->bind[$type])) {
return;
}
$this->bind[$type]->set($id, $param, $this->method_args);
}
/**
* 执行API
*
* @param Request $request 请求
* @param Response $response 响应
*/
public function __invoke($request, &$response)
{
$args = array();
//绑定参数和返回值
$this->bind['param']->bind($request, $response, $args);
$this->bind['return']->bind($request, $response, $args);
//利用参数绑定的能力,提取@cache注释的信息
$cache_ttl = 0;
$cache_check = null;
$cache_res = new Response(array(
'ttl' => function ($param) use(&$cache_ttl)
{
$cache_ttl = $param;
},
'check' => function ($param) use(&$cache_check)
{
$cache_check = $param;
},
'body'=>function ($_=null){}
));
$this->bind['cache']->bind($request, $cache_res, $args);
$use_cache = !$this->bind['cache']->isEmpty();
$given = count($args);
if ($given === 0) {
$required_num = 0;
} else {
ksort($args, SORT_NUMERIC);
end($args);
$required_num = key($args) + 1;
}
Verify::isTrue($given === $required_num, new BadRequest("{$this->ins->class}::{$this->method_name} $required_num params required, $given given")); // 变量没给全
$cache_key = null;
if ($use_cache) {
//输入参数包括函数参数和类注入数据两部分
//所以以这些参数的摘要作为缓存的key
$injected = $this->ins->getInjected();
$cache_res->flush();//取出cache参数
$cache_key = "invoke_{$this->ins->class}_{$this->method_name}_" . sha1(serialize($args).serialize($injected).$cache_ttl);
$succeeded = false;
$data = $this->checkAbleCache->get($cache_key, $succeeded);
if ($succeeded && is_array($data)) {
$response->setBuffer($data);
$response->flush();
Logger::info("{$this->ins->class}::{$this->method_name} get response from cache $cache_key");
return;
}
}
$impl = $this->ins->getImpl($request);
//
if (!$this->bind['throws']->isEmpty()) {
try {
$res = call_user_func_array(array(
$impl,
$this->method_name,
), $args);
} catch (\Exception $e) {
$response->clear(); // 清除之前绑定的变量, 异常发生时可能已经写入了一些参数
$this->bind['throws']->bind($request, $response, $e);
$response['break'][][0]=true;
$response->flush();
return;
}
} else {
$res = call_user_func_array(array(
$impl,
$this->method_name,
), $args);
}
$this->bind['return']->setReturn($res);
if ($use_cache) {
$this->checkAbleCache->set($cache_key, $response->getBuffer(), $cache_ttl, $cache_check);
Logger::info("{$this->ins->class}::{$this->method_name} set response to cache $cache_key, ttl=$cache_ttl, check=".($cache_check===null?'null':get_class($cache_check)));
}
$response->flush();
}
/**
* 获取被绑定的参数列表
* 返回的是参数位置
*
* @return array
*/
public function getBindParamPos()
{
return array_merge($this->bind['return']->getBindParamPos(), $this->bind['param']->getBindParamPos(), $this->bind['cache']->getBindParamPos());
}
/**
* 检查参数是否均已经绑定
*/
public function check()
{
$params = $this->getBindParamPos();
foreach ($this->method_args as $id => $arg) {
list ($name, $is_ref, $is_optional, $default) = $arg;
if (false === array_search($id, $params)) {
Verify::isTrue($is_optional, "{$this->ins->class}::{$this->method_name} param: $name not be bound");
}
}
}
/**
* @return string
*/
public function getMethodName(){
return $this->method_name;
}
/**
* 绑定的参数
*/
public function getParams(){
return $this->bind['param'];
}
/**
* 绑定的返回值
*/
public function getReturns(){
return $this->bind['return'];
}
/**
* 绑定的异常
*/
public function getThrows(){
return $this->bind['throws'];
}
/**
* @return string
*/
public function getClassName(){
return $this->ins->class;
}
/**
* @return object
*/
public function getContainer(){
return $this->ins;
}
public $method_name;
private $method_args = array();
private $ins;
// 绑定的变量
private $bind = array();
/**
* @property
* @var phprs\util\KVCatchInterface
*/
private $cache=null;
private $checkAbleCache;
/** @inject("ioc_factory") */
private $factory;
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* $Id: Request.php 63900 2015-05-19 07:09:58Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief Request
*/
namespace phprs;
use phprs\util\Verify;
use Peekmo\JsonPath\JsonStore;
use phprs\util\exceptions\BadRequest;
/**
* @author caoym
* http请求包装
* 允许使用jsonpath表达式获取http请求信息
* 如
* req['$._GET.param1']
*
* TODO: 使用更友好的变量名替代直接用php的全局变量, 如$.uri.query 替代$._GET, $.body 替代$._POST
*/
class Request implements \ArrayAccess
{
//TODO: 在API实现中直接使用$_GET等全局变量是不推荐的, 应该给予警告提醒
/**
*
* @param array $data
*/
function __construct($data=null,$url_begin=0){
if($data === null){
$data = $GLOBALS;
$data['header'] = $this->getAllHeaders();
}
$this->url_begin = $url_begin;
//支持json请求(Content-Type: application/json)
$contentType = null;
if(isset($data['header']['Content-Type'])){
$contentType = $data['header']['Content-Type'];
list($contentType, ) = explode(';', $contentType)+array(null,null);
if($contentType == 'application/json'){
$post = file_get_contents('php://input');
if($post != ''){
$post = json_decode($post, true);
Verify::isTrue(is_array($post), new BadRequest('post unjson data with application/json type'));
$data['_POST'] = $post;
if(!isset($data['_REQUEST'])){
$data['_REQUEST'] = [];
}
$data['_REQUEST'] = array_merge($data['_POST'], $data['_REQUEST'] );
}
}
elseif($contentType == 'text/plain'){
$data['_POST'] = file_get_contents('php://input');
}
}
//TODO: 支持put请求
if(isset($data['_SERVER']['REQUEST_METHOD']) && 'PUT' == $data['_SERVER']['REQUEST_METHOD']){
if($contentType == 'application/x-www-form-urlencoded'){
$queryString = file_get_contents('php://input');
$query = array();
parse_str($queryString, $query);
$data['_POST'] = $query;
}
}
$full_path = $data['_SERVER']['REQUEST_URI'];
Verify::isTrue($full_path, '$._SERVER.REQUEST_URI not found' );
list($full_path,) = explode('?', $full_path);
$paths = explode('/', $full_path);
$paths = array_filter($paths,function ($i){return $i !== '';});
$paths = array_slice($paths, $this->url_begin);
$data['path'] = $paths;
$this->data = new JsonStore($data);
}
/**
* 获取http请求的所有header信息
* @return array
*/
function getAllHeaders() {
$headers = array();
foreach ($_SERVER as $name => $value)
{
if (substr($name, 0, 5) == 'HTTP_')
{
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
$headers[$name] = $value;
} else if ($name == "CONTENT_TYPE") {
$headers["Content-Type"] = $value;
} else if ($name == "CONTENT_LENGTH") {
$headers["Content-Length"] = $value;
}
}
return $headers;
}
/**
* (non-PHPdoc)
* @see ArrayAccess::offsetExists()
*/
public function offsetExists($offset){
return count($this->data->get($offset)) !==0;
}
/**
* (non-PHPdoc)
* @see ArrayAccess::offsetGet()
*/
public function offsetGet($offset)
{
$res = $this->data->get($offset);
if(!isset($res[0])){
trigger_error("$offset not exist");
}
return $res[0];
}
/**
* @param string $expr jsonpath 表达式
* @param $create 是否找不到就创建
* @return [found, is succeed]
*/
public function find($expr, $create=false){
$res= $this->data->get($expr, false, $create);
if(count($res) ===0 ){
return array(null,false);
}else if(count($res) ===1){
return array(&$res[0], true);
}else{
return array(&$res, true);
}
}
/**
* (non-PHPdoc)
* @see ArrayAccess::offsetSet()
*/
public function offsetSet($offset, $value)
{
Verify::isTrue($this->data->set($offset, $value));
}
/**
* (non-PHPdoc)
* @see ArrayAccess::offsetUnset()
*/
public function offsetUnset($offset)
{
Verify::isTrue(false, 'NOT IMPL');
}
/**
* 取数包含所有请求信息的数组
* @return multitype:
*/
public function toArray(){
return $this->data->toArray();
}
private $data;
private $url_begin;
}

View File

@@ -0,0 +1,188 @@
<?php
/**
* $Id: Response.php 58820 2015-01-16 16:29:33Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
* @brief Request
*/
namespace phprs;
use phprs\util\Verify;
/**
* http响应包装
* 保存http响应, 并通过sender写出
* 并可以数组的方式设置数据
* 如
* $res['status'][]='200 OK'
* $res['body'][]='...'
*
* @author caoym
*/
class Response implements \ArrayAccess
{
/**
* 创建响应
* @param array $sender 数据发送方法
*/
function __construct($sender = null)
{
if ($sender !== null) {
$this->sender = $sender;
}
if ($this->sender === null) {
// TODO 严格检查方法的参数数量
// 输出时按照下面数组中的顺序输出
$this->sender = array(
'header' => function ($_=null)
{
call_user_func_array('header', func_get_args());
},
'status' => function ($var, $replace = true)
{
header($_SERVER["SERVER_PROTOCOL"] . ' '.$var, $replace);
},
'cookie' =>function ($name, $value, $expire=null, $path='/', $domain=null, $secure=null){
if(is_string($expire)){
$expire = strtotime($expire);
}
setcookie($name, $value, $expire, $path, $domain, $secure);
},
'body' => function ($var)
{
if (is_array($var) || is_object($var)) {
header("Content-Type: application/json; charset=UTF-8");
echo json_encode($var); // TODO 自定义适配方法
} else {
echo $var;
}
},
'break'=>function($_=null){
//do nothing
},
'res'=>function ($status, $body){
header($_SERVER["SERVER_PROTOCOL"] . ' '.$status, true);
if (is_array($body)) {
header("Content-Type: application/json; charset=UTF-8");
echo json_encode($body); // TODO 自定义适配方法
} else {
echo $body;
}
}
);
}
}
/**
* (non-PHPdoc)
*
* @see ArrayAccess::offsetExists()
*/
public function offsetExists($offset)
{
return isset($this->sender[$offset]);
}
/**
* 通过[]操作符设置输出数据
*/
public function &offsetGet($offset)
{
Verify::isTrue($this->offsetExists($offset), 'unsupported response ' . $offset);
if (! isset($this->buffer[$offset])) {
$this->buffer[$offset] = array();
}
return $this->buffer[$offset];
}
/**
* (non-PHPdoc)
*
* @see ArrayAccess::offsetSet()
*/
public function offsetSet($offset, $value)
{
Verify::isTrue(false, 'NOT IMPL');
}
/**
* (non-PHPdoc)
*
* @see ArrayAccess::offsetUnset()
*/
public function offsetUnset($offset)
{
Verify::isTrue(false, 'NOT IMPL');
}
/**
* 清除缓存
* @return void
*/
public function clear(){
$this->buffer = array();
}
/**
* 想缓存写出
* @param $limit 取指定的项目
* @param $func 取出后调用的方法
* @return array:
*/
public function flush($limit=null, $func=null)
{
foreach ($this->sender as $name=>$sender){
if (!isset($this->buffer[$name])){
continue;
}
if($limit !==null ){
if($limit !== $name){
continue;
}
if($func!==null){
$sender = $func;
}
}
$funcs = $this->buffer[$name];
foreach ($funcs as $args) {
// 确保所有参数均已设置
ksort($args, SORT_NUMERIC);
$i = 0;
foreach ($args as $k => $v) {
Verify::isTrue($k === $i ++, "the no.$i arg from $name not exist");
}
call_user_func_array($sender, $args);
}
if($limit !==null){
break;
}
}
}
/**
* 附加更多数据
* @param array $buffer
*/
public function append($buffer){
foreach ($buffer as $name => $funcs) {
foreach ($funcs as $func){
$this->buffer[$name][]=$func;
}
}
}
/**
*
* @return multitype:
*/
public function getBuffer(){
return $this->buffer;
}
/**
* @param unknown $buffer
*/
public function setBuffer($buffer){
$this->buffer = $buffer;
}
private $buffer = array();
private $sender;
}

View File

@@ -0,0 +1,346 @@
<?php
/**
* $Id: Router.php 58820 2015-01-16 16:29:33Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief Router
*/
namespace phprs;
use phprs\util\HttpRouterEntries;
use phprs\util\Verify;
use phprs\util\AutoClassLoader;
use phprs\util\Logger;
use phprs\util\exceptions\NotFound;
use phprs\util\ClassLoader;
/**
* 结果保留在内存
* @author caoym
*/
class BufferedRespond extends Response{
/**
* flush 不直接输出
* @see \phprs\Response::flush()
* @param string limit 现在输出的项目
* @param callable $func 输出方法
* @return void
*/
public function flush($limit=null, $func=null)
{
return ;
}
}
/**
* restful api 路由
* 加载API类文件, 通过API类的@annotation获取路由规则信息, 当请求到来时, 调用匹配的
* API类方法.
*
* 通常每一个请求只对应到一个最严格匹配的API接口, 所谓"最严格匹配",比如:
* API1 接口 => url: /apis/test
* API2 接口 => url: /apis
* 那么当请求"/apis/test/123/" 最严格匹配的接口是API1
*
* 如果需要让一个请求经过多个API调用, 比如有时候会需要一个统一验证的接口, 让所有请
* 求先通过验证接口, 再调用其他接口. 此时可以通过Router的hooks属性, 设置一组hook实
* 现. hook其实和普通的接口一样, 只是在hooks中指定后, 执行行为将有所不同: 请求会按
* 优先级逐个经过hook, 只要匹配, hook的方法就会被调用, 直到最后调用普通的API
*
* 通过@return({"break", true})停止请求链路
*
* @author caoym
*/
class Router
{
private $class_loader; //用于确保反序列化时自动加载类文件
/**
*
*/
function __construct( ){
//AutoClassLoader确保序列化后自动加载类文件
$this->class_loader = new AutoClassLoader();
ClassLoader::addInclude($this->api_path);
//TODO: 支持名字空间
//TODO: 支持多路径多类名
$this->load($this->api_path, $this->apis, $this->api_method);
//允许通过接口访问api信息
if($this->export_apis){
$this->loadApi($this->routes, __DIR__.'/apis/ApiExporter.php', 'phprs\apis\ApiExporter');
}
}
/**
* 获取api文件列表
* @return array
*/
public function getApiFiles(){
return $this->class_loader->getClasses();
}
/**
* 调用路由规则匹配的api
* @param Request $request
* @param Response $respond
* @return void
*/
public function __invoke($request=null, &$respond=null){
if($request === null){
$request = new Request(null,$this->url_begin);
}
if($respond==null){
$respond = new Response();
}
$request['$.router'] = $this;
//先按配置的顺序调用hook
foreach ($this->hook_routes as $hook){
$res = new BufferedRespond();
if(!$this->invokeRoute($hook, $request, $res)){
continue;
}
$respond->append($res->getBuffer());
$break = false;
$respond->flush('break', function($var)use(&$break){$break = $var;});
if($break){
Logger::info("invoke break");
$respond->flush();
return;
}
}
$res = new BufferedRespond();
Verify::isTrue($this->invokeRoute($this->routes, $request, $res), new NotFound());
$respond->append($res->getBuffer());
$respond->flush();
}
/**
*
* @param string|array $api_path
* @param string $apis
* @param string $api_method
*/
public function load($api_path, $apis=null , $api_method=null){
if(is_string($api_path)){
$api_paths = array($api_path);
}else{
$api_paths = $api_path;
}
Verify::isTrue(is_array($api_paths), 'invalid param');
foreach ($api_paths as $api_path){
$this->loadRoutes($this->routes, $api_path, $apis, $api_method);
foreach ($this->hooks as $hook) {
$hook_route=array();
$this->loadRoutes($hook_route, $api_path.'/hooks', $hook, null);
$this->hook_routes[] = $hook_route;
}
}
}
/**
* @return array
*/
public function getRoutes(){
return $this->routes;
}
/**
* @return array
*/
public function getHooks(){
return $this->hook_routes;
}
/**
* 调用路由规则匹配的api
* @param array $routes 路由规则
* @param unknown $request
* @param unknown $respond
* @return boolean 是否有匹配的接口被调用
*/
private function invokeRoute($routes, $request, &$respond){
$method = $request['$._SERVER.REQUEST_METHOD'];
$path = $request['$.path'];
$uri = $request['$._SERVER.REQUEST_URI'];
list(,$params) = explode('?', $uri)+array( null,null );
$params = is_null($params)?null:explode('&', $params);
Logger::debug("try to find route $method ".$uri);
$match_path = array();
if(isset($routes[$method])){
if(($api = $routes[$method]->findByArray($path,$params,$match_path)) !== null){
Logger::debug("invoke $uri => {$api->getClassName()}::{$api->getMethodName()}");
$api($request, $respond);
return true;
}
}
if(!isset($routes['*'])){
return false;
}
if(($api = $routes['*']->find($uri, $match_path)) === null){
return false;
}
Logger::debug("invoke $uri => {$api->getClassName()}::{$api->getMethodName()}");
$api($request, $respond);
return true;
}
/**
* @param string $apis_dir
* @param string $class
* @param string $method
* @return void
*/
public function addRoutes($apis_dir, $class=null, $method=null){
$this->loadRoutes($this->routes, $apis_dir, $class, $method);
}
/**
* 遍历API目录生成路由规则
* @param object $factory
* @param string $apis_dir
* @param string $class
* @param string $method
* @param array $skipclass 需要跳过的类名
* @return Router
*/
private function loadRoutes(&$routes, $apis_dir, $class, $method){
Logger::info("attempt to load router $apis_dir");
$dir = null;
if(is_dir($apis_dir) && $class === null){
$apis_dir=$apis_dir.'/';
Verify::isTrue(is_dir($apis_dir), "$apis_dir not a dir");
$dir = @dir($apis_dir);
Verify::isTrue($dir !== null, "open dir $apis_dir failed");
$geteach = function ()use($dir){
$name = $dir->read();
if(!$name){
return $name;
}
return $name;
};
}else{
if(is_file($apis_dir)){
$files = array($apis_dir);
$apis_dir = '';
}else{
$apis_dir=$apis_dir.'/';
if(is_array($class)){
foreach ($class as &$v){
$v .= '.php';
}
$files = $class;
}else{
$files = array($class.'.php');
}
}
$geteach = function ()use(&$files){
// 🚨 PHP8兼容性each()函数替换,严格保持原有行为
// ⚠️ 重要必须保持数组指针移动行为与each()完全一致
// ⚠️ 重要:必须保持文件加载顺序绝对不变
$key = key($files);
if($key === null) {
return false; // ⚠️ 保持与each()相同的结束判断逻辑
}
$value = current($files);
next($files); // ⚠️ 关键保持指针移动与each()完全一致
return $value; // ⚠️ 保持返回值格式与each()[1]完全一致
};
}
while( !!($entry = $geteach()) ){
$path = $apis_dir. str_replace('\\', '/', $entry);
if(is_file($path) && substr_compare ($entry, '.php', strlen($entry)-4,4,true) ==0){
$class_name = substr($entry, 0, strlen($entry)-4);
$this->loadApi($routes, $path, $class_name, $method);
}else{
Logger::debug($path.' ignored');
}
}
if($dir !== null){
$dir->close();
}
Logger::info("load router $apis_dir ok");
return $routes;
}
/**
* 加载api类
* @param array $routes
* @param string $class_file
* @param string $class_name
* @param string $method
* @return void
*/
private function loadApi(&$routes, $class_file, $class_name, $method=null){
Verify::isTrue(is_file($class_file), $class_file.' is not an exist file');
Logger::debug("attempt to load api: $class_name, $class_file");
$this->class_loader->addClass($class_name, $class_file);
$api = null;
if ($this->ignore_load_error){
try {
$api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null);
} catch (\Exception $e) {
Logger::warning("load api: $class_name, $class_file failed with ".$e->getMessage());
return ;
}
}else{
$api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null);
}
foreach ($api->routes as $http_method=>$route){
if(!isset($routes[$http_method])){
$routes[$http_method] = new HttpRouterEntries();
}
$cur = $routes[$http_method];
foreach ($route as $entry){
list($uri,$invoke,$strict) = $entry;
$realpath = preg_replace('/\/+/', '/', '/'.$uri);
$strict = ($strict===null)?$this->default_strict_matching:$strict;
Verify::isTrue($cur->insert($realpath, $invoke, $strict), "repeated path $realpath");
Logger::debug("api: $http_method $realpath => $class_name::{$entry[1]->method_name} ok, strict:$strict");
}
}
Logger::debug("load api: $class_name, $class_file ok");
}
private $routes=array();
private $hook_routes=array();
/** @inject("ioc_factory") */
public $factory;
/** @property */
private $api_path='apis';
/** @property */
private $apis=null;
/** @property */
private $api_method=null;
private $api_root=null;
/** @property
* 指定hook的类名, 从优先级高到低
* 如果一条规则匹配多个hook, 则优先级高的先执行, 再执行优先级低的
*/
private $hooks=array();
/**
* @property
* 是否允许通过接口获取api信息
*/
private $export_apis=true;
/**
* 用于匹配路由的url偏移
* @property
*/
public $url_begin=0;
/**
* 指定路由规则默认情况下是否严格匹配path如果@route中已经指定严格模式则忽略此默认设置
* 严格模式将只允许同级目录匹配,否则父目录和子目录也匹配。
* 非严格匹配时
* 路由"GET /a" 和请求"GET /a"、"GET /a/b"、"GET /a/b/c"等匹配
* 严格匹配时
* 路由"GET /a" 和请求"GET /a"匹配、和"GET /a/b"、"GET /a/b/c"等不匹配
* @property
*/
public $default_strict_matching=false;
/**
* @property 忽略类加载时的错误,只是跳过出错的接口。否则抛出异常。
*/
public $ignore_load_error=true;
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* $Id: RouterWithCache.php 57516 2014-12-23 05:44:20Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief RouterCache
*/
namespace phprs;
use phprs\util\FileExpiredChecker;
use phprs\util\Logger;
/**
* 支持缓存的Router
* 初始化Router需要解析类和方法的注释, 通过@标记绑定接口的参数, 此过程非常耗时, 缓
* 存可以将解析后的结果保留, 包括api容器, 调用参数的绑定顺序等, 避免此消耗.
*
* 由于Router初始化时并不会创建API实例, 而是在API被调用时才创建, 所以缓存不会保存
* API实例, 此特性有助于简化API的设计, API会在每个请求中重新初始化
* @author caoym
*/
class RouterWithCache
{
/**
* @return void
*/
function __construct(){
$ok=false;
if($this->factory->getConfFile() ===null){
$key = 'phprs_route3_'.sha1(serialize($this->factory->getConf()));
}else{
$key = 'phprs_route3_'.sha1($this->factory->getConfFile());
}
$this->impl = $this->cache->get($key, $ok);
if($ok && is_object($this->impl)){
Logger::info("router loaded from cache");
return ;
}
$this->impl = $this->factory->create('phprs\\Router');
//缓存过期判断依据
//检查接口文件是否有修改\新增
$check_files = array_values($this->impl->getApiFiles());
$check_dirs=array();
foreach($check_files as $file){
if(is_file($file)){
$check_dirs[] = dirname($file);
}
}
$check_files = array_merge($check_files, $check_dirs);
$check_files[]=$this->factory->getConfFile();
$this->cache->set($key, $this->impl, 0, new FileExpiredChecker($check_files)); //接口文件或者配置文件修改
}
/**
* 调用路由规则匹配的api
* @param Request $request
* @param Response $respond
* @return mixed
*/
function __invoke($request=null, &$respond=null){
return $this->impl->__invoke($request, $respond);
}
/** @property({"default":"@phprs\util\Cache"}) */
private $cache;
/** @inject("ioc_factory") */
private $factory;
private $impl;
}

View File

@@ -0,0 +1,799 @@
<?php
/**
* $Id: ApiExporter.php 58155 2015-01-05 14:45:30Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
* ApiExporter
*/
namespace phprs\apis;
use phprs\Invoker;
use phprs\util\AnnotationReader;
use Peekmo\JsonPath\JsonStore;
use phprs\util\HttpRouterEntries;
use phprs\util\Verify;
/**
* 导出AP描述信息
* @author caoym
* @path("/")
*/
class ApiExporter
{
/**
* 导出API信息
* @route({"GET", "/apis.json"})
* @return({"header", "Content-Type: application/json; charset=UTF-8"})
* @return({"body"}) array
*/
public function exportJson()
{
$apis = array();
// 导出hook信息
foreach ($this->router->getHooks() as $hooks) {
foreach ($hooks as $method => $hook) {
$entries = $hook->export();
foreach ($entries as $entry) {
list ($uri, $invoker) = $entry;
$info['type'] = 'hook';
$info['uri'] = array(
$method,
$uri
);
$info = array_merge($info, $this->getInvokerInfo($method,$uri, $invoker));
$apis[$invoker->getClassName()]['apis'][] = $info;
}
}
}
// 导出api信息
foreach ($this->router->getRoutes() as $method => $route) {
$entries = $route->export();
foreach ($entries as $entry) {
list ($uri, $invoker) = $entry;
$info['type'] = 'api';
$info['uri'] = array(
$method,
$uri,
);
$info = array_merge($info, $this->getInvokerInfo($method,$uri,$invoker));
$apis[$invoker->getClassName()]['apis'][] = $info;
}
}
foreach ($apis as $class_name => &$info) {
$ann = new \ReflectionClass($class_name);
$apis[$class_name]['doc'] = $this->getDocText($ann->getDocComment());
//排序, 便于阅读
usort($info['apis'], function($lh, $rh){
return strcmp($rh['uri'][1].$rh['uri'][0], $lh['uri'][1].$lh['uri'][0] );
});
}
return $apis;
}
private static $css = <<<EOT
<style type="text/css">
pre {border: 1px solid #bbb; padding: 10px;}
</style>
EOT;
/**
* 导出API信息
* 简陋的html形式
*
* @param Router $router
* @route({"GET","/apis/",true})
* @return ({"header", "Content-Type: text/html; charset=UTF-8"})
*/
public function exportMainHtml()
{
$info = $this->exportJson();
$body = '<html>';
$body .= '<body>';
$body = '<ol>';
foreach ($info as $class_name => $apis) {
$body .= "<li><a href=./" . str_replace('\\', '/', $class_name) . ">$class_name</a></li>";
$body .= '<p>';
$body .= nl2br(htmlentities($apis['doc']));
$body .= '</p>';
}
$body .= '</ol>';
$body .= '</body>';
$body .= '</html>';
return $body;
}
/**
* 导出API信息
* 简陋的html形式
*
* @route({"GET", "/apis/*"})
*
* @param({"class_name", "$.path[1:]"})
* @return ({"header", "Content-Type: text/html; charset=UTF-8"})
*/
public function exportApiHtml($class_name)
{
if (is_array($class_name)) {
$class_name = implode('\\', $class_name);
}
// TODO: html+js重写吧
$body = self::$css;
$body .= '<html>';
$body .= '<body>';
$info = $this->exportJson();
$apis = $info[$class_name];
// 类名
$body .= '<h1>';
$body .= htmlentities($class_name);
$body .= '</h1>';
$body .= '<p>';
$body .= nl2br(htmlentities($apis['doc']));
$body .= '</p>';
$body .= '<ol>';
// 接口
foreach ($apis['apis'] as $api) {
$body .= '<h2>';
$body .= '<li>';
$body .= htmlentities($api['uri'][0] . ' ' . $api['uri'][1]);
$body .= '</li>';
$body .= '</h2>';
// 说明
$body .= '<p>';
$body .= nl2br(htmlentities($api['doc']));
$body .= '</p>';
// 请求
list ($sample, $doc) = $this->createRequestDoc($api);
$body .= '<h3>>>Request</h3>';
$body .= '<pre>';
$body .= $sample;
$body .= '</pre>';
$body .= '<p>';
$body .= nl2br(htmlentities($doc));
$body .= '</p>';
// 响应
list ($sample, $doc) = $this->createResponseDoc($api, $api['type'] ==='api');
$body .= '<h3>>>Response(OK)</h3>';
$body .= '<pre>';
$body .= $sample;
$body .= '</pre>';
$body .= '<p>';
$body .= nl2br(htmlentities($doc));
$body .= '</p>';
// 异常
$fails = $this->createExceptionDoc($api);
foreach ($fails as $name => $info) {
$body .= '<h3>>>Response (Fail: ' . $name . ')</h3>';
$body .= '<pre>';
$body .= $info[0];
$body .= '</pre>';
$body .= '<p>';
$body .= nl2br(htmlentities($info[1]));
$body .= '</p>';
}
$body .= '<h3>>>Response (Fail: unknown)</h3>';
$body .= '<pre>';
$body .= "HTTP/1.1 500 Internal Server Error\r\n\r\n";
$body .= '</pre>';
}
$body .= '</ol>';
$body .= '</body>';
$body .= '</html>';
return $body;
}
/**
* 生成请求的示例和说明
*
* @param array $api
* @return array [sample, doc]
*/
private function createRequestDoc($api)
{
//TODO: 需要处理特殊情况: 输入被绑定在多个参数, 或者输入的不同重叠区域被绑定到不同参数时
$docs = '';
// 提取参数
$params = new JsonStore(array());
foreach ($api['params'] as $name => $param) {
$ori = $params->get($param['value']);
if (count($ori) !== 0) { // 现在不支持同一个变量多个地方引用
continue;
}
$info = new \ArrayObject(array(
$name,
$param,
));
$params->set($param['value'], $info);
}
$params = $params->toArray();
// 路由中指定的路径
$route_path = HttpRouterEntries::stringToPath($api['uri'][1]); // 这是绝对路径
$path = $api['uri'][0];
// 路径拼到示例中
if (isset($params['path'])) {
$req_path = $params['path']; // 请求中使用的路径, 这是相对路径
$offest = count(HttpRouterEntries::stringToPath($api['root'])); // 相对于绝对路径的偏移
if (is_array($req_path)) { // 参数只是路径的一部分
if(count($req_path)>0){
$end = max(array_keys($req_path));
Verify::isTrue($end <128, "too long path with length $end");
for ($i = 0; $i <= $end; $i ++) {
if (isset($req_path[$i])) {
list ($arg_name, $arg_info) = $req_path[$i];
if(isset($route_path[$i + $offest]) && $route_path[$i + $offest] !=='*'){
//忽略固定的路径
}else{
$route_path[$i + $offest] = "[$arg_name]";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
}
} else {
if (! isset($route_path[$i + $offest])) {
$route_path[$i + $offest] = '*';
}
}
}
}
} else { // 参数整个路径
list ($arg_name, $arg_info) = $req_path;
$route_path[$offest] = "[$arg_name]";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
}
unset($params['path']);
}
$path .= ' /';
$path .= implode('/', $route_path);
// querystring
if (isset($params['_GET'])) {
$get = $params['_GET'];
if(is_array($get)){
$first = true;
foreach ($get as $name => $value) {
list ($arg_name, $arg_info) = $value;
if ($first) {
$path = $path . '?';
$first = false;
} else {
$path = $path . '&';
}
$path = "$path$name=[$arg_name]";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
}
}else{
// 参数整个_GET
list ($arg_name, $arg_info) = $get;
$path = "$path?[$arg_name]";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
}
unset($params['_GET']);
}
$path .= " HTTP/1.1\r\n";
// header
$header = '';
if (isset($params['header'])) {
$headers = $params['header'];
$first = true;
foreach ($headers as $header_name => $value) {
//if (substr_compare($name, 'HTTP_X_', 0, 7) !== 0) {
// continue;
//}
//$words = explode('_', substr($name, 7));
//$header_name = '';
//foreach ($words as $k => $word) {
// $words[$k] = ucwords(strtolower($word));
//}
//$header_name = implode('-', $words);
list ($arg_name, $arg_info) = $value;
$header = "$header$header_name: [$arg_name]\r\n";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
unset($params['_SERVER'][$name]);
}
}
// cookie
$header = '';
if (isset($params['_COOKIE'])) {
$cookies = $params['_COOKIE'];
$first = true;
$header = $header."Cookie: ";
foreach ($cookies as $cookie_name => $value) {
list ($arg_name, $arg_info) = $value;
$header = "$header$cookie_name=[$arg_name];";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
}
$header .= "\r\n";
}
// body
$body = '';
if (isset($params['_POST'])) {
$post = $params['_POST'];
$first = true;
if(is_array($post)){
foreach ($post as $name => $value) {
list ($arg_name, $arg_info) = $value;
if ($first) {
$first = false;
} else {
$body = $body . '&';
}
$body = "$body$name=[$arg_name]";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
}
}else{
// 参数整个_POST
list ($arg_name, $arg_info) = $post;
$body = "{$body}[$arg_name]";
$docs = "$docs$arg_name:\r\n {$arg_info['doc']}\r\n\r\n";
}
unset($params['_POST']);
}
if (isset($params['_FILES'])) {
$files = $params['_FILES'];
if(is_array($files)){
foreach ($files as $name => $value) {
//TODO: 这里假设只有一个文件上传
list ($arg_name, $arg_info) = $this->searchArgInfo($value);
$docs = "$docs$name:\r\n {$arg_info['doc']}\r\n\r\n";
}
}
unset($params['_POST']);
}
$sample = $path . $header . "\r\n" . $body;
return array(
$sample,
$docs,
);
}
private function searchArgInfo($value){
if(is_object($value)){
return $value;
}
return $this->searchArgInfo(array_values($value)[0]);
}
/**
* 生成响应的示例和说明
*
* @param array $api
* @return array [sample, doc]
*/
private function createResponseDoc($api, $default_return=true)
{
// 'name' => $fun_name,
// 'args' => $args,
// 'doc' => $anns['return'][$id]['doc']
$status = '';
if($default_return){
$status = "HTTP/1.1 200 OK\r\n";
}
$header = '';
$doc = '';
$body = '';
foreach ($api['returns'] as $return) {
$info = $this->getResponseInfo($return);
if (isset($info['status'])) {
$status = $info['status'];
}
if (isset($info['header'])) {
$header .= $info['header'];
}
if (isset($info['body'])) {
$body .= $info['body'];
}
if (isset($info['doc']) && $info['doc']) {
$doc .= $info['doc'];
}
}
$sample = $status . $header . "\r\n" . $body;
return array(
$sample,
$doc,
);
}
/**
* 生成响应的示例和说明
*
* @param array $api
* @return array [sample, doc]
*/
private function createExceptionDoc($api)
{
// 'name' => $fun_name,
// 'args' => $args,
// 'doc' => $anns['return'][$id]['doc']
$res = array();
foreach ($api['throws'] as $name => $throws) {
$status = "HTTP/1.1 500 Internal Server Error\r\n";
$header = '';
$doc = '';
$body = '';
foreach ($throws as $throw) {
$info = $this->getResponseInfo($throw);
if (isset($info['status'])) {
$status = $info['status'];
}
if (isset($info['header'])) {
$header .= $info['header'];
}
if (isset($info['body'])) {
if(is_array($info['body'])){
$body .= json_encode($info['body']);
}else{
$body .= $info['body'];
}
}
if (isset($info['doc']) && $info['doc']) {
$doc .= $info['doc'];
}
}
$sample = $status . $header . "\r\n" . $body;
$res[$name] = array(
$sample,
$doc,
);
}
return $res;
}
/**
* 获取单个响应的示例和说明
*
* @param array $api
* @return array [sample, doc]
*/
private function getResponseInfo($return)
{
$res = array();
if ($return['name'] === 'status') {
$arg = $return['args'][0];
$value = $arg['value'];
if ($arg['is_const']) {
$res['status'] = "HTTP/1.1 $value\r\n";
} else {
$res['status'] = "HTTP/1.1 [$value]\r\n";
}
if ($return['doc']) {
$res['doc'] = "$value:\r\n {$return['doc']}\r\n\r\n";
}
} elseif($return['name'] === 'res'){
$arg = $return['args'][0];
$value = $arg['value'];
if ($arg['is_const']) {
$res['status'] = "HTTP/1.1 $value\r\n";
} else {
$res['status'] = "HTTP/1.1 [$value]\r\n";
}
if ($return['doc']) {
$res['doc'] = "$value:\r\n {$return['doc']}\r\n\r\n";
}
$arg = $return['args'][1];
$value = $arg['value'];
if ($arg['is_const']) {
$res['body'] = $value;
} else {
if($value){
$res['body'] = "[$value]";
}else{
$res['body'] = "[return]";
}
}
if ($return['doc']) {
if(is_array($value)){
$value = json_encode($value);
}
$res['doc'] = "return $value:\r\n {$return['doc']}\r\n\r\n";
}
}elseif($return['name'] === 'header') {
$arg = $return['args'][0];
$value = $arg['value'];
if ($arg['is_const']) {
$res['header'] .= "$value \r\n";
} else {
$res['header'] .= "[$value] \r\n";
}
if ($return['doc']) {
$res['doc'] = "$value:\r\n {$return['doc']}\r\n\r\n";
}
} elseif ($return['name'] === 'cookie') {
$args = $return['args'];
foreach ($args as $k => &$arg) {
if (! $arg['is_const']) {
$value = $arg['value'];
$arg['value'] = "[$value]";
}
}
if ($return['doc']) {
$res['doc'] = "cookie {$args[0]['value']}:\r\n {$return['doc']}\r\n\r\n";
}
$res['header'] = 'Set-Cookie: ' . $args[0]['value'] . '=' . $args[1]['value']
. (empty($args[2]['value']) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', strtotime($args[2]['value'])) . ' GMT')
. (empty($args[3]['value']) ? '' : '; path=' . $args[3]['value'])
. (empty($args[4]['value']) ? '' : '; domain=' . $args[4]['value'])
. (empty($args[5]['value']) ? '' : '; secure')
. (empty($args[6]['value']) ? '' : '; HttpOnly');
$res['header'] .= "\r\n";
} elseif ($return['name'] === 'body') {
$arg = $return['args'][0];
$value = $arg['value'];
if ($arg['is_const']) {
$res['body'] = $value;
} else {
if($value){
$res['body'] = "[$value]";
}else{
$res['body'] = "[return]";
}
}
if ($return['doc']) {
if(is_array($value)){
$value = json_encode($value);
}
$res['doc'] = "return $value:\r\n {$return['doc']}\r\n\r\n";
}
}
return $res;
}
/**
* 遍历数组, 子数组
*
* @param unknown $arr
*/
static function walkTree(&$arr, $visitor)
{
foreach ($arr as $k => &$v) {
if (is_array($v)) {
self::walkTree($v, $visitor);
} else {
$visitor($v);
}
}
}
/**
* 获取invoker信息
*
* @param Invoker $invoker
* @param $method http方法
* @param $uri http url
* @return array
*/
public function getInvokerInfo( $method,$uri, $invoker)
{
$res = array();
$res['impl'] = $invoker->getMethodName();
$ann = new AnnotationReader();
$refl = new \ReflectionClass($invoker->getClassName());
$mrefl = $refl->getMethod($invoker->getMethodName());
$anns = $ann->getMethodAnnotations($mrefl, true);
// 过滤无效的参数
if(isset($anns['param'])){
$anns['param'] = array_values(array_filter($anns['param'], function ($i)
{
return isset($i['value']);
}));
}
if(isset($anns['return'])){
$anns['return'] = array_values(array_filter($anns['return'], function ($i)
{
return isset($i['value']);
}));
}
if(isset($anns['throws'])){
$anns['throws'] = array_values(array_filter($anns['throws'], function ($i)
{
return isset($i['value']);
}));
}
$res['doc'] = $this->getDocText($mrefl->getDocComment());
//找到匹配的@route注释
foreach ($anns['route'] as $route_doc){
//TODO: 同时精确匹配和通配符匹配时, 怎么处理
if(isset($route_doc['value'])){
list($m, $u) = $route_doc['value'];
$full_url = $invoker->getContainer()->path.'/'.$u;
$full_url = HttpRouterEntries::stringToPath($full_url);
if($full_url == HttpRouterEntries::stringToPath($uri) &&
$m === $method){
$text = $this->getDocText($route_doc['desc']);
if(!empty($text)){
$res['doc'] .= "\r\n";
$res['doc'] .= $text;
}
break;
}
}
}
$anns['route'];
$res['root'] = '/';
$res['path'] = $invoker->getContainer()->path;
// 获取参数信息
$res['params'] = array();
foreach ($invoker->getParams()->getParams() as $param) {
list ($is_const, $value, $info, $id) = $param;
list ($name, $is_ref, $is_optional, $default) = $info;
if (! $is_const) {
$res['params'][$name] = array(
'value' => $value,
'is_ref' => $is_ref,
'is_optional' => $is_optional,
'default' => $default,
'doc' => $this->getDocText($anns['param'][$id]['desc']),
);
}
}
// 依赖只是特殊的参数
$defaults = $refl->getDefaultProperties();
foreach ($refl->getProperties() as $property) {
foreach ($ann->getPropertyAnnotations($property, true) as $type => $value) {
if ($type !== 'inject') {
continue;
}
$name = $property->getName();
$value = $value[0];
if (is_array($value['value'])) {
$src = $value['value']['src'];
if (isset($value['value']['optional']) && $value['value']['optional']) {
$is_optional = true;
}
if (isset($value['value']['default'])) {
$default = $value['value']['default'];
}
} else {
$src = $value['value'];
}
if (substr($src, 0, 1) !== '$') {
continue;
}
if (array_key_exists($name, $defaults)) {
$is_optional = true;
$default = $defaults[$name];
}
$res['params'][$name] = array(
'value' => $src,
'is_ref' => false,
'is_optional' => $is_optional,
'default' => $default,
'doc' => $this->getDocText($value['desc']),
);
}
}
// 获取返回值信息
$res['returns'] = array();
foreach ($invoker->getReturns()->getParams() as $fun_name => $calls) {
foreach ($calls as $id => $call) {
$args = array();
foreach ($call as $num => $arg) {
list ($is_const, $value, $pos, $info) = $arg;
list ($name, $is_ref, $is_optional, $default) = $info;
$args[$num] = array(
'value' => $value,
'name' => $name,
'is_const' => $is_const,
);
}
$res['returns'][] = array(
'name' => $fun_name,
'args' => $args,
'doc' => $id===-1?null:$this->getDocText($anns['return'][$id]['desc']),
);
}
}
// 获取异常信息
$res['throws'] = array();
foreach ($invoker->getThrows()->getParams() as $exce_name => $throws) {
// $res['throws'][$exce_name] = array();
foreach ($throws as $fun_name => $calls) {
foreach ($calls as $id => $call) {
$args = array();
if($call !== null){
foreach ($call as $num => $arg) {
$args[$num] = array(
'value' => $arg,
'name' => null,
'is_const' => true,
);
}
}
$res['throws'][$exce_name][] = array(
'name' => $fun_name,
'args' => $args,
'doc' => $this->getDocText($anns['throws'][$id]['desc']),
);
}
}
}
return $res;
}
/**
* 去掉文档中的@标记和*号等, 保留可读文本
*
* @param string $doc
* @return string
*/
private function getDocText($doc)
{
$lines = explode("\n", $doc);
$ignore = array(
'@return',
'@param',
'@throws',
'@route',
'@path',
'@cache',
);
$text = '';
$fistline = true;
$is_ignored = false;
foreach ($lines as $num => $ori_line) {
$line = trim($ori_line, "*/\r\n\t ");
if($is_ignored){
if(substr($line, 0, 1 ) !== '@'){
continue;
}
}
$is_ignored = false;
foreach ($ignore as $word) {
if (strlen($line)>= strlen($word) && 0 === substr_compare($line, $word, 0, strlen($word))) {
$is_ignored = true;
break;
}
}
if ($is_ignored) {
continue;
}
if ($fistline) {
$fistline = false;
} else {
$text .= "\r\n";
}
$text .= trim(trim($ori_line, " \t\r\n"), "*/");
}
$text = trim($text, "\r\n");
return $text;
}
/**
* @inject("$.router")
*/
private $router;
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* $Id: DB.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql;
/**
*
* @author caoym
*
*/
class DB extends \PDO
{
public function __construct(
$dsn,
$username,
$passwd,
$options = [\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES'utf8';"]){
parent::__construct($dsn, $username, $passwd, $options);
$this->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* $Id: Native.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql;
/**
* 原始sql字符串, 拼接时不进行转义
* @author caoym
*
*/
class Native
{
/**
* @param string $str
*/
function __construct($str) {
$this->str = $str;
}
public function __toString(){
return $this->str;
}
public function get(){
return $this->str;
}
private $str;
}

View File

@@ -0,0 +1,44 @@
# ezsql
An an easy-to-use SQL builder.
## HOW TO USE
$db = new \PDO($dsn, $username, $passwd);
### SELECT
$res = Sql::select('a, b')
->from('table')
->leftJoin('table1')->on('table.id=table1.id')
->where('a=?',1)
->groupBy('b')->having('sum(b)=?', 2)
->orderBy('c', Sql::$ORDER_BY_ASC)
->limit(0,1)
->forUpdate()->of('d')
->get($db);
### UPDATE
$rows = Sql::update('table')
->set('a', 1)
->where('b=?', 2)
->orderBy('c', Sql::$ORDER_BY_ASC)
->limit(1)
->exec($db)
->rows
### INSERT
$newId = Sql::insertInto('table')
->values(['a'=>1])
->exec($db)
->lastInsertId()
### DELETE
$rows = Sql::deleteFrom('table')
->where('b=?', 2)
->orderBy('c', Sql::$ORDER_BY_ASC)
->limit(1)
->exec($db)
->rows

View File

@@ -0,0 +1,132 @@
<?php
/**
* $Id: Sql.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql;
use phprs\ezsql\rules\select\SelectRule;
use phprs\ezsql\rules\insert\InsertRule;
use phprs\ezsql\rules\update\UpdateRule;
use phprs\ezsql\rules\delete\DeleteRule;
use phprs\ezsql\rules\basic\BasicRule;
use phprs\ezsql\rules\replace\ReplaceIntoRule;
require_once __DIR__.'/rules/select.php';
require_once __DIR__.'/rules/insert.php';
require_once __DIR__.'/rules/update.php';
require_once __DIR__.'/rules/delete.php';
require_once __DIR__.'/rules/replace.php';
/**
* Easy SQL
*
* How-to-use:
*
* $db = new DB($dsn, $username, $passwd);
* // 1. select
* $res = Sql::select('a, b')
* ->from('table')
* ->leftJoin('table1')->on('table.id=table1.id')
* ->where('a=?',1)
* ->groupBy('b')->having('sum(b)=?', 2)
* ->orderBy('c', Sql::$ORDER_BY_ASC)
* ->limit(0,1)
* ->forUpdate()->of('d')
* ->get($db);
*
* // 2. update
* $rows = Sql::update('table')
* ->set('a', 1)
* ->where('b=?', 2)
* ->orderBy('c', Sql::$ORDER_BY_ASC)
* ->limit(1)
* ->exec($db)
* ->rows
*
* // 3. insert
* $newId = Sql::insertInto('table')
* ->values(['a'=>1])
* ->exec($db)
* ->lastInsertId()
*
* //4. delete
* $rows = Sql::deleteFrom('table')
* ->where('b=?', 2)
* ->orderBy('c', Sql::$ORDER_BY_ASC)
* ->limit(1)
* ->exec($db)
* ->rows
*
* @author caoym <caoyangmin@gmail.com>
*/
class Sql{
/**
* select('column0,column1') => "SELECT column0,column1"
*
* select('column0', 'column1') => "SELECT column0,column1"
*
* @param $param0 columns
* @return \phprs\ezsql\rules\select\FromRule
*/
static public function select($param0='*', $_=null){
$obj = new SelectRule(new SqlConetxt());
$args = func_get_args();
if(empty($args)){
$args = ['*'];
}
return $obj->select(implode(',', $args));
}
/**
* insertInto('table') => "INSERT INTO table"
*
* @param string $table
* @return \phprs\ezsql\rules\insert\ValuesRule
*/
static public function insertInto($table) {
$obj = new InsertRule(new SqlConetxt());
return $obj->insertInto($table);
}
/**
* update('table') => "UPDATE table"
* @param string $table
* @return \phprs\ezsql\rules\update\UpdateSetRule
*/
static public function update($table) {
$obj = new UpdateRule(new SqlConetxt());
return $obj->update($table);
}
/**
* deleteFrom('table') => "DELETE FROM table"
* @param string $table
* @return \phprs\ezsql\rules\basic\WhereRule
*/
static public function deleteFrom($table){
$obj = new DeleteRule(new SqlConetxt());
return $obj->deleteFrom($table);
}
/**
* replaceInto('table') => "REPLACE INTO table"
* @param string $table
* @return \phprs\ezsql\rules\replace\ValuesRule
*/
static public function replaceInto($table){
$obj = new ReplaceIntoRule(new SqlConetxt());
return $obj->replaceInto($table);
}
/**
* Splice sql use native string(without escaping)
* for example:
* where('time>?', 'now()') => " WHERE time > 'now()' "
* where('time>?', Sql::native('now()')) => " WHERE time > now() "
* @param string $str
* @return Native
*/
static public function native($str){
return new Native($str);
}
static public $ORDER_BY_ASC ='ASC';
static public $ORDER_BY_DESC ='DESC';
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* $Id: SqlConetxt.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql;
/**
* @author caoym
*/
class SqlConetxt{
/**
* 拼接sql语句并自动插入空格
* @param string $sql 表达式
*/
public function appendSql($sql, $addSpace=true){
if($this->sql == ''){
$this->sql = $sql;
}else{
if($addSpace){
$this->sql = $this->sql.' '.$sql;
}else{
$this->sql = $this->sql.$sql;
}
}
}
/**
* 增加绑定变量值
* @param array $params 变量
*/
public function appendParams($params){
$this->params = array_merge($this->params, $params);
}
public $sql='';
public $params=[];
}

View File

@@ -0,0 +1,469 @@
<?php
/**
* $Id: impls.php 401 2015-11-06 08:28:26Z dong.chen $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql\impls;
use phprs\util\NestedStringCut;
use phprs\util\Verify;
use phprs\ezsql\SqlConetxt;
class Response{
public function __construct($success,$pdo, $st){
$this->pdo = $pdo;
$this->st = $st;
$this->success = $success;
$this->rows = $this->st->rowCount();
}
public function lastInsertId($name=null){
return $this->pdo->lastInsertId($name);
}
/**
* @var bool
* true on success or false on failure.
*/
public $success;
/**
* @var int
* the number of rows.
*/
public $rows;
/**
*
* @var \PDO
*/
public $pdo;
/**
* @var \PDOStatement
*/
public $st;
}
class SelectImpl
{
static public function select($context, $columns){
$context->appendSql("SELECT $columns");
}
}
class FromImpl
{
static public function from($context, $tables){
$context->appendSql("FROM $tables");
}
}
class DeleteImpl
{
static public function deleteFrom($context, $from)
{
$context->appendSql("DELETE FROM $from");
}
}
class JoinImpl
{
static public function join($context, $type, $table) {
if($type){
$context->appendSql("$type JOIN $table");
}else{
$context->appendSql("JOIN $table");
}
}
}
class JoinOnImpl
{
static public function on($context, $condition) {
$context->appendSql("ON $condition");
}
}
class ForUpdateImpl
{
static public function forUpdate($context){
$context->appendSql("FOR UPDATE");
}
}
class ForUpdateOfImpl
{
static public function of($context, $column){
$context->appendSql("OF $column");
}
}
class InsertImpl
{
static public function insertInto($context, $table) {
$context->appendSql("INSERT INTO $table");
}
}
class ReplaceImpl
{
static public function replaceInto($context, $table) {
$context->appendSql("REPLACE INTO $table");
}
}
class ValuesImpl
{
static public function values($context, $values){
$params = [];
$stubs = [];
foreach ($values as $v){
if(is_a($v, 'phprs\\ezsql\\Native')){//直接拼接sql不需要转义
$stubs[]=$v->get();
}else{
$stubs[]='?';
$params[] = $v;
}
}
$stubs = implode(',', $stubs);
if(array_keys($values) === range(0, count($values) - 1)){
//VALUES(val0, val1, val2)
$context->appendSql("VALUES($stubs)");
}else{
//(col0, col1, col2) VALUES(val0, val1, val2)
$columns = implode(',', array_keys($values));
$context->appendSql("($columns) VALUES($stubs)",false);
}
$context->appendParams($params);
}
private $sql = null;
}
class UpdateImpl
{
static public function update($context, $table){
$context->appendSql("UPDATE $table");
}
}
class UpdateSetImpl
{
public function set($context, $column, $value){
$prefix = '';
if($this->first){
$this->first = false;
$prefix = 'SET ';
}else{
$prefix = ',';
}
if(is_a($value, 'phprs\\ezsql\\Native')){
$context->appendSql("$prefix$column=$value",$prefix == 'SET ');
}else{
$context->appendSql("$prefix$column=?",$prefix == 'SET ');
$context->appendParams([$value]);
}
}
public function setArgs($context, $values){
$set = [];
$params = [];
foreach ($values as $k=>$v){
if(is_a($v, 'phprs\\ezsql\\Native')){//直接拼接sql不需要转义
$set[]= "$k=".$v->get();
}else{
$set[]= "$k=?";
$params[]=$v;
}
}
if($this->first){
$this->first = false;
$context->appendSql('SET '.implode(',', $set));
$context->appendParams($params);
}else{
$context->appendSql(','.implode(',', $set),false);
$context->appendParams($params);
}
}
private $first=true;
}
class OrderByImpl
{
public function orderByArgs($context, $orders){
if(empty($orders)){
return $this;
}
$params = array();
foreach ($orders as $k=>$v){
if(is_integer($k)){
Verify::isTrue(
preg_match('/^[a-zA-Z0-9_.]+$/', $v),
new \InvalidArgumentException("invalid params for orderBy(".json_encode($orders).")"));
$params[] = $v;
}else{
$v = strtoupper($v);
Verify::isTrue(
preg_match('/^[a-zA-Z0-9_.]+$/', $k) &&
($v =='DESC' || $v =='ASC'), new \InvalidArgumentException("invalid params for orderBy(".json_encode($orders).")"));
$params[] = "$k $v";
}
}
if($this->first){
$this->first = false;
$context->appendSql('ORDER BY '.implode(',', $params));
}else{
$context->appendSql(','.implode(',', $params),false);
}
return $this;
}
public function orderBy($context, $column, $order=null){
if($this->first){
$this->first = false;
$context->appendSql("ORDER BY $column");
}else{
$context->appendSql(",$column", false);
}
if($order){
$context->appendSql($order);
}
return $this;
}
private $first=true;
}
class LimitImpl
{
static public function limit($context, $size){
$intSize = intval($size);
Verify::isTrue(strval($intSize) == $size,
new \InvalidArgumentException("invalid params for limit($size)"));
$context->appendSql("LIMIT $size");
}
static public function limitWithOffset($context,$start, $size){
$intStart = intval($start);
$intSize = intval($size);
Verify::isTrue(strval($intStart) == $start && strval($intSize) == $size,
new \InvalidArgumentException("invalid params for limit($start, $size)"));
$context->appendSql("LIMIT $start,$size");
}
}
class WhereImpl{
static private function findQ($str,$offset = 0,$no=0){
$found = strpos($str, '?', $offset);
if($no == 0 || $found === false){
return $found;
}
return self::findQ($str, $found+1, $no-1);
}
static public function having($context, $expr, $args){
self::condition($context, 'HAVING', $expr, $args);
}
static public function where($context, $expr, $args){
self::condition($context, 'WHERE', $expr, $args);
}
static public function havingArgs($context, $args){
self::conditionArgs($context, 'HAVING', $args);
}
static public function whereArgs($context, $args){
self::conditionArgs($context, 'WHERE', $args);
}
/**
* find like Mongodb query glossary
* whereArray(
* [
* 'id'=>['>'=>1],
* 'name'=>'cym',
* ]
* )
* 支持的操作符有
* = 'id'=>['=' => 1]
* > 'id'=>['>' => 1]
* < 'id'=>['<' => 1]
* <> 'id'=>['<>' => 1]
* >= 'id'=>['>=' => 1]
* <= 'id'=>['<=' => 1]
* BETWEEN 'id'=>['BETWEEN' => [1 ,2]]
* LIKE 'id'=>['LIKE' => '1%']
* IN 'id'=>['IN' => [1,2,3]]
* NOT IN 'id'=>['NOT IN' => [1,2,3]]
*
* @param array $args
*/
static public function conditionArgs($context, $prefix, $args=[]){
if($args ===null){
return ;
}
$exprs = array();
$params = array();
foreach ($args as $k => $v){
if(is_array($v)){
$ops = ['=', '>', '<', '<>', '>=', '<=', 'IN', 'NOT IN', 'BETWEEN', 'LIKE'];
$op = array_keys($v)[0];
$op = strtoupper($op);
Verify::isTrue(
false !== array_search($op, $ops),
new \InvalidArgumentException("invalid param $op for whereArgs"));
$var = array_values($v)[0];
if($op == 'IN' || $op == 'NOT IN'){
$stubs = [];
foreach ($var as $i){
if(is_a($i, 'phprs\\ezsql\\Native')){
$stubs[]=strval($i);
}else{
$stubs[]='?';
$params[] = $i;
}
}
$stubs = implode(',', $stubs);
$exprs[] = "$k $op ($stubs)";
}else if($op == 'BETWEEN'){
$cond = "$k BETWEEN";
if(is_a($var[0], 'phprs\\ezsql\\Native')){
$cond = "$cond ".strval($var[0]);
}else{
$cond = "$cond ?";
$params[] = $var[0];
}
if(is_a($var[1], 'phprs\\ezsql\\Native')){
$cond = "$cond AND ".strval($var[1]);
}else{
$cond = "$cond AND ?";
$params[] = $var[1];
}
$exprs[] = $cond;
}else{
if(is_a($var, 'phprs\\ezsql\\Native')){
$exprs[] = "$k $op ".strval($var);
}else{
$exprs[] = "$k $op ?";
$params[] = $var;
}
}
}else{
if(is_a($v, 'phprs\\ezsql\\Native')){
$exprs[] = "$k = ".strval($v);
}else{
$exprs[] = "$k = ?";
$params[] = $v;
}
}
}
return self::condition($context, $prefix, implode(' AND ', $exprs), $params);
}
static public function condition($context, $prefix, $expr, $args){
if(!empty($expr)){
if($args){
//因为PDO不支持绑定数组变量, 这里需要手动展开数组
//也就是说把 where("id IN(?)", [1,2]) 展开成 where("id IN(?,?)", 1,2)
$cutted = null;
$cut = null;
$toReplace = array();
$newArgs=array();
//找到所有数组对应的?符位置
foreach ($args as $k =>$arg){
if(is_array($arg) || is_a($arg, 'phprs\\ezsql\\Native')){
if(!$cutted){
$cut = new NestedStringCut($expr);
$cutted = $cut->getText();
}
//找到第$k个?符
$pos = self::findQ($cutted, 0, $k);
$pos = $cut->mapPos($pos);
Verify::isTrue($pos !== false,
new \InvalidArgumentException("unmatched params and ? @ $expr"));
if(is_array($arg)){
$stubs = [];
foreach ($arg as $i){
if(is_a($i, 'phprs\\ezsql\\Native')){
$stubs[] = strval($i);
}else{
$stubs[] = '?';
$newArgs[] = $i;
}
}
$stubs = implode(',', $stubs);
}else{
$stubs = strval($arg);
}
$toReplace[] = [$pos, $stubs];
}else{
$newArgs[]=$arg;
}
}
if(count($toReplace)){
$toReplace = array_reverse($toReplace);
foreach ($toReplace as $i){
list($pos, $v) = $i;
$expr = substr($expr, 0, $pos).$v. substr($expr, $pos+1);
}
$args = $newArgs;
}
}
$context->appendSql($prefix.' '.$expr);
if($args){
$context->appendParams($args);
}
}
}
}
class GroupByImpl{
static public function groupBy($context, $column){
$context->appendSql("GROUP BY $column");
}
}
class ExecImpl
{
/**
*
* @param $context SqlConetxt
* @param $db \PDO
* @param $exceOnError boolean whether throw exceptions
* @return Response
*/
static public function exec($context, $db, $exceOnError=true) {
if($exceOnError){
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
$st = $db->prepare($context->sql);
$success = $st->execute($context->params);
return new Response($success, $db,$st);
}
/**
*
* @param SqlConetxt $context
* @param PDO $db
* @param boolean $errExce
* @param string $asDict return as dict or array
* @return false|array
*/
static public function get($context, $db, $dictAs=null ,$errExce=true){
if($errExce){
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
$st = $db->prepare($context->sql);
if($st->execute($context->params)){
$res = $st->fetchAll(\PDO::FETCH_ASSOC);
if ($dictAs){
$dict= [];
foreach ($res as $i){
$dict[$i[$dictAs]]=$i;
}
return $dict;
}
return $res;
}else{
return false;
}
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* $Id: basic.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql\rules\basic;
use phprs\ezsql\impls\ExecImpl;
use phprs\ezsql\impls\LimitImpl;
use phprs\ezsql\impls\OrderByImpl;
use phprs\ezsql\impls\WhereImpl;
require_once dirname(__DIR__).'/impls.php';
class BasicRule
{
public function __construct($context){
$this->context = $context;
}
protected $context;
}
class ExecRule extends BasicRule
{
/**
* Execute sql
* @param \PDO $db
* @param boolean $errExce whether throw exceptios
* @return Response
*/
public function exec($db, $errExce=true) {
return ExecImpl::exec($this->context, $db, $errExce);
}
}
class LimitRule extends ExecRule
{
/**
* limit(1) => "LIMIT 1"
* @param int $size
* @return \phprs\ezsql\rules\basic\ExecRule
*/
public function limit($size) {
LimitImpl::limit($this->context, $size);
return new ExecRule($this->context);
}
}
class OrderByRule extends LimitRule
{
public function __construct($context){
parent::__construct($context);
$this->impl = new OrderByImpl();
}
/**
* orderByArgs(['column0', 'column1'=>Sql::$ORDER_BY_ASC]) => "ORDER BY column0,column1 ASC"
* @param array $orders
* @return \phprs\ezsql\rules\basic\LimitRule
*/
public function orderByArgs($orders) {
$this->impl->orderByArgs($this->context, $orders);
return new LimitRule($this->context);
}
/**
*
* orderBy('column') => "ORDER BY column"
* orderBy('column', Sql::$ORDER_BY_ASC) => "ORDER BY column ASC"
* orderBy('column0')->orderBy('column1') => "ORDER BY column0, column1"
*
* @param string $column
* @param string $order Sql::$ORDER_BY_ASC or Sql::$ORDER_BY_DESC
*
* @return \phprs\ezsql\rules\basic\LimitRule
*/
public function orderBy($column, $order=null) {
$this->impl->orderBy($this->context, $column, $order);
return new LimitRule($this->context);
}
private $impl;
}
class WhereRule extends OrderByRule
{
/**
*
* where('a=?', 1) => "WHERE a=1"
* where('a=?', Sql::native('now()')) => "WHERE a=now()"
* where('a IN (?)', [1, 2]) => "WHERE a IN (1,2)"
*
* @param string $expr
* @param mixed $_
* @return \phprs\ezsql\rules\basic\OrderByRule
*/
public function where($expr, $_= null) {
WhereImpl::where($this->context, $expr, array_slice(func_get_args(), 1));
return new OrderByRule($this->context);
}
/**
*
* whereArgs([
* 'a'=>1,
* 'b'=>['IN'=>[1,2]]
* 'c'=>['BETWEEN'=>[1,2]]
* 'd'=>['<>'=>1]
* ])
*
* =>
* "WHERE a=1 AND b IN(1,2) AND c BETWEEN 1 AND 2 AND d<>1"
* @param string $args
* @return \phprs\ezsql\rules\basic\OrderByRule
*/
public function whereArgs($args) {
WhereImpl::whereArgs($this->context, $args);
return new OrderByRule($this->context);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* $Id: delete.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql\rules\delete;
use phprs\ezsql\rules\basic\BasicRule;
use phprs\ezsql\impls\DeleteImpl;
use phprs\ezsql\rules\basic\WhereRule;
require_once dirname(__DIR__).'/impls.php';
require_once __DIR__.'/basic.php';
class DeleteRule extends BasicRule
{
/**
* deleteFrom('table') => "DELETE FROM table"
* @param string $table
* @return \phprs\ezsql\rules\basic\WhereRule
*/
public function deleteFrom($table) {
DeleteImpl::deleteFrom($this->context, $table);
return new WhereRule($this->context);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* $Id: insert.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql\rules\insert;
use phprs\ezsql\rules\basic\BasicRule;
use phprs\ezsql\rules\basic\ExecRule;
use phprs\ezsql\impls\InsertImpl;
use phprs\ezsql\impls\ValuesImpl;
require_once dirname(__DIR__).'/impls.php';
require_once __DIR__.'/basic.php';
class InsertRule extends BasicRule
{
/**
*
* insertInto('table')->values([1,2]) => "INSERT INTO table VALUES(1,2)"
* @param string $table
* @return \phprs\ezsql\rules\insert\ValuesRule
*/
public function insertInto($table) {
InsertImpl::insertInto($this->context, $table);
return new ValuesRule($this->context);
}
}
class ValuesRule extends BasicRule
{
/**
*
* insertInto('table')->values([1,2]) => "INSERT INTO table VALUES(1,2)"
* insertInto('table')->values(['a'=>1, 'b'=>Sql::native('now()')]) => "INSERT INTO table(a,b) VALUES(1,now())"
* @param unknown $values
* @return \phprs\ezsql\rules\basic\ExecRule
*/
public function values($values) {
ValuesImpl::values($this->context, $values);
return new ExecRule($this->context);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace phprs\ezsql\rules\replace;
use phprs\ezsql\rules\basic\BasicRule;
use phprs\ezsql\rules\basic\ExecRule;
use phprs\ezsql\impls\ReplaceImpl;
use phprs\ezsql\impls\ValuesImpl;
require_once dirname(__DIR__).'/impls.php';
require_once __DIR__.'/basic.php';
class ReplaceIntoRule extends BasicRule
{
/**
* replaceInto('table')->values([1,2]) => "REPLACE INTO table VALUES(1,2)"
* @param string $table
* @return \phprs\ezsql\rules\replace\ValuesRule
*/
public function replaceInto($table) {
ReplaceImpl::replaceInto($this->context, $table);
return new ValuesRule($this->context);
}
}
class ValuesRule extends BasicRule
{
/**
* replaceInto('table')->values([1,2]) => "REPLACE INTO table VALUES(1,2)"
* replaceInto('table')->values(['a'=>1, 'b'=>Sql::native('now()')]) => "REPLACE INTO table(a,b) VALUES(1,now())"
* @param unknown $values
* @return \phprs\ezsql\rules\basic\ExecRule
*/
public function values($values) {
ValuesImpl::values($this->context, $values);
return new ExecRule($this->context);
}
}

View File

@@ -0,0 +1,271 @@
<?php
/**
* $Id: select.php 246 2015-10-21 04:48:09Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql\rules\select;
use phprs\ezsql\rules\basic\BasicRule;
use phprs\ezsql\impls\ExecImpl;
use phprs\ezsql\impls\SelectImpl;
use phprs\ezsql\impls\FromImpl;
use phprs\ezsql\impls\JoinImpl;
use phprs\ezsql\impls\JoinOnImpl;
use phprs\ezsql\impls\WhereImpl;
use phprs\ezsql\impls\GroupByImpl;
use phprs\ezsql\impls\OrderByImpl;
use phprs\ezsql\impls\LimitImpl;
use phprs\ezsql\impls\ForUpdateOfImpl;
use phprs\ezsql\impls\ForUpdateImpl;
require_once dirname(__DIR__).'/impls.php';
require_once __DIR__.'/basic.php';
class SelectRule extends BasicRule
{
/**
* select('column0, column1') => "SELECT column0, column1"
* select('column0', 'column1') => "SELECT column0, column1"
* @param string $columns
* @return \phprs\ezsql\rules\select\FromRule
*/
public function select($columns) {
SelectImpl::select($this->context, $columns);
return new FromRule($this->context);
}
}
class GetRule extends BasicRule
{
/**
* Execute sql and get responses
* @param \PDO $db
* @param $errExce whether throw exceptions
* @return array
*/
public function get($db, $asDict=false,$errExce=true) {
return ExecImpl::get($this->context, $db, $asDict,$errExce);
}
}
class FromRule extends GetRule
{
/**
* from('table') => "FROM table"
* @param string $table
* @return \phprs\ezsql\rules\select\JoinRule
*/
public function from($table){
FromImpl::from($this->context, $table);
return new JoinRule($this->context);
}
}
class ForUpdateOfRule extends GetRule
{
/**
* forUpdate()->of('column') => 'FOR UPDATE OF column'
* @param string $column
* @return \phprs\ezsql\rules\select\GetRule
*/
public function of($column){
ForUpdateOfImpl::of($this->context, $column);
return new GetRule($this->context);
}
}
class ForUpdateRule extends GetRule
{
/**
* forUpdate() => 'FOR UPDATE'
* @return \phprs\ezsql\rules\select\ForUpdateOfRule
*/
public function forUpdate(){
ForUpdateImpl::forUpdate($this->context);
return new ForUpdateOfRule($this->context);
}
}
class LimitRule extends ForUpdateRule
{
/**
* limit(0,1) => "LIMIT 0,1"
* @param int $start
* @param int $size
* @return \phprs\ezsql\rules\select\ForUpdateRule
*/
public function limit($start, $size) {
LimitImpl::limitWithOffset($this->context, $start, $size);
return new ForUpdateRule($this->context);
}
}
class OrderByRule extends LimitRule
{
public function __construct($context){
parent::__construct($context);
$this->order = new OrderByImpl();
}
/**
* orderBy('column') => "ORDER BY column"
* orderBy('column', Sql::$ORDER_BY_ASC) => "ORDER BY column ASC"
* orderBy('column0')->orderBy('column1') => "ORDER BY column0, column1"
*
* @param string $column
* @param string $order Sql::$ORDER_BY_ASC or Sql::$ORDER_BY_DESC
* @return \phprs\ezsql\rules\select\OrderByRule
*/
public function orderBy($column, $order=null) {
$this->order->orderBy($this->context, $column, $order);
return $this;
}
/**
* orderByArgs(['column0', 'column1'=>Sql::$ORDER_BY_ASC]) => "ORDER BY column0,column1 ASC"
* @param array $args
* @return \phprs\ezsql\rules\select\OrderByRule
*/
public function orderByArgs($args) {
$this->order->orderByArgs($this->context, $args);
return $this;
}
/**
* @var OrderByImpl
*/
private $order;
}
class HavingRule extends OrderByRule
{
/**
*
* having('SUM(a)=?', 1) => "HAVING SUM(a)=1"
* having('a>?', Sql::native('now()')) => "HAVING a>now()"
* having('a IN (?)', [1, 2]) => "HAVING a IN (1,2)"
*
* @param string $expr
* @param string $_
* @return \phprs\ezsql\rules\select\OrderByRule
*/
public function having($expr, $_=null) {
WhereImpl::having($this->context, $expr, array_slice(func_get_args(), 1));
return new OrderByRule($this->context);
}
/**
*
* havingArgs([
* 'a'=>1,
* 'b'=>['IN'=>[1,2]]
* 'c'=>['BETWEEN'=>[1,2]]
* 'd'=>['<>'=>1]
* ])
*
* =>
* "HAVING a=1 AND b IN(1,2) AND c BETWEEN 1 AND 2 AND d<>1"
*
*
* @param array $args
* @return \phprs\ezsql\rules\select\OrderByRule
*/
public function havingArgs($args) {
WhereImpl::havingArgs($this->context, $args);
return new OrderByRule($this->context);
}
}
class GroupByRule extends OrderByRule
{
/**
* groupBy('column') => "GROUP BY column"
* @param string $column
* @return \phprs\ezsql\rules\select\HavingRule
*/
public function groupBy($column) {
GroupByImpl::groupBy($this->context, $column);
return new HavingRule($this->context);
}
}
class WhereRule extends GroupByRule
{
/**
*
* where('a=?', 1) => "WHERE a=1"
* where('a=?', Sql::native('now()')) => "WHERE a=now()"
* where('a IN (?)', [1, 2]) => "WHERE a IN (1,2)"
*
* @param string $expr
* @param mixed $_
* @return \phprs\ezsql\rules\select\GroupByRule
*/
public function where($expr, $_=null) {
WhereImpl::where($this->context, $expr, array_slice(func_get_args(), 1));
return new GroupByRule($this->context);
}
/**
* whereArgs([
* 'a'=>1,
* 'b'=>['IN'=>[1,2]]
* 'c'=>['BETWEEN'=>[1,2]]
* 'd'=>['<>'=>1]
* ])
*
* =>
* "WHERE a=1 AND b IN(1,2) AND c BETWEEN 1 AND 2 AND d<>1"
* @param array $args
* @return\phprs\ezsql\rules\select\GroupByRule
*/
public function whereArgs($args) {
WhereImpl::whereArgs($this->context,$args);
return new GroupByRule($this->context);
}
}
class JoinRule extends WhereRule
{
/**
* join('table1')->on('table0.id=table1.id') => "JOIN table1 ON table0.id=table1.id"
* @param string $table
* @return \phprs\ezsql\rules\select\JoinOnRule
*/
public function join($table){
JoinImpl::join($this->context,null, $table);
return new JoinOnRule($this->context);
}
/**
* leftJoin('table1')->on('table0.id=table1.id') => "LEFT JOIN table1 ON table0.id=table1.id"
* @param string $table
* @return \phprs\ezsql\rules\select\JoinOnRule
*/
public function leftJoin($table){
JoinImpl::join($this->context,'LEFT', $table);
return new JoinOnRule($this->context);
}
/**
* rightJoin('table1')->on('table0.id=table1.id') => "RIGHT JOIN table1 ON table0.id=table1.id"
* @param string $table
* @return \phprs\ezsql\rules\select\JoinOnRule
*/
public function rightJoin($table) {
JoinImpl::join($this->context,'RIGHT', $table);
return new JoinOnRule($this->context);
}
/**
* innerJoin('table1')->on('table0.id=table1.id') => "INNER JOIN table1 ON table0.id=table1.id"
* @param string $table
* @return \phprs\ezsql\rules\select\JoinOnRule
*/
public function innerJoin($table) {
JoinImpl::join($this->context,'INNER', $table);
return new JoinOnRule($this->context);
}
}
class JoinOnRule extends BasicRule
{
/**
* join('table1')->on('table0.id=table1.id') => "JOIN table1 ON table0.id=table1.id"
* @param string $condition
* @return \phprs\ezsql\rules\select\WhereRule
*/
public function on($condition){
JoinOnImpl::on($this->context, $condition);
return new JoinRule($this->context);
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* $Id: update.php 131 2015-10-10 02:25:57Z yangmin.cao $
* @author caoym(caoyangmin@gmail.com)
*/
namespace phprs\ezsql\rules\update;
use phprs\ezsql\rules\basic\BasicRule;
use phprs\ezsql\rules\basic\WhereRule;
use phprs\ezsql\impls\UpdateSetImpl;
use phprs\ezsql\impls\UpdateImpl;
require_once dirname(__DIR__).'/impls.php';
require_once __DIR__.'/basic.php';
class UpdateRule extends BasicRule
{
/**
* update('table')->set('a', 1) => "UPDATE table SET a=1"
* @param string $table
* @return \phprs\ezsql\rules\update\UpdateSetRule
*/
public function update($table) {
UpdateImpl::update($this->context, $table);
return new UpdateSetRule($this->context);
}
}
class UpdateSetRule extends WhereRule
{
public function __construct($context){
parent::__construct($context);
$this->impl = new UpdateSetImpl();
}
/**
* update('table')->set('a', 1) => "UPDATE table SET a=1"
* update('table')->set('a', 1)->set('b',Sql::native('now()')) => "UPDATE table SET a=1,b=now()"
* @param string $column
* @param mixed $value
* @return \phprs\ezsql\rules\update\UpdateSetRule
*/
public function set($column, $value) {
$this->impl->set($this->context, $column, $value);
return $this;
}
/**
* update('table')->set(['a'=>1, 'b'=>Sql::native('now()')]) => "UPDATE table SET a=1,b=now()"
* @param array $values
* @return \phprs\ezsql\rules\update\UpdateSetRule
*/
public function setArgs($values) {
$this->impl->setArgs($this->context, $values);
return $this;
}
private $impl;
}

View File

@@ -0,0 +1,101 @@
<?php
namespace phprs\util;
/**
* 去除//注释的内容,但会跳过引号内的//
* @author caoym
*
*/
class AnnotationCleaner
{
static public function clean($text) {
$o = new AnnotationCleaner();
return $o->clean_($text);
}
//去掉注释
private function clean_($text) {
$this->dest = '';
$this->tmp = $text;
$state = 'stateNormal';
while ($state){
$state = $this->$state();
}
return $this->dest;
}
private function stateNormal(){
$stateBegin = [
'//'=>'stateAntSL', //单行注释
'/*'=>'stateAntML',//多行注释
'\''=>'stateStrSQ', //单引号
'"'=>'stateStrDQ',//双引号
];
$count = strlen($this->tmp);
for($i=0; $i<$count; $i++){
foreach ($stateBegin as $k=>$v){
if(substr($this->tmp, $i, strlen($k)) == $k){
$this->dest .= substr($this->tmp, 0, $i);
$this->tmp = substr($this->tmp, $i);
return $v;
}
}
}
$this->dest .= $this->tmp;
$this->tmp = '';
return false;
}
/**
* 单行注释
*/
private function stateAntSL(){
$pos = strpos($this->tmp, "\n");
if($pos){
$this->tmp = substr($this->tmp, $pos);
}else{
$this->tmp = '';
}
return 'stateNormal';
}
/**
* 双行注释
*/
private function stateAntML(){
$pos = strpos($this->tmp, "*/");
if($pos){
$this->tmp = substr($this->tmp, $pos+2);
}else{
$this->tmp = '';
}
return 'stateNormal';
}
// 单引号
private function stateStrSQ(){
return $this->stateStr('\'');
}
// 双引号
private function stateStrDQ(){
return $this->stateStr('"');
}
private function stateStr($q){
$count = strlen($this->tmp);
for($i=1; $i<$count; $i++){
if(substr($this->tmp, $i, 1) == $q){
$this->dest .= substr($this->tmp, 0, $i+1);
$this->tmp = substr($this->tmp, $i+1);
return 'stateNormal';
}else if(substr($this->tmp, $i, 1) == '\\'){//文本内转义
$i++;
continue;//跳过一个
}
}
$this->dest .= $this->tmp;
$this->tmp = '';
return 'stateNormal';
}
private $tmp;
private $dest;
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* $Id: AnnotationReader.php 56458 2014-11-29 15:06:20Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
**/
namespace phprs\util;
//初始化全局的AnnotationReader并增加对自定义Annotation的支持
class AnnotationReader{
public function __construct(){
$this->parser= new DocParser();
}
public function getClassAnnotations(\ReflectionClass $class, $record_doc=false)
{
$cn = $class->getName();
if(isset($this->cache[$cn]['class'])){
return $this->cache[$cn]['class'];
}
$this->cache[$cn]['class'] = array();
$annots = $this->parser->parse($class->getDocComment(), 'class '.$cn, $record_doc);
foreach ($annots as $annot){
$key = $annot[0];
$annot = $annot[1];
$this->cache[$cn]['class'][$key][]=$annot;
}
return $this->cache[$cn]['class'];
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(\ReflectionMethod $method, $record_doc=false)
{
$cn = $method->getDeclaringClass()->getName();
$id = $method->getName();
if(isset($this->cache[$cn]['method'][$id])){
return $this->cache[$cn]['method'][$id];
}
$this->cache[$cn]['method'][$id] = array();
$annots = $this->parser->parse($method->getDocComment(), 'method '.$cn.'::'.$id.'()', $record_doc);
foreach ($annots as $annot){
$key = $annot[0];
$annot = $annot[1];
$this->cache[$cn]['method'][$id][$key][]=$annot;
}
return $this->cache[$cn]['method'][$id];
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(\ReflectionProperty $property, $record_doc=false)
{
$cn = $property->getDeclaringClass()->getName();
$id = $property->getName();
if(isset($this->cache[$cn]['property'][$id])){
return $this->cache[$cn]['property'][$id];
}
$this->cache[$cn]['property'][$id] = array();
$annots = $this->parser->parse($property->getDocComment(), 'property '.$cn.'::$'.$id, $record_doc);
foreach ($annots as $annot){
$key= $annot[0];
$annot= $annot[1];
$this->cache[$cn]['property'][$id][$key][]=$annot;
}
return $this->cache[$cn]['property'][$id];
}
private $cache=array() ;
private $parser ;
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* $Id: ApcCache.php 57516 2014-12-23 05:44:20Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
*/
namespace phprs\util;
/**
* apc 缓存
* @author caoym
*
*/
class ApcCache implements KVCatchInterface
{
/**
* get
* @param string $key
* @param boolean $succeeded
* @return mixed The stored variable or array of variables on success; false on failure
*/
public function get($key, &$succeeded)
{
return apc_fetch($key, $succeeded);
}
/**
* @param string $key
* @param string $var
* @param int $ttl
* @return boolean bool Returns true on success or false on failure. Second syntax returns array with error keys.
*/
public function set($key, $var, $ttl)
{
return apc_store($key, $var, $ttl);
}
/**
* @param string $key
* @return boolean mixed Returns true on success or false on failure.
*/
public function del($key)
{
return apc_delete($key);
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* $Id: AutoClassLoader.php 58820 2015-01-16 16:29:33Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs\util;
/**
* 反序列化时自动加载类
* @author caoym
*
*/
class AutoClassLoader{
/**
* 反序列化时被调用
*/
function __wakeup(){
ClassLoader::addClassMap($this->map, false);
}
/**
* 添加类文件映射
* @param unknown $name
* @param unknown $file
*/
public function addClass($name, $file){
$this->map[$name] = $file;
ClassLoader::addClassMap($this->map, false);
}
/**
*
* @return array:
*/
public function getClasses(){
return $this->map;
}
private $map=array();
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* $Id: Cache.php 57516 2014-12-23 05:44:20Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
* @brief Cache
*/
namespace phprs\util;
/**
*
* @author caoym
*/
class Cache extends CheckableCache
{
public function __construct()
{
if (!function_exists('apc_fetch') || !function_exists('apc_store') || !function_exists('apc_delete')) {
parent::__construct(new FileCache(), $this->tag);
}else{
parent::__construct(new ApcCache(), $this->tag);
}
}
/** @property */
private $tag = "";
}

View File

@@ -0,0 +1,102 @@
<?php
namespace phprs\util;
/**
* 可检查缓存是否失效的缓存
* @author caoym
*
*/
class CheckableCache
{
/**
* @param object $impl instanceof of KVCatchInterface
*/
function __construct($impl, $tag = ''){
$this->impl = $impl;
$this->tag = $tag;
}
/**
* 设置cache
*
* @param string $name
* @param mixed $var
* @param int
* @param SerializableFunc $expire_check
* @return boolean
* 缓存过期检查方法, 缓存过期(超过ttl)后, get时调用, 返回true表示缓存继续可用.
* 如checker($got_var, $time)
*
*/
public function set($name, $var, $ttl = 0, $expire_check = null)
{
$name = $this->tag.$name;
$res = $this->impl->set($name, array(
$var,
$ttl,
$expire_check,
time(),
), is_null($expire_check) ? $ttl : 0);
if (!$res){
Logger::warning("set cache $name failed");
}else{
Logger::debug("set cache $name ok, ttl=$ttl, check=".($expire_check===null?'null':get_class($expire_check)));
}
return $res;
}
/**
* 获取cache
* @param string $name
* @param boolean $succeeded
* @return mixed
*/
public function get($name, &$succeeded=null)
{
$name = $this->tag.$name;
$res = $this->impl->get($name, $succeeded);
if ($succeeded) {
$succeeded = false;
list ($data, $ttl, $checker, $create_time) = $res;
// 如果指定了checker, ttl代表每次检查的间隔时间, 0表示每次get都需要经过checker检查
// 如果没有指定checker, ttl表示缓存过期时间, 为0表示永不过期
if ($checker !== null) {
if ($ttl == 0 || ($create_time + $ttl < time())) {
$valid = false;
try{
if(is_callable($checker)){
$valid = $checker($data, $create_time);
}
}
catch (\Exception $e){
Logger::warning('call checker failed with '.$e->getTraceAsString());
$valid = false;
}
if(!$valid){
Logger::debug("cache $name expired by checker");
$this->impl->del($name);
return null;
}
}
}else if ($ttl != 0 && ($create_time + $ttl < time())) {
Logger::debug("cache $name expired by ttl");
$this->impl->del($name);
return null;
}
Logger::debug("get $name from cache, ttl=$ttl, create_time=$create_time, check=".($checker===null?'null':get_class($checker)));
$succeeded = true;
return $data;
}
return null;
}
/**
* 删除
* @param string $name
*/
public function del($name){
$name = $this->tag.$name;
return $this->impl->del($name);
}
private $tag;
private $impl;
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* $Id: ClassLoader.php 58839 2015-01-17 16:18:55Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs\util;
/**
* class loader
* @author caoym
*
*/
class ClassLoader{
/**
* @param unknown $path
*/
static public function addInclude($path){
if (is_array($path)){
self::$includes = array_unique(array_merge(self::$includes, $path));
}else{
self::$includes[] = $path;
self::$includes = array_unique(self::$includes);
}
}
/**
*
* @param array $map
* @param boolean $replace
*/
static public function addClassMap($map, $replace=false){
if($replace){
self::$class_map = array_merge(self::$class_map, $map);
}else{
self::$class_map = array_merge($map, self::$class_map);
}
}
/**
* autoLoad
* @param unknown $classname
* @return void
*/
static public function autoLoad($classname){
if(array_key_exists($classname, self::$class_map)){
$path = self::$class_map[$classname];
require_once $path;
}
foreach(self::$includes as $path) {
$path = $path . '/' . str_replace('\\', '/', $classname) . '.php';
if (file_exists($path)) {
self::$class_map[$classname] = $path;
require_once $path;
break;
}
}
}
static public $class_map=array();
static public $includes=array();
}

View File

@@ -0,0 +1,141 @@
<?php
namespace phprs\util;
class CurlResponse{
public $headers=array();
public $content;
public $http_code;
public $errno;
public $errstr;
public $status;
public $content_type;
public function isOk(){
return $this->errno==0 && intval($this->http_code)>=200 && intval($this->http_code)<300;
}
public function message(){
return "errno:{$this->errno}, errstr:{$this->errstr}, http_code:{$this->http_code}, content:".print_r($this->content,true);
}
public function handleHeader($ch, $header_line){
if(count($this->headers) ==0){
$this->status = trim(explode(' ', $header_line,2)[1]);
}
list($n,$v) = explode(':', $header_line)+array(null,null);
if(strcasecmp(trim($n),'Content-Type')===0){
$this->content_type = trim($v);
}
$this->headers[]=$header_line;
return strlen($header_line);
}
/**
* 从http返回的数据中分离header和body
* @param string $data
*/
public function parseReturnData($data){
while(true){
$lines = explode("\n", $data, 2);
if(!$lines || count($lines) == 0){
break;
}
if(trim($lines[0]) == ''){//空行,header 结束
if (stristr($this->content_type, 'application/Json') !== false) {
$this->content = json_decode($lines[1], true);
} else {
$this->content = $lines[1];
}
break;
}
$this->handleHeader(null, $lines[0]);
if(count($lines) !=2){
break;
}
$data = $lines[1];
}
}
}
class Curl
{
public function __construct(){
$this->ch = curl_init();
if(!file_exists('/dev/null')){
curl_setopt($this->ch, CURLOPT_COOKIEJAR, 'NUL');
}else{
curl_setopt($this->ch, CURLOPT_COOKIEJAR, '/dev/null');
}
}
public function reset(){
curl_close($this->ch);
$this->ch = curl_init();
if(!file_exists('/dev/null')){
curl_setopt($this->ch, CURLOPT_COOKIEJAR, 'NUL');
}else{
curl_setopt($this->ch, CURLOPT_COOKIEJAR, '/dev/null');
}
}
public function __destruct(){
curl_close($this->ch);
}
public function GET($url, $headers=null,$followLoc=true){
return $this->execCurl($url, __FUNCTION__, null, $headers,$followLoc);
}
public function POST($url, $content, $headers=null,$followLoc=true){
return $this->execCurl($url, __FUNCTION__, $content, $headers,$followLoc);
}
public function PUT($url, $content, $headers=null,$followLoc=true){
return $this->execCurl($url, __FUNCTION__, $content, $headers,$followLoc);
}
private function execCurl($url, $method='GET', $content=null, $headers=null,$followLoc=true){
$res = new CurlResponse();
if(isset($method)){
curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
}
curl_setopt($this->ch, CURLOPT_URL,$url);
curl_setopt($this->ch, CURLOPT_TIMEOUT, 60);
if(!empty($headers)){
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($this->ch,CURLOPT_FOLLOWLOCATION, $followLoc?1:0);//支持跳转
//curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, array($res,'handleHeader')); // handle received headers
curl_setopt($this->ch, CURLOPT_HEADER, true);
if(!empty($content)){
$content_type = '';
// 取content-type
foreach ($headers as $h){
list($n,$v) = explode(':',$h)+array(null,null);
if(strcasecmp(trim($n),'Content-Type')===0){
$content_type = trim($v);
break;
}
}
if(is_array($content) && $content_type == 'application/json'){
curl_setopt($this->ch, CURLOPT_POSTFIELDS, json_encode($content));
}else if($content_type == 'multipart/form-data'){
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $content);
}else{
if(is_array($content) ){
curl_setopt($this->ch, CURLOPT_POSTFIELDS, http_build_query($content));
}else{
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $content);
}
}
}
$response = curl_exec($this->ch);
$res->http_code = curl_getinfo($this->ch,CURLINFO_HTTP_CODE);
$res->errno = curl_errno($this->ch);
$res->errstr = curl_error($this->ch);
$res->parseReturnData($response);
return $res;
}
/** curl handle */
private $ch;
}

View File

@@ -0,0 +1,605 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace phprs\util;
use ReflectionClass;
use Doctrine\Common\Annotations\DocLexer;
use Doctrine\Common\Annotations\AnnotationException;
/**
* A parser for docblock annotations.
*
* It is strongly discouraged to change the default annotation parsing process.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
final class DocParser
{
/**
* An array of all valid tokens for a class name.
*
* @var array
*/
private static $classIdentifiers = array(
DocLexer::T_IDENTIFIER,
DocLexer::T_TRUE,
DocLexer::T_FALSE,
DocLexer::T_NULL
);
/**
* The lexer.
*
* @var \Doctrine\Common\Annotations\DocLexer
*/
private $lexer;
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names must be the raw names as used in the class, not the fully qualified
* class names.
*
* @var array
*/
private $ignoredAnnotationNames = array();
/**
* @var string
*/
private $context = '';
/**
* Hash-map for handle types declaration.
*
* @var array
*/
private static $typeMap = array(
'float' => 'double',
'bool' => 'boolean',
// allow uppercase Boolean in honor of George Boole
'Boolean' => 'boolean',
'int' => 'integer',
);
/**
* Constructs a new DocParser.
*/
public function __construct()
{
$this->lexer = new DocLexer;
}
/**
* Sets the annotation names that are ignored during the parsing process.
*
* The names are supposed to be the raw names as used in the class, not the
* fully qualified class names.
*
* @param array $names
*
* @return void
*/
public function setIgnoredAnnotationNames(array $names)
{
$this->ignoredAnnotationNames = $names;
}
/**
* Parses the given docblock string for annotations.
*
* @param string $input The docblock string to parse.
* @param string $context The parsing context.
*
* @return array Array of annotations. If no annotations are found, an empty array is returned.
*/
public function parse($input, $context = '', $record_doc)
{
$pos = $this->findInitialTokenPosition($input);
if ($pos === null) {
return array();
}
$this->context = $context;
$this->lexer->setInput(trim(substr($input, $pos), '* /'));
$this->lexer->moveNext();
return $this->Annotations($record_doc);
}
/**
* Finds the first valid annotation
*
* @param string $input The docblock string to parse
*
* @return int|null
*/
private function findInitialTokenPosition($input)
{
$pos = 0;
// search for first valid annotation
while (($pos = strpos($input, '@', $pos)) !== false) {
// if the @ is preceded by a space or * it is valid
if ($pos === 0 || $input[$pos - 1] === ' ' || $input[$pos - 1] === '*') {
return $pos;
}
$pos++;
}
return null;
}
/**
* Attempts to match the given token with the current lookahead token.
* If they match, updates the lookahead token; otherwise raises a syntax error.
*
* @param integer $token Type of token.
*
* @return boolean True if tokens match; false otherwise.
*/
private function match($token)
{
if ( ! $this->lexer->isNextToken($token) ) {
$this->syntaxError($this->lexer->getLiteral($token));
}
return $this->lexer->moveNext();
}
/**
* Attempts to match the current lookahead token with any of the given tokens.
*
* If any of them matches, this method updates the lookahead token; otherwise
* a syntax error is raised.
*
* @param array $tokens
*
* @return boolean
*/
private function matchAny(array $tokens)
{
if ( ! $this->lexer->isNextTokenAny($tokens)) {
$this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens)));
}
return $this->lexer->moveNext();
}
/**
* Generates a new syntax error.
*
* @param string $expected Expected string.
* @param array|null $token Optional token.
*
* @return void
*
* @throws AnnotationException
*/
private function syntaxError($expected, $token = null)
{
if ($token === null) {
$token = $this->lexer->lookahead;
}
$message = sprintf('Expected %s, got ', $expected);
$message .= ($this->lexer->lookahead === null)
? 'end of string'
: sprintf("'%s' at position %s", $token['value'], $token['position']);
if (strlen($this->context)) {
$message .= ' in ' . $this->context;
}
$message .= '.';
throw AnnotationException::syntaxError($message);
}
/**
* Annotations ::= Annotation {[ "*" ]* [Annotation]}*
* @param $record_doc 记录文档
* @return array
*/
private function Annotations($record_doc = false)
{
$annotations = array();
$doc_begin = 0;
while (null !== $this->lexer->lookahead) {
if (DocLexer::T_AT !== $this->lexer->lookahead['type']) {
$this->lexer->moveNext();
continue;
}
// make sure the @ is preceded by non-catchable pattern
if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
$this->lexer->moveNext();
continue;
}
// make sure the @ is followed by either a namespace separator, or
// an identifier token
if ((null === $peek = $this->lexer->glimpse())
|| (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
|| $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
$this->lexer->moveNext();
continue;
}
$doc_end = $this->lexer->lookahead['position'];
$this->match(DocLexer::T_AT);
$name = $this->Identifier();
$value = $this->MethodCall();
if($record_doc){
if(($count= count($annotations)) !==0){
$doc= $this->lexer->getInputUntilPosition($doc_end);
$text = substr($doc, $doc_begin);
$annotations[$count-1][1]['doc'] = $text;
$text = strstr($text, ')');
$annotations[$count-1][1]['desc'] = substr($text, 1);
}
$doc_begin = $doc_end;
}
$annotations[] = array(
$name,
$value
);
}
if ($record_doc) {
if (($count = count($annotations)) !== 0) {
$doc = $this->lexer->getInputUntilPosition(0xFFFF);
$text = substr($doc, $doc_begin);
$annotations[$count-1][1]['doc'] = $text;
$text = strstr($text, ')');
$annotations[$count-1][1]['desc'] = substr($text, 1);
}
}
return $annotations;
}
/**
* MethodCall ::= ["(" [Values] ")"]
*
* @return array
*/
private function MethodCall()
{
$values = array();
if ( ! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
return $values;
}
$this->match(DocLexer::T_OPEN_PARENTHESIS);
if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
$values = $this->Values();
}
$this->match(DocLexer::T_CLOSE_PARENTHESIS);
return $values;
}
/**
* Values ::= Array | Value {"," Value}* [","]
*
* @return array
*/
private function Values()
{
$values = array($this->Value());
while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
$this->match(DocLexer::T_COMMA);
if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
break;
}
$token = $this->lexer->lookahead;
$value = $this->Value();
if ( ! is_object($value) && ! is_array($value)) {
$this->syntaxError('Value', $token);
}
$values[] = $value;
}
foreach ($values as $k => $value) {
if (is_object($value) && $value instanceof \stdClass) {
$values[$value->name] = $value->value;
} else if ( ! isset($values['value'])){
$values['value'] = $value;
} else {
if ( ! is_array($values['value'])) {
$values['value'] = array($values['value']);
}
$values['value'][] = $value;
}
unset($values[$k]);
}
return $values;
}
/**
* Constant ::= integer | string | float | boolean
*
* @return mixed
*
* @throws AnnotationException
*/
private function Constant()
{
$identifier = $this->Identifier();
if ( ! defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) {
list($className, $const) = explode('::', $identifier);
$alias = (false === $pos = strpos($className, '\\')) ? $className : substr($className, 0, $pos);
$found = false;
switch (true) {
case !empty ($this->namespaces):
foreach ($this->namespaces as $ns) {
if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
$className = $ns.'\\'.$className;
$found = true;
break;
}
}
break;
case isset($this->imports[$loweredAlias = strtolower($alias)]):
$found = true;
$className = (false !== $pos)
? $this->imports[$loweredAlias] . substr($className, $pos)
: $this->imports[$loweredAlias];
break;
default:
if(isset($this->imports['__NAMESPACE__'])) {
$ns = $this->imports['__NAMESPACE__'];
if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
$className = $ns.'\\'.$className;
$found = true;
}
}
break;
}
if ($found) {
$identifier = $className . '::' . $const;
}
}
// checks if identifier ends with ::class, \strlen('::class') === 7
$classPos = stripos($identifier, '::class');
if ($classPos === strlen($identifier) - 7) {
return substr($identifier, 0, $classPos);
}
if (!defined($identifier)) {
throw AnnotationException::semanticalErrorConstants($identifier, $this->context);
}
return constant($identifier);
}
/**
* Identifier ::= string
*
* @return string
*/
private function Identifier()
{
// check if we have an annotation
if ( ! $this->lexer->isNextTokenAny(self::$classIdentifiers)) {
$this->syntaxError('namespace separator or identifier');
}
$this->lexer->moveNext();
$className = $this->lexer->token['value'];
while ($this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value']))
&& $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) {
$this->match(DocLexer::T_NAMESPACE_SEPARATOR);
$this->matchAny(self::$classIdentifiers);
$className .= '\\' . $this->lexer->token['value'];
}
return $className;
}
/**
* Value ::= PlainValue | FieldAssignment
*
* @return mixed
*/
private function Value()
{
$peek = $this->lexer->glimpse();
if (DocLexer::T_EQUALS === $peek['type']) {
return $this->FieldAssignment();
}
return $this->PlainValue();
}
/**
* PlainValue ::= integer | string | float | boolean | Array | Annotation
*
* @return mixed
*/
private function PlainValue()
{
if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
return $this->Arrayx();
}
if ($this->lexer->isNextToken(DocLexer::T_AT)) {
return $this->Annotation();
}
if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
return $this->Constant();
}
switch ($this->lexer->lookahead['type']) {
case DocLexer::T_STRING:
$this->match(DocLexer::T_STRING);
return $this->lexer->token['value'];
case DocLexer::T_INTEGER:
$this->match(DocLexer::T_INTEGER);
return (int)$this->lexer->token['value'];
case DocLexer::T_FLOAT:
$this->match(DocLexer::T_FLOAT);
return (float)$this->lexer->token['value'];
case DocLexer::T_TRUE:
$this->match(DocLexer::T_TRUE);
return true;
case DocLexer::T_FALSE:
$this->match(DocLexer::T_FALSE);
return false;
case DocLexer::T_NULL:
$this->match(DocLexer::T_NULL);
return null;
default:
$this->syntaxError('PlainValue');
}
}
/**
* FieldAssignment ::= FieldName "=" PlainValue
* FieldName ::= identifier
*
* @return array
*/
private function FieldAssignment()
{
$this->match(DocLexer::T_IDENTIFIER);
$fieldName = $this->lexer->token['value'];
$this->match(DocLexer::T_EQUALS);
$item = new \stdClass();
$item->name = $fieldName;
$item->value = $this->PlainValue();
return $item;
}
/**
* Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}"
*
* @return array
*/
private function Arrayx()
{
$array = $values = array();
$this->match(DocLexer::T_OPEN_CURLY_BRACES);
// If the array is empty, stop parsing and return.
if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
$this->match(DocLexer::T_CLOSE_CURLY_BRACES);
return $array;
}
$values[] = $this->ArrayEntry();
while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
$this->match(DocLexer::T_COMMA);
// optional trailing comma
if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
break;
}
$values[] = $this->ArrayEntry();
}
$this->match(DocLexer::T_CLOSE_CURLY_BRACES);
foreach ($values as $value) {
list ($key, $val) = $value;
if ($key !== null) {
$array[$key] = $val;
} else {
$array[] = $val;
}
}
return $array;
}
/**
* ArrayEntry ::= Value | KeyValuePair
* KeyValuePair ::= Key ("=" | ":") PlainValue | Constant
* Key ::= string | integer | Constant
*
* @return array
*/
private function ArrayEntry()
{
$peek = $this->lexer->glimpse();
if (DocLexer::T_EQUALS === $peek['type']
|| DocLexer::T_COLON === $peek['type']) {
if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
$key = $this->Constant();
} else {
$this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));
$key = $this->lexer->token['value'];
}
$this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON));
return array($key, $this->PlainValue());
}
return array(null, $this->Value());
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* $Id: FileCache.php 57516 2014-12-23 05:44:20Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
*/
namespace phprs\util;
/**
* 基于文件实现的缓存, 类似APC
* @author caoym
*/
class FileCache implements KVCatchInterface
{
/**
* @param string cache_dir 缓存保存的路径, 为空则使用sys_get_temp_dir取得的路径
*/
public function __construct($cache_dir=null) {
if($cache_dir===null){
$this->cache_dir = sys_get_temp_dir().'/caoym_temp';
}else{
$this->cache_dir = $cache_dir;
}
}
/**
* 设置key
* @param string $key
* @param mixed $var
* @param int $ttl TODO: 暂不支持
* @return void
*/
public function set($key, $var, $ttl=0){
Verify::isTrue(is_dir($this->cache_dir) || @mkdir($this->cache_dir, 0777, true));
$path = $this->cache_dir.'/'.sha1($key);
return SaftyFileWriter::write($path, serialize($var));
}
/**
* 删除key
* @param string $key
* @return boolean Returns true on success or false on failure.
*/
public function del($key){
$path = $this->cache_dir.'/'.sha1($key);
return @unlink($path);
}
/**
* 模拟apc, 只在没有apc的开发环境使用
* @param string $key
* @param boolean $succeeded
* @return mixed object on success or false on failure
*/
public function get($key, &$succeeded=null){
$succeeded = false;
$path = $this->cache_dir.'/'.sha1($key);
if(!file_exists($path)){
return false;
}
$res = file_get_contents($path);
if($res === false){
return false;
}
$succeeded = true;
return unserialize($res);
}
private $cache_dir;
}

View File

@@ -0,0 +1,56 @@
<?php
namespace phprs\util;
/**
* 检查文件是否过期
* @author caoym
*/
class FileExpiredChecker
{
/**
* @param string|array $file_name 文件的绝对路径
*/
function __construct($file_name){
$file_names = array();
if(is_string($file_name)){
$file_names[]=$file_name;
}else{
Verify::isTrue(is_array($file_name));
$file_names = $file_name;
}
foreach ($file_names as $file_name){
if(is_file($file_name)){
$this->file_name[$file_name] = @filemtime($file_name);
}else {
$this->file_name[$file_name] = @filemtime($file_name);
if(!is_dir($file_name)){
continue;
}
$files = @dir($file_name);
Verify::isTrue($files!== null, "open dir $file_name failed");
while (!!($file = $files->read())){
if($file == '.' || $file == '..') {continue;}
$this->file_name[$file_name.'/'.$file] = @filemtime($file_name.'/'.$file);
}
$files->close();
}
}
}
/**
* 判断是否过期
* @param mixed $data 从缓存中得到的数据
* @return boolean
*/
public function __invoke($data, $create_time){
$res = false;
foreach ($this->file_name as $name => $time){
if(@filemtime($name) !== $time){
Logger::info("cache expired by file $name modified");
return false;
}
$res = true;
}
return $res;
}
private $file_name=array(); //文件全路径
}

View File

@@ -0,0 +1,76 @@
<?php
namespace phprs\util;
class FileOp
{
/**
* 复制文件或目录
* @param string $src
* @param string $dst
* @param string $except
* @return boolean
*/
static public function copys($src, $dst, $except=null) {
if (is_file($src)) {
if ($except !== null && (realpath($except) == realpath($src) || realpath($except) == realpath($dst))) {
return true;
}
return @copy($src, $dst);
}
Verify::isTrue(is_dir($src), "$src not a file nor a dir");
$dir = dir($src);
@mkdir($dst);
while(false !== ( $file = $dir->read()) ) {
if (( $file != '.' ) && ( $file != '..' )) {
$src_path = $src . '/' . $file;
$dst_path = $dst . '/' . $file;
if(!self::copys($src_path, $dst_path)){
$dir->close();
return false;
}
Logger::debug("file copy from $src_path to $dst_path");
}
}
$dir->close();
return true;
}
/**
* mkdir -p
* @param string $path
* @param number $mode
* @return boolean
*/
static public function mkdirs($path, $mode = 0777) {
$dirs = explode('/', $path);
$count = count($dirs);
$path = '';
for ($i = 0; $i < $count; ++ $i) {
if ($i !== 0) {
$path .= DIRECTORY_SEPARATOR;
}
if ($dirs[$i] === '') {
continue;
}
$path .= $dirs[$i];
if (! is_dir($path) && ! mkdir($path, $mode)) {
return false;
}
}
return true;
}
/**
* 临时目录
* @param string $dir
* @param string $prefix
* @return string
*/
function tempdir($dir, $prefix) {
$tempfile=tempnam($dir, $prefix);
if (file_exists($tempfile)) {
unlink($tempfile);
}
mkdir($tempfile);
return $tempfile;
}
}

View File

@@ -0,0 +1,256 @@
<?php
/**
* $Id: HttpRouterEntries.php 58820 2015-01-16 16:29:33Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief HttpRouterEntries
*/
namespace phprs\util;
/**
* 记录路由信息
* @author caoym
* 针对 path?arg1=x&argy=y的形式url进行路由
* 搜索匹配的规则时,如果有多条规则匹配,则返回路径最深的(或者querystring最多的)规则
* 比如规则
* "/api" => service1,
* "/api?arg1=x" => service2,
* 查询"/api?arg1=x&arg2=y" 找到service2
*/
class HttpRouterEntries{
function __construct(){
$this->routes = new Tree();
}
/**
*
* @param string $q
* @param string& $matched_path
* @return found object
*/
function find($q,&$matched_path=null){
list($path,$param) = explode('?', $q)+array( null,null );
$paths = self::stringToPath($path);
$params = is_null($param)?null:explode('&', $param);
return $this->findByArray($paths, $params, $matched_path);
}
/**
* 查找url对应的实体
* @param string $paths
* @param string $params
* @param array $matched_path 已匹配的路径
* @return mixed|null
*/
function findByArray($paths,$params, &$matched_path =null){
$paths = array_filter($paths,function ($i){return $i!== '/' &&!empty($i);});
array_unshift($paths, '/');
$paths[] = Tree::$end; //最后总是插入Tree::$end表示路径结束
$route=null;
$valid_path=null;//最深一个不为空的路径
$visited = 0;
$geted=0;
$walked_path=array();
//找到匹配的最深路径,/a/b/c匹配的路径可以是/,/a,/a/c,/a/b/c,但最深的是/a/b/c
$this->routes->visit($paths,function ($node,$value)use(&$valid_path,&$route,&$visited,&$geted,&$walked_path){
if(!is_null($value)) {
$route=$value;
if(!($route instanceof \phprs\util\Tree)) {
$valid_path=$value;
}
$geted = $visited;
}
$walked_path[]=$node;
$visited ++;
return true;
});
if(is_null($route)){
return null;
}
if($matched_path !==null){
$matched_path = $walked_path;
}
//如果匹配完整的路径, 则还需要匹配querystring参数
if(count($paths) == $geted+1){
if($route instanceof \phprs\util\Tree){
//条件要求有querystring所以请求也需要有querystring
if(empty($params)){
return $valid_path;
}
$found = $this->findParams($route, $params);
return is_null($found)?$valid_path:$found ;
}else{
return $route;
}
}else{//如果不匹配全路径则只能处理没有querystring的条件
if($route instanceof \phprs\util\Tree) {
return $valid_path;
}
return $route;
}
return null;
}
/**
* 查找querystring对应的实体
* querystring 参数匹配条件在tree中的存储方式如下
*
* 1.c=xxx&f=xxx&g=xxx
* 2.c=xxx&e=xxx&f-xxx
* 3.b=xxx&c=xxx
* (经过排序的数据结构)
* c=xxx -> f=xxx -> g=xxx
* \->e=xxx->f=xxx
* b=xxx ->d=xxx
* 当收到请求 b=xxx&d=xxx&a=xxx&c=xxx时
* 先拆成数组再排序=> a=xxx,b=xxx,c=xxx,d=xxx
* 先找 a=xxx根据当前的数据结构只可能是有一个或者没有不可能存在多个没找到则跳过
* 找b=xxx则在b=xxx节点下找c=xxx没找到则跳过
* 在b=xxx节点下找d=xxx,找到,且 b=xxx ->d=xxx是完整的规则、请求的params也已处理完则命中此规则
* @param Tree $root 根节点
* @param $params 需要查找的节点名
* @return mixed|null
*/
private function findParams($root,$params){
//排序,使参数与顺序无关
sort($params,SORT_STRING);
$params = array_filter($params,function ($i){return !empty($i);});
$find_step = array();
$matching = null;
foreach ($params as $param){
$find_step[]=$param;
$node = $root->findNode($find_step);
if($node === null) {
array_pop($find_step);//换一个接着找
}else{
if(array_key_exists('value', $node)){
$matching = $node['value'];
}
}
}
return $matching;
}
/**
* 增加一条规则
* @param $query string 请求的url形式
* @param $e mixed
* @param $strict boolean 是否严格匹配
* @returnboolean 是否插入成功
*/
public function insert($query,$e,$strict=false){
list($path, $param) = explode('?', $query)+array( null, null );
$path = str_replace('\\', '/', $path);
$paths = explode('/', $path);
$route=null;
$params = null;
if($param !==null && $param !== ""){
$params = explode('&', $param);
}
if($strict || !empty($params)){
$paths[]=Tree::$end; //对于严格匹配的路由规则有querystring的路由path部分也需要严格匹配在路径末尾加入end偏于匹配
}
return $this->insertByArray($paths,$params,$e);
}
/**
* 增加一条路由规则
* @param array $paths
* @param array $params
* @param mixed $e
* @return boolean
*/
public function insertByArray($paths,$params,$e){
//生成树形的路由表,便于快速查找,
// 如 规则
// "/b/a?z=1&y=2" => service1
// "/b/a?z=1&x=2" => service2
// 对应的 规则结构是
// path -> a+
// |-b+
// param -> |-x+
// |-z => service2
// |-y+
// |-z => service1
//
//
//
$paths = array_filter($paths, function ($i){return !empty($i);});
array_unshift($paths, '/');
if(empty($paths)){
$paths = array('');
}
if( $params !== null){
//排序,使参数与顺序无关
sort($params, SORT_STRING);
$params = array_filter($params,function ($i){return !empty($i);});
$node = $this->routes->findNode($paths, true, true);
if($node && $node['value'] instanceof \phprs\util\Tree){
$route = $node['value'];
return $route->insert($params, $e,false);
}else{
$route = new Tree();
$route->insert($params, $e,false);
return $this->routes->insert($paths, $route,false);
}
}else{
return $this->routes->insert($paths, $e ,false);
}
}
/**
* 树打平输出成数组
* @return array
*/
public function export(){
$ori = $this->routes->export();
$res = array();
foreach ($ori as $v){
list($path, $value) = $v;
if($value instanceof \phprs\util\Tree){
$querys = $value->export(); //提取querystring
$path_str = self::pathToString($path);
foreach ($querys as $query){
list($param, $value) = $query;
$res[]=array($path_str.'?'.implode('&',$param), $value);
}
}else{
$res[]=array(self::pathToString($path), $value);
}
}
return $res;
}
/**
* 字符串路转径数组路
* @param string $path
* @return array
*/
static public function stringToPath($path){
$path = str_replace('\\', '/', $path);
$paths = explode('/', $path);
$paths = array_filter($paths,function ($i){return !empty($i);});
return array_values($paths);
}
/**
* 数组路径转字符串路径
* @param array $path
* @return string 用/连接的字符串
*/
static public function pathToString($path){
$append=function (&$str, $v){
//连续的/忽略
if(strlen($str) !==0 && substr_compare($str, '/', strlen($str)-1) ===0 && $v==='/'){
return;
}
//两个项目间加/
if(strlen($str) !==0 && substr_compare($str, '/', strlen($str)-1) !==0 && $v!=='/'){
$str =$str.'/'.$v;
}else{
$str.=$v;
}
};
$str = '';
foreach ($path as $i){
$append($str, $i);
}
return $str;
}
private $routes;
}

View File

@@ -0,0 +1,447 @@
<?php
/**
* $Id: IoCFactory.php 64919 2015-06-08 05:48:03Z caoyangmin $
* TODO: Reflection有性能问题
* @author caoyangmin
* @brief
* IoCFactory
*/
namespace phprs\util;
/**
* 依赖注入工厂
* 创建实例, 并根据配置注入依赖
* @author caoym
*
*/
class IoCFactory
{
/**
* @param string|array $conf 文件或者配置数组
* 配置数组格式如下:
* [
* id=>[
* "class"=>类名,
* "singleton"=>false, 是否是单例, 如果是, 则只会创建一份(同一个工厂内)
* "pass_by_construct"=false, 属性是否通过构造函数传递, 如果不是, 则通过访问属性的方式传递
* "properties"=>{
* 属性名=>属性值
* }
* @param array $dict 设置字典
* 配置文件中, 属性值可以使用{key}的方式指定模板变量, 注入属性到实例是这些模板变
* 量会被替换成setDict方法设置的值
* @param array $metas 类元信息, 如果不指定, 则自动从类文件中导出
* ]
*/
public function __construct($conf=null, $dict=null, $metas=null)
{
if($conf === null){
$this->conf = array();
}elseif(is_array($conf)){
$this->conf = $conf;
}else{
Verify::isTrue(is_file($conf), "$conf is not a valid file");
if(strtolower(pathinfo($conf, PATHINFO_EXTENSION)) == 'php'){
$this->conf = include($conf);
}else{
Verify::isTrue(false !== ($data = file_get_contents($conf)), "$conf open failed");
$data = self::clearAnnotation($data);
Verify::isTrue(is_array($this->conf = json_decode($data,true)), "$conf json_decode failed with ".json_last_error());
}
$this->conf_file = $conf;
}
if($dict !== null){
$this->conf = $this->replaceByDict($this->conf, $dict);
}
$this->metas = $metas;
}
/**
* 去掉注解
* @param unknown $text
*/
static public function clearAnnotation($text){
return AnnotationCleaner::clean($text);
}
/**
* 获取配置的属性
* @param string $id
* @return array|null
*/
public function getConf($id=null){
if($id === null){
return $this->conf;
}else{
if(isset($this->conf[$id]) && isset($this->conf[$id]['properties'])){
return $this->conf[$id]['properties'];
}
return null;
}
}
/**
*
* @param string $id
*/
public function getClassName($id=null){
if(isset($this->conf[$id])){
$class = $this->conf[$id];
if(isset($class['class'])){
return $class['class'];
}else{
return $id;
}
}else{
return $id;
}
}
/**
* 根据id得到对象实例
* //TODO 单实例间互相引用会是问题
* @param string $id
* @param array $properties 类属性, 覆盖配置文件中的属性
* @param callable $injector fun($src), 获取注入值的方法
* @param callable $init fun($inst, &$got) 初始化实例, 在创建后, 调用构造函数前
* @param array $construct_args 构造函数的参数
* @return object
*/
public function create($id, $construct_args=null, $properties=null, $injector=null, $init=null )
{
if($properties === null){
$properties = array();
}
if($construct_args===null){
$construct_args = array();
}
$ins = null;
$is_singleton = false;
$class_name = $this->getClassName($id);
if(isset($this->conf[$id])){
$class = $this->conf[$id];
$class_refl = new \ReflectionClass($class_name);
// 如果是单例
// 带构造参数的类不支持单例
if (count($properties) ===0 && count($construct_args) === 0 && isset($class['singleton']) && $class['singleton']) {
$is_singleton = true;
if (isset($this->singletons[$id])) {
$ins = $this->singletons[$id];
Logger::info("get {$id}[$class_name] as singleton");
return $ins;
}
}
if(isset($class['properties'])){
$properties = array_merge($class['properties'], $properties);
}
if (isset($class['pass_by_construct']) && $class['pass_by_construct']){ //属性在构造时传入
Verify::isTrue(count($construct_args) ===0, "$class_name pass_by_construct"); //构造时传输属性, 所以不能再传入其他构造参数
//组合构造参数
$construct_args = $this->buildConstructArgs($class_refl, $properties);
$properties=array();
}
}else{
$class_refl = new \ReflectionClass($class_name);
}
if(!$is_singleton){
if (array_search($id, $this->create_stack) !== false){
$tmp = $this->create_stack;
Verify::e("create $id failed, cyclic dependencies can only used with singleton. cyclic:".print_r($tmp,true));
}
$this->create_stack[] = $id;
}
try {
if (isset($class['pass_by_construct']) && $class['pass_by_construct']){
$ins = $class_refl->newInstanceArgs($construct_args);
$meta = $this->getMetaInfo($class_refl);
if ($is_singleton){
$this->singletons[$id] = $ins;
}
$this->injectDependent($class_refl, $ins, $meta, $properties, $injector);
}else{
$nti = new NewThenInit($class_refl);
$ins = $nti->getObject();
$meta = $this->getMetaInfo($class_refl);
if ($is_singleton){
$this->singletons[$id] = $ins;
}
$this->injectDependent($class_refl, $ins, $meta, $properties, $injector);
if($init !==null){
$init($ins);
}
$nti->initArgs($construct_args);
}
} catch (\Exception $e) {
array_pop($this->create_stack);
throw $e;
}
array_pop($this->create_stack);
Logger::info("create {$id}[$class_name] ok");
return $ins;
}
/**
* 取配置文件路径, 如果是通过数组创建的, 则返回null
* @return string|NULL
*/
public function getConfFile(){
return $this->conf_file;
}
/**
* 配置中是否有指定id的类
* @param string $id
* @return boolean
*/
public function hasClass($id){
return isset($this->conf[$id]);
}
/**
* 设置属性值, 允许设置private/protected属性
* @param $refl
* @param object $class
* 类名或实例
* @param string $name
* 属性名
* @param mixed $value
* 属性值
*/
static function setPropertyValue($refl, $ins, $name, $value)
{
Verify::isTrue($m = $refl->getProperty($name));
$m->setAccessible(true);
$m->setValue($ins, $value);
}
/**
* 取属性值
* @param $refl
* @param object $ins
* @param string $name
* @return mixed
*/
static function getPropertyValue($refl, $ins, $name)
{
Verify::isTrue($m = $refl->getProperty($name));
$m->setAccessible(true);
return $m->getValue($ins);
}
/**
* 获取元信息
* 会缓存
* @param string $class
* @return array
*/
public function getMetaInfo($class){
if(is_string($class)){
$refl = new \ReflectionClass($class);
}else{
$refl = $class;
}
$name = $refl->getName();
if($this->metas !==null && isset($this->metas[$name])){
return $this->metas[$name];
}
static $cache = null;
if($cache === null){
$cache = new Cache();
}
$succeeded = false;
$cache_key = 'meta_'.sha1($refl->getFileName().'/'.$name);
$data = $cache->get($cache_key, $succeeded);
if($succeeded){
return $data;
}
MetaInfo::testAnnotation();
$data = MetaInfo::get($name);
$files = [$refl->getFileName()];
$parent = $refl->getParentClass();
if($parent){
$files[] = $parent->getFileName();
}
foreach ($refl->getInterfaces() as $i){
$files[] = $i->getFileName();
}
$cache->set($cache_key, $data, 60, new FileExpiredChecker($files));
return $data;
}
/**
* 根据属性组合构造参数
* @param ReflectionClass $class
* @param array $properties
* @return array
*/
private function buildConstructArgs($class, $properties){
if($properties===null) {
return array();
}
if(count($properties)==0){
return array();
}
$refMethod = $class->getConstructor();
$params = $refMethod->getParameters();
$args = array();
foreach ($params as $key => $param) {
$param_name = $param->getName();
if(isset($properties[$param_name])){
$args[$key] = $this->getProperty($properties[$param_name]);
}else{
Verify::isTrue($param->isOptional(), "{$class->getName()}::__construct miss required param: $param_name");//参数没有指定, 除非是可选参数
break;
}
}
return $args;
}
/**
* 获取属性
* 替换属性中的{}和@标记
* @param string $value
* @return object|string
*/
private function getProperty($value){
if (is_string($value) && substr($value, 0, 1) == '@') {
return $this->create(substr($value, 1));
} else {
return $value;
}
}
/**
* 注入依赖
* @param ReflectionClass $refl;
* @param unknown $ins
* @param unknown $meta
* @param unknown $properties
* @return void
*/
public function injectDependent($refl, $ins, $meta, $properties, $injector=null)
{
$defaults=array();
$class_name = $refl->getName();
$class_defaults = $refl->getDefaultProperties();
if(isset($meta['property']) ){
foreach ($meta['property'] as $property => $value) {
//参数是否可选
if(isset($value['value']) && isset($value['value']['optional']) && $value['value']['optional']){
continue;
}
//设置了默认值
if(isset($value['value']) && isset($value['value']['default'])){
$defaults[$property] = $value['value']['default'];
continue;
}
// 是否设置了默认值
if (array_key_exists($property, $class_defaults)) {
continue;
}
Verify::isTrue(array_key_exists($property, $properties), "$class_name::$property is required");
}
}
// 设置属性
if ($properties !== null) {
foreach ($properties as $name => $value) {
unset($defaults[$name]);
$v = $this->getProperty($value);
self::setPropertyValue($refl, $ins, $name, $v);
}
}
// 注入依赖
if(isset($meta['inject'])){
foreach ($meta['inject'] as $property => $value) {
//先设置必须的属性
if(is_array($value['value'])){
$src = $value['value']['src'];
//参数是否可选
if(isset($value['value']) && isset($value['value']['optional']) && $value['value']['optional']){
continue;
}
//设置了默认值
if(isset($value['value']) && isset($value['value']['default'])){
$defaults[$property] = $value['value']['default'];
continue;
}
}else{
$src = $value['value'];
}
//是否设置了默认值
if(array_key_exists($property, $class_defaults)){
continue;
}
if ($src === "ioc_factory" || $src == "factory"){
continue;
}else{
$got = false;
Verify::isTrue($injector !==null , "$class_name::$property is required");
$val = $injector($src, $got);
Verify::isTrue($got , "$class_name::$property is required");
self::setPropertyValue($refl, $ins, $property, $val);
unset($meta['inject'][$property]);
}
}
//在设置可选的
foreach ($meta['inject'] as $property => $value) {
if(is_array($value['value'])){
$src = $value['value']['src'];
}else{
$src = $value['value'];
}
if ( $src == "ioc_factory" || $src == "factory") {
self::setPropertyValue($refl, $ins, $property, $this);
unset($defaults[$property]);
}else if($injector){
$val = $injector($src, $got);
if($got){
self::setPropertyValue($refl, $ins, $property, $val);
unset($defaults[$property]);
}
}
}
}
// 设置默认属性
foreach ($defaults as $name => $value ){
unset($defaults[$name]);
$v = $this->getProperty($value);
self::setPropertyValue($refl, $ins, $name, $v);
}
}
/**
* 替换字典
* @see setDict
* @param string|array $value
* @return void
*/
private function replaceByDict($value, $dict){
if(is_string($value)){
$keys = $this->getDictKeys($value);
foreach ($keys as $key){
Verify::isTrue(isset($dict[$key]), "{$key} not specified");
}
foreach ($dict as $key=>$replace){
$value = str_replace('{'.$key.'}', $replace, $value);
}
return $value;
}else if(is_array($value)){
foreach ($value as $k=>$v){
$value[$k] = $this->replaceByDict($v, $dict);
}
return $value;
}else{
return $value;
}
}
/**
* 从字符串中获取模板变量
* @param string $value
* @return array
*/
static function getDictKeys($value){
preg_match_all('/\{([0-9a-zA-Z\-\_]*?)\}/', $value, $params);
$params += array(null, array());
return $params[1];
}
protected $metas;
protected $singletons = array();
protected $conf = null;
protected $dict = array();
protected $conf_file;
private $create_stack=array(); // 正在创建的类,用于检测循环依赖
}

View File

@@ -0,0 +1,172 @@
<?php
namespace phprs\util;
/**
* 加强版IoCFactory...
* 1. 创建的是指定类的容器实例,而不是类的实例
* 2. 在第一次调用时才创建类的实例
* 3. 支持@cache注释对类的接口进行缓存
* @author caoym
*
*/
class IoCFactoryEx extends IoCFactory
{
public function __construct($conf=null, $dict=null, $metas=null){
parent::__construct($conf, $dict, $metas);
}
/**
* 根据id创建对象(的容器)实例
* @param string $id
* @param array $properties 类属性, 覆盖配置文件中的属性
* @param callable $injector fun($src), 获取注入值的方法
* @param callable $init fun($inst, &$got) 初始化实例, 在创建后, 调用构造函数前
* @param array $construct_args 构造函数的参数
* @return object
*/
public function create($id, $construct_args=null, $properties=null, $injector=null, $init=null ){
$meta = $this->getMetaInfo($this->getClassName($id));
if(isset($meta['cache'])){
return new IoCObjectWrap($this, $id, $construct_args, $properties, $injector, $init);
}
return $this->createRawObject($id, $construct_args, $properties, $injector, $init);
}
/**
* 根据id创建对象(的容器)实例, 不使用容器
* @param string $id
* @param array $properties 类属性, 覆盖配置文件中的属性
* @param callable $injector fun($src), 获取注入值的方法
* @param callable $init fun($inst, &$got) 初始化实例, 在创建后, 调用构造函数前
* @param array $construct_args 构造函数的参数
* @return object
*/
public function createRawObject($id, $construct_args=null, $properties=null, $injector=null, $init=null ){
return parent::create($id, $construct_args, $properties, $injector, $init);
}
}
/**
* 容器
* @author caoym
*
*/
class IoCObjectWrap{
public function __construct($factory, $id, $construct_args, $properties, $injector, $init){
$this->__impl__ = $factory->createRawObject('phprs\\util\\IoCContainer', [
'id' => $id,
'construct_args' => $construct_args,
'properties' => $properties,
'injector' => $injector,
'init' => $init
]);
}
public function __get($name){
return $this->__impl__->getObj()->$name;
}
public function __set($name , $value ){
$this->__impl__->getObj()->$name = $value;
}
public function __isset ($name){
return isset($this->__impl__->getObj()->$name);
}
public function __unset ($name){
unset($this->__impl__->getObj()->$name);
}
public function __call($name, $arguments) {
$arguments[0]=111;
return;
return $this->__impl__->callThroughCache($name, $arguments);
}
/**
* @var IoCContainerImpl
*/
private $__impl__;
}
/**
*
* @author caoym
*/
class IoCContainer{
public function __construct($id, $construct_args, $properties, $injector, $init){
$this->id = $id;
$this->construct_args = $construct_args;
$this->properties = $properties;
$this->injector = $injector;
$this->init = $init;
}
public function getObj(){
if($this->obj==null){
$this->obj = $this->factory->createRawObject(
$this->id ,
$this->construct_args,
$this->properties,
$this->injector,
$this->init
);
}
return $this->obj;
}
/**
*
*/
public function callThroughCache($method, $arguments){
$op = $this->getCacheOptions($method);
if($op){
$got = false;
$key = $this->genKey($method, $arguments);
$res = $this->cache->get($key, $got);
if($got){
return $res;
}else{
/*static $methods = [];
$name = $this->factory->getClassName($this->id).'::'.$method;
if(!array_key_exists($name, $methods)){
$refl = new \ReflectionClass($this->factory->getClassName($this->id));
$methods[$name] = $refl->getMethod($method);
}
$res = $methods[$name]->invokeArgs($this->getObj(), $arguments);
*/
$res = call_user_func_array([$this->getObj(),$method], $arguments);
$this->cache->set($key, $res, $op['ttl'], isset($op['checker'])?$op['checker']:null);
return $res;
}
}else{
return call_user_func_array([$this->getObj(), $method], $arguments);
}
}
public function getCacheOptions($method){
if(!array_key_exists($method, $this->cachedMethods)){
$meta = $this->factory->getMetaInfo($this->factory->getClassName($this->id));
if(isset($meta['cache'][$method]['value'])){
$val = $meta['cache'][$method]['value'];
list($k, $v) = $val;
Verify::isTrue($k == 'ttl', "no TTL with @cache in $method");
$this->cachedMethods[$method][$k] = $v;
}
}
return $this->cachedMethods[$method];
}
private function genKey($method, $arguments){
return '_ioc_'.$this->id.$method.sha1(serialize($arguments));
}
private $obj;
private $id;
private $construct_args;
private $properties;
private $injector;
private $init;
private $cachedMethods=[];
/**
* @inject("factory")
* @var IoCFactoryEx
*/
private $factory;
/**
* @property
* @var CheckableCache
*/
private $cache;
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* $Id: KVCatchInterface.php 57516 2014-12-23 05:44:20Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
*/
namespace phprs\util;
interface KVCatchInterface
{
/**
* 设置key
* @param string $key
* @param mixed $var
* @param int $ttl
* @return boolean
*/
public function set($key, $var, $ttl);
/**
* 删除key
* @param string $key
* @return boolean
*/
public function del($key);
/**
* get key
* @param string $key
* @param boolean $succeeded
* @return mixed
*/
public function get($key, &$succeeded);
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* $Id: Logger.php 58820 2015-01-16 16:29:33Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs\util;
/**
* 简单的日志输出, 方便应用替换自己的日志实现
* @author caoym
*
*/
class Logger
{
static $flags = 12; // ERROR|WARNING
/**
* echo输出
*/
static $to_echo;
/**
* 忽略输出
*/
static $to_void;
/**
* trigger_error 输出
* @var callable
*/
static $to_php_log;
/**
* 输出到哪, 默认为Logger::$to_php_log
* @var callable
*/
static $writer;
const DEBUG=1;
const INFO=2;
const WARNING=4;
const ERROR=8;
/**
* debug log
* @param string $msg
* @return void
*/
public static function debug($msg){
if(self::$flags&self::DEBUG){
call_user_func(Logger::$writer, self::DEBUG, $msg);
}
}
/**
* info log
* @param string $msg
* @return void
*/
public static function info($msg){
if(self::$flags&self::INFO){
call_user_func(Logger::$writer, self::INFO, $msg);
}
}
/**
* warning log
* @param string $msg
* @return void
*/
public static function warning($msg){
if(self::$flags&self::WARNING){
call_user_func(Logger::$writer, self::WARNING, $msg);
}
}
/**
* error log
* @param string $msg
* @return void
*/
public static function error($msg){
if(self::$flags&self::ERROR){
call_user_func(Logger::$writer, self::ERROR, $msg);
}
}
/**
* init
* @return void
*/
public static function init(){
Logger::$to_echo = function ($level, $message){
$titles = array(
Logger::DEBUG => '==DEBUG==',
Logger::INFO => '==INFO==',
Logger::WARNING => '==WARNING==',
Logger::ERROR => '==ERROR==',
);
echo $titles[$level].' '.$message."<br>\n";
};
Logger::$to_php_log = function ($level, $message)
{
$titles = array(
Logger::DEBUG => E_USER_NOTICE,
Logger::INFO => E_USER_NOTICE,
Logger::WARNING => E_USER_WARNING,
Logger::ERROR => E_USER_ERROR,
);
$caller = debug_backtrace()[2];
trigger_error($message.' in '.$caller['file'].' on line '.$caller['line'].''."\n<br />", $titles[$level]);
};
Logger::$to_void = function ($level, $message){
};
if(Logger::$writer === null){
Logger::$writer = Logger::$to_php_log;
}
}
}
Logger::init();

View File

@@ -0,0 +1,219 @@
<?php
/**
* $Id: MessagePump.php 58821 2015-01-17 03:51:41Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
* @brief ResetOutput
*/
namespace phprs\util;
/**
* 处理消息循环
* 消息执行顺序为先入后出(栈)
* 消息被分成正常执行消息和空闲执行消息
* 空闲执行消息只有在没有正常执行消息时才会执行
* 允许同时存在多个队列,可以关闭某个队列,如果某个队列中所有消息完成,将触发end事件
*
* @author caoym
*/
class MessagePump
{
/**
* 创建一个新的消息队列
* @param callable $onEnd 当队列执行完后调用
* @return int 队列id
*/
public function newQueue(callable $onEnd = null)
{
Verify::isTrue(! isset($this->queues[$this->start_queue_id]));
$this->action_queues[$this->start_queue_id] = array();
$this->idle_queues[$this->start_queue_id] = array();
$this->end_handles[$this->start_queue_id] = $onEnd;
Logger::debug("[MQ {$this->start_queue_id}] created");
return $this->start_queue_id++;
}
/**
* 关闭一个消息队列,队列中未执行的操作将不会再执行
*
* @param int $queue_id
* @return void
*/
public function closeQueue($queue_id)
{
if (! isset($this->idle_queues[$queue_id])){
return;
}
Logger::debug("[MQ $queue_id] attempt to close");
// 队列末尾加入null,消息循环处理到null后任务队列结束
array_unshift($this->action_queues[$queue_id], null);
$this->next_action[]=$queue_id;
// $this->idle_queues[$queue_id][]=null;
}
/**
* 加入常规执行消息
*
* @param int $queue_id队列id
* @param callable $action 调用方法
* @param array $args 调用参数
* @param callable $exception_handle异常处理
* @param string $desc 描述信息
* @param boolean immediately 是否立即执行
* @return void
*/
public function pushAction($queue_id, $action, $args, $exception_handle, $desc, $immediately=false)
{
if (! isset($this->action_queues[$queue_id])) {
Logger::warning("unknown message queue $queue_id");
return;
}
$count=count($this->action_queues[$queue_id]);
if($count && $this->action_queues[$queue_id][0] ===null){
Logger::warning("[MQ $queue_id] try to add action to closed queue");
return;
}
$action = array(
$action,
$args,
$exception_handle,
$desc,
);
if($immediately){
Logger::debug("[MQ $queue_id] new action [$desc'] immediately");
$this->callAction($queue_id, $action);
}else{
Logger::debug("[MQ $queue_id] new action [$desc']");
$this->action_queues[$queue_id][] =$action;
$this->next_action[] = $queue_id;
}
}
/**
* 加入空闲执行消息
*
* @param callable $action
* @param callable $exception_handle
* @param string $desc
* 描述信息
* @return void
*/
public function pushIdle($queue_id, $action, $args, $exception_handle, $desc)
{
if (! isset($this->idle_queues[$queue_id])) {
Logger::warning("unknown message queue $queue_id");
return;
}
$this->idle_queues[$queue_id][] = array(
$action,
$args,
$exception_handle,
$desc,
);
$this->next_idle[] = $queue_id;
}
/**
* 运行消息循环
* @return void
*/
public function run()
{
if ($this->is_running) {
return;
}
Logger::debug("[MQ Pump] begin");
$this->is_running = true;
while (count($this->next_action) || count($this->next_idle)) {
//没有活动事件,执行idle事件
if(count($this->next_action) === 0){
$queue_id = array_pop($this->next_idle);
if (!isset($this->idle_queues[$queue_id])) { // 队列可能被关闭
continue;
}
$idle = array_pop($this->idle_queues[$queue_id]);
Verify::isTrue($idle !== null, 'never been here!!');
if ($idle[2] !== null) {
try {
call_user_func_array($idle[0], $idle[1]);
} catch (\Exception $e) {
$idle[2]($e);
}
} else {
call_user_func_array($idle[0],$idle[1]);
}
continue;
}
$queue_id = array_pop($this->next_action);
if (!isset($this->action_queues[$queue_id])) { // 队列可能被关闭
continue;
}
$actions = &$this->action_queues[$queue_id];
$left = count($actions);
$action = array_pop($actions);
if ($action !== null) {
$this->callAction($queue_id, $action);
} elseif ($left !== 0) {
//null插入队列,表示执行队列结束, 可以安全关闭队列了
$onend = $this->end_handles[$queue_id];
unset($this->action_queues[$queue_id]);
unset($this->idle_queues[$queue_id]);
unset($this->end_handles[$queue_id]);
Logger::debug("[MQ $queue_id] closed");
if ($onend !== null) {
$onend();
}
}
}
//不是在单个队列为空时将其关闭,因为对于存在子流程的时候,其消息队列可能为空,但
//其他流程执行可能导致子流程产生活动消息,所以不能在队列为空时就关闭队列
foreach ($this->end_handles as $onend){
if($onend !== null){
$onend();
}
}
$this->next_action = array();
$this->next_idle = array();
$this->action_queues = array();
$this->idle_queues = array();
$this->end_handles= array();
$this->is_running = false;
Logger::debug("[MQ Pump] end");
}
/**
*
* @param unknown $queue_id
* @param unknown $action
*/
private function callAction($queue_id, $action){
if ($action[3] !== null) {
Logger::debug("[MQ $queue_id]".$action[3]);
}
if ($action[2] !== null) {
try {
call_user_func_array($action[0],$action[1]);
} catch (\Exception $e) {
Logger::warning("[MQ $queue_id] exception: $e");
$action[2]($e);
}
} else {
call_user_func_array($action[0],$action[1]);
}
}
private $is_running = false;
private $start_queue_id = 0;
private $next_action = array(); // 保存下一个操作所在的队列
private $next_idle = array(); // 保存空闲时下一次执行操作所在的队列
private $action_queues = array(); // 执行队列
private $idle_queues = array(); // 空闲队列
private $end_handles = array();
}
?>

View File

@@ -0,0 +1,83 @@
<?php
namespace phprs\util;
/**
* @author caoym
*/
class AnnotationTest{
/**
* @return void
*/
public function test(){
}
}
/**
* 元信息
* 处理注释中的@annotation, 生成以@annotation为key的数组
* @author caoym
*/
class MetaInfo
{
/**
* 获取元信息
* @param object|class $inst
* @param boolean $record_doc 是否加载注释文本, 如果是
* @param array $select, 只取选中的几个
* @return array
*/
static function get($inst, $record_doc=false, $select=null){
$reflection = new \ReflectionClass($inst);
$reader= new AnnotationReader($reflection);
$info = array();
if($record_doc){
if(false !== ($doc = $reflection->getDocComment())){
$info['doc'] = $doc;
}
}
if($select !== null){
$select = array_flip($select);
}
foreach ($reader->getClassAnnotations($reflection, $record_doc) as $id =>$ann ){
if($select !==null && !array_key_exists($id, $select)){
continue;
}
$ann=$ann[0];//可能有多个重名的, 只取第一个
$info[$id] = $ann;
}
foreach ($reflection->getMethods() as $method ){
foreach ( $reader->getMethodAnnotations($method, $record_doc) as $id => $ann){
if($select !==null && !array_key_exists($id, $select)){
continue;
}
$ann=$ann[0];//可能有多个重名的, 只取第一个
$info += array($id=>array());
$info[$id][$method->getName()] = $ann;
}
}
foreach ($reflection->getProperties() as $property ){
foreach ( $reader->getPropertyAnnotations($property, $record_doc) as $id => $ann){
if($select !==null && !array_key_exists($id, $select)){
continue;
}
$ann = $ann[0];//可能有多个重名的, 只取第一个
$info += array($id=>array());
$info[$id][$property->getName()] = $ann;
}
}
return $info;
}
static function testAnnotation(){
Verify::isTrue(count(self::get(new AnnotationTest(),true)), 'Annotation dose not work! If opcache is enable, please set opcache.save_comments=1 and opcache.load_comments=1');
}
/**
* 有效的元信息
* @var unknown
*/
private static $valid=array();
}
?>

View File

@@ -0,0 +1,108 @@
<?php
/**$Id$*/
namespace phprs\util;
/**
* 剪出字符串中的嵌套字符串
* 既从aaa"bb\"b"ccc中, 取出"bb\"b"
* @author caoym
*/
class NestedStringCut{
public function __construct($str){
$pos = 0;
$state = 'stateNormal';
while (true){
$pos = $this->$state($str, $pos, $state);
if($pos === false){
break;
}
};
return false;
}
public function getSnippets(){
return $this->snippets;
}
public function getText(){
return implode('', $this->snippets);
}
/**
* 将剪切后的字符串位置转换成原始字符串位置
* @param int $pos
* @param int
*/
public function mapPos($pos){
foreach ($this->snippets as $k => $v){
$pos += $k;
if($pos < $k + strlen($v)){
break;
}
$pos -= ($k + strlen($v));
}
return $pos;
}
/**
* 普通状态
*/
private function stateNormal($str, $pos, &$next){
$ori = $pos;
$posSQ = strpos($str, '\'', $pos);
$posDQ = strpos($str, '"', $pos);
$pos = $posSQ;
$this->subStateQ = '\'';
$next = 'stateQ';
if($posDQ !== false && (($posDQ < $pos) || ($pos === false)) ){
$pos = $posDQ;
$this->subStateQ = '"';
}
if($pos !== false){
$this->snippets[$ori] = substr($str, $ori, $pos-$ori);
$pos ++;
}else{
$this->snippets[$ori] = substr($str, $ori);
}
return $pos;
}
/**
* 进入引号状态
*/
private function stateQ($str, $pos, &$next){
$posESC = strpos($str, '\\', $pos);
$posQ = strpos($str, $this->subStateQ, $pos);
$pos = $posESC;
$next = 'stateESC';
if($posQ !== false && (($posQ<$posESC) || ($posESC === false))){
$pos = $posQ;
$next = 'stateNormal';
}
if($pos !== false){
$pos ++;
}
return $pos;
}
/**
* 进入转义状态
*/
private function stateESC($str, $pos, &$next){
$pos++;
if($pos >= strlen($str)){
return false;
}
$next = 'stateQ';
return $pos;
}
/**
* 去掉嵌套字符串后的内容
* @var array
*/
private $snippets=array();
private $subStateQ;
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* $Id: NewThenInit.php 65241 2015-06-12 01:55:00Z lipengcheng02 $
*
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
* NewThenInit
*/
namespace phprs\util;
/**
* 创建和初始化分离
* 先创建, 再初始化
* @author caoym
*/
class NewThenInit
{
/**
* @param string|ReflectionClass $class 类
*/
public function __construct($class){
if(is_string($class)){
$this->refl = new \ReflectionClass($class);
}else{
$this->refl = $class;
}
$this->obj = $this->refl->newInstanceWithoutConstructor();
}
/**
* 返回创建的实例
* @return object
*/
public function getObject(){
return $this->obj;
}
/**
* 初始化
* @param 可变数量参数
*/
public function init($arg0 = null, $_ = null){
$this->initArgs(func_get_args());
}
/**
* 初始化
* @param array $args 参数列表
*/
public function initArgs($args){
$cnst = $this->refl->getConstructor();
if($cnst !== null) {
$cnst->invokeArgs( $this->obj, $args);
}else{
Verify::isTrue(count($args) ===0, $this->refl->getName().' no constructor found with '.func_num_args().' params');
}
}
private $refl;
private $obj;
}

View File

@@ -0,0 +1,93 @@
<?php
namespace phprs\util;
use phprs\util\KVCatchInterface;
/*class Redis{
public function connect(){}
public function set(){}
public function get(){}
public function del(){}
}*/
class RedisCache implements KVCatchInterface
{
/**
* 设置key
* @param string $key
* @param mixed $var
* @param int $ttl
* @return boolean
*/
public function set($key, $var, $ttl=null){
if($this->serialize){
$var = serialize($var);
}
if($ttl === null){
$this->getImpl()->set($key, $var);
}else{
return $this->getImpl()->setex($key, $ttl, $var);
}
}
/**
* 删除key
* @param string $key
* @return boolean
*/
public function del($key){
return $this->getImpl()->delete($key) == 1;
}
/**
* get key
* @param string $key
* @param boolean $succeeded
* @return mixed
*/
public function get($key, &$succeeded=null){
$res = $this->getImpl()->get($key);
$succeeded = ($res !== false);
if($this->serialize){
$res = unserialize($res);
}
return $res;
}
private function getImpl(){
if($this->redis === null){
$this->redis = new \Redis();
}
if(!$this->redis->isConnected()){
$this->redis->connect($this->host, $this->port);
}
if(!empty($this->user) && !empty($this->pwd)){
Verify::isTrue($this->redis->auth($this->user . ":" . $this->pwd), $this->redis->getLastError());
}
return $this->redis;
}
/**
* @property
* 服务器地址
*/
private $host;
/**
* @property
* 服务器端口
*/
private $port;
/**
* @property
* 服务器实例Id
*/
private $user;
/**
* @property
* 服务器实例密码
*/
private $pwd;
/**
* @var \Redis
*/
private $redis;
/** @property */
private $serialize = true;
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* $Id: SaftyFileWriter.php 57435 2014-12-21 15:04:22Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief
*/
namespace phprs\util;
/**
* 并发安全的写文件
* 其原子性取决于文件系统
* 通过先写临时文件, 然后重命名的方式实现
* @author caoym
*
*/
class SaftyFileWriter
{
/**
* 写入文件
* @param string $path 路径
* @param mixed $data 写入的值
* @param boolean $overwrite 是否覆盖已有文件
* @return boolean
*/
static public function write($path, $data, $overwrite = true){
$path = str_replace('\\', '/', $path);
$pos = strrpos ($path, '/');
$file_name="";
$file_dir="";
if($pos === false){
$file_name=$path;
}else{
$file_dir = substr($path, 0,$pos+1);
$file_name = substr($path, $pos+1);
}
$tmp_file= tempnam($file_dir, 'tsb_sfw');
Verify::isTrue(false !== file_put_contents($tmp_file, $data), "write to file: $tmp_file failed");
if($overwrite){
@unlink($path); //删除原始文件
}
if(!@rename($tmp_file, $path)){
@unlink($tmp_file); //删除原始文件
Verify::e("write to file: $tmp_file failed");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* $Id: SerializableFunc.php 56636 2014-12-03 11:26:59Z caoyangmin $
*
* @author caoyangmin(caoyangmin@baidu.com)
* @brief SerializableFunc
*/
namespace phprs\util;
/**
* 支持序列化的函数
* @author caoym
*
*/
class SerializableFunc{
/**
* 方法,绑定参数
* 如
* func,arg1,arg2
* array('a','method1'), arg1,arg2
*/
public function __construct(/*$func, $bind*/){
$args = func_get_args();
Verify::isTrue(count($args)>0);
Verify::isTrue(is_callable($args[0]));
$this->func = $args[0];
$this->bind = array_slice($args,1);
}
/**
*
* 调用时,将bind参数加在方法的最前面
* @return mixed
*/
public function __invoke(){
$args = func_get_args();
$params = $this->bind;
foreach ($args as $arg){
array_push($params, $arg);
}
$res = call_user_func_array($this->func, $params);
foreach ($this->next as $next){
call_user_func_array($next,$args);
}
return $res;
}
/**
* 串行调用
* @var unknown
*/
public $next=array();
private $bind;
private $func;
}
?>

View File

@@ -0,0 +1,201 @@
<?php
/**
* $Id: Tree.php 58820 2015-01-16 16:29:33Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief Tree
*/
namespace phprs\util;
class Tree {
/**
* 插入一个节点
* 插入时允许使用通配符*
* @param array $path
* @param unknown $value
* @param string $replace_exits 是否替换已存在的
* @param mixed $replaced 被替换的原始值
* @return boolean
*/
public function insert( $path, $value, $replace_exits=false, &$replaced=null){
assert(count($path));
$left = $path;
$cur = array_shift($left);//剩余未遍历的
if(!array_key_exists($cur,$this->arr)){
$this->arr[$cur] = array();
}
$insert= false;
$this->visitNode($path,function ($name,&$node)use(&$insert,&$left,$value,$replace_exits, &$replaced){
$cur = array_shift($left);
if($cur === null){
if(array_key_exists('value',$node)){//是否已存在
if(!$replace_exits){
return false;
}
$replaced = $node['value'];
}
$node['value'] = $value;
$insert = true;
return false;//停止遍历
}else{
if(!array_key_exists('next',$node)){
$node['next'] = array();
}
if(!array_key_exists($cur,$node['next'])){
$node['next'][$cur]= array();
}
}
return true;
}, true);
return $insert;
}
/**
* 删除一个路径下的所有节点
* @param array $path
* @return boolean
*/
public function erase(array $path){
if(count($path)==0){
return false;
}
$size = count($path);
$key = $path[$size-1];
if($size ==1){
if(array_key_exists($key, $this->arr)){
unset($this->arr[$key]);
return true;
}
return false;
}
$i = 0;
$res = false;
$full = $this->visitNode($path,function ($name,&$node)use(&$i,$size,$key,&$res){
if(++$i == $size-1){
if(isset($node['next']) && isset($node['next'][$key])){
unset($node['next'][$key]);
$res = true;
}
return false;
}
return true;
}, true);
return $res;
}
/**
* 遍历路径
* @param array $path
* @return boolean 全部遍历完返回true否则返回false
*/
public function visit( $path ,$vistor, $exact_match=false){
return $this->visitNode($path,function ($name,$node)use($vistor){
return $vistor($name,array_key_exists('value',$node)?$node['value']:null);
}, $exact_match);
}
/**
* 查找指定路径的节点
* @param array $path
* @param boolean $exact_match 是否精确匹配,如果是,则通配符被认为与其他值不同
* @return 返回节点的值, 或者null
*/
public function find(array $path, $exact_match=false){
$found = null;
$full = $this->visitNode($path,function ($name,$node)use(&$found){
$found = array_key_exists('value',$node)?$node['value']:null;
return true;
}, $exact_match);
return $full?$found:null;
}
/**
* 查找指定路径的节点
* @param array $path
* @param boolean $exact_match 是否精确匹配,如果是,则通配符被认为与其他值不同
* @param boolean $all_req_paths 是否要求查询路径的所有元素都必须遍历到
* @param boolean $all_paths 是否
* @return 返回节点的, 或者null
*/
public function findNode(array $path, $exact_match=false, $all_req_paths=false){
$found = null;
$full = $this->visitNode($path,function ($name,$node)use(&$found){
$found = $node;
return true;
}, $exact_match, $all_req_paths);
return $full?$found:null;
}
/**
* 遍历路径
* @param array $path
* @param boolean $exact_match 是否精确匹配,如果是,则通配符被认为与其他值不同
* @param boolean $all_req_paths 是否要求查询路径的所有元素都必须遍历到
* @return boolean 变量完成返回true遍历未完成时终止返回false
*/
private function visitNode( $path, $vistor, $exact_match=false, $all_req_paths=false){
assert(count($path));
$pos = &$this->arr;
$next = &$pos;
foreach ($path as $i){
if(is_null($next)) {
return !$all_req_paths;
}
if(!array_key_exists($i, $next)){
if($exact_match){
return false;
}
if($i == self::$end){
return false;
}
//不要求完全匹配, 尝试匹配通配符
if(!array_key_exists(self::$wildcard, $next)){
return false;
}
$pos = &$next[self::$wildcard];
}else{
$pos = &$next[$i];
}
if(!$vistor($i,$pos)){
return false;
}
if(array_key_exists('next',$pos)){
$next = &$pos['next'];
}else{
$nul = null;
$next = &$nul; //$next = null 会导致引用的对象被赋值而不是next被赋值
}
}
return true;
}
/**
* 树打平输出成数组
* @return array
*/
public function export(){
$res=array();
self::treeToArray($this->arr, $res);
return $res;
}
/**
*
* @param array $tree
* @param array $res
* @return void
*/
static private function treeToArray($tree, &$res){
foreach ($tree as $name=>$node){
if($node === null){
continue;
}
if( isset($node['value']) ){
$res[] = array(array($name), $node['value']);
}
if( isset($node['next']) ){
$tmp = array();
self::treeToArray($node['next'], $tmp);
foreach ($tmp as $v){
array_unshift($v[0], $name);
$res[] = array($v[0], $v[1]);
}
}
}
}
private $arr=array();
static public $end="\n";
static public $wildcard='*';
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* $Id: Verify.php 57435 2014-12-21 15:04:22Z caoyangmin $
* @author caoym(caoyangmin@gmail.com)
* @brief
*/
namespace phprs\util;
/**
* if(false) throw ;
* @param boolen $var 判断条件
* @param string $msg 异常消息
* @throws Exception
* @return unknown
*/
class Verify{
/**
* 如果判断不为true,抛出异常
* @param boolean $var
* @param string|Exception $msg
* @param number $code
* @throws \Exception
* @return unknown
*/
static public function isTrue($var, $msg = null)
{
if (!$var) {
if($msg === null || is_string($msg)){
throw new \Exception($msg);
}else{
throw $msg;
}
} else {
return $var;
}
}
/**
*
* @param \Exception|string $e
* @throws unknown
*/
static public function e($e){
if ($e === null || is_string($e)) {
throw new \Exception($e);
} else {
throw $e;
}
}
}

View File

@@ -0,0 +1,6 @@
<?php
namespace phprs\util\exceptions;
class BadRequest extends \Exception
{
}

View File

@@ -0,0 +1,6 @@
<?php
namespace phprs\util\exceptions;
class Forbidden extends \Exception
{
}

View File

@@ -0,0 +1,6 @@
<?php
namespace phprs\util\exceptions;
class NotFound extends \Exception
{
}