diff --git a/compile/huodong.m.yohobuy.com/1.0.3/b4b0cb998886e57bb0f81d7036c95c4b.php b/compile/huodong.m.yohobuy.com/1.0.3/b4b0cb998886e57bb0f81d7036c95c4b.php new file mode 100644 index 0000000..836746f --- /dev/null +++ b/compile/huodong.m.yohobuy.com/1.0.3/b4b0cb998886e57bb0f81d7036c95c4b.php @@ -0,0 +1,135 @@ +<?php return function ($in, $debugopt = 1) { + $cx = array( + 'flags' => array( + 'jstrue' => false, + 'jsobj' => false, + 'spvar' => true, + 'prop' => false, + 'method' => false, + 'mustlok' => true, + 'echo' => false, + 'debug' => $debugopt, + ), + 'constants' => array(), + 'helpers' => array(), + 'blockhelpers' => array(), + 'hbhelpers' => array(), + 'partials' => array(), + 'scopes' => array(), + 'sp_vars' => array('root' => $in), + 'lcrun' => 'Plugin\LCRun3', + + ); + + return '<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('staticTitle')), ENT_QUOTES, 'UTF-8').'</title> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> + <style> + *{margin: 0;padding: 0;} + #coupon-container {width: 100%;} + #coupon-container img {width: 100%; height: auto; display: block;overflow: hidden;} + .img-box {width: 100%;position: relative;} + .img-box .href {width: 50%; height: 100%;position: absolute;top: 0;} + .img-box .href-left {left: 0} + .img-box .href-right {right: 0} + .img-box .href-center {width: 90%;height: 100%;position: absolute;top: 0;left: 5%;} + .show-more {height: 100%;width: 25%;position: absolute;top: 0;right: 10%;} + .yoho-tip {position: fixed;display: none;text-align: center;width: 70%;padding: 34px 0;top: 50%;left: 50%;margin-left: -35%;margin-top: -45px;background-color: #000;opacity: 0.9;color: #fff;font-size: 18px;border: none;border-radius: 10px;} + </style> + <link rel="dns-prefetch" href="//cdn.yoho.cn"> + <link rel="dns-prefetch" href="//static.yohobuy.com"> +</head> +<body> + <div id="coupon-container" param="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('activityId')), ENT_QUOTES, 'UTF-8').'" isLogged="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('isLogged')), ENT_QUOTES, 'UTF-8').'"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/01.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/02.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/03-a.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-1.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/04-a.jpg"> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/05-a.jpg"> + <a class="href href-right" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_yohope')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/06.jpg"> + <a class="href href-center" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_mycoupon')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-2.jpg"> + + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/07.jpg"> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/08.jpg"> + <a class="href href-left" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_more')), ENT_QUOTES, 'UTF-8').'"></a> + <a class="href href-right" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_more')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/09.jpg"> + <span class="href href-left get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17443"></span> + <span class="href href-right get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17445"></span> + </div> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-1.jpg"> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/10.jpg"> + <a class="href href-left" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_more')), ENT_QUOTES, 'UTF-8').'"></a> + <a class="href href-right" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_1')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/11.jpg"> + <span class="href href-left get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17463"></span> + <span class="href href-right get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17451"></span> + </div> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-1.jpg"> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/12.jpg"> + <a class="href href-left" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_2')), ENT_QUOTES, 'UTF-8').'"></a> + <a class="href href-right" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_3')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/13.jpg"> + <span class="href href-left get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17461"></span> + <span class="href href-right get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17459"></span> + </div> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-1.jpg"> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/14.jpg"> + <a class="href href-left" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_4')), ENT_QUOTES, 'UTF-8').'"></a> + <a class="href href-right" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_5')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/15.jpg"> + <span class="href href-left get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17453"></span> + <span class="href href-right get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17455"></span> + </div> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-1.jpg"> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/16.jpg"> + <a class="href href-left" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_6')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/17.jpg"> + <span class="href href-left get-coupon" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jumpUrl')), ENT_QUOTES, 'UTF-8').'" param="17449"></span> + </div> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-1.jpg"> + + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/24-1.jpg"> + <div class="img-box"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/24-2.jpg"> + <a class="show-more" href="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('url_help')), ENT_QUOTES, 'UTF-8').'"></a> + </div> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/24-3.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/line-2.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/25-1-a.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/25-2-a.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/25-3-a.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/26-a.jpg"> + <img src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/img/newuser/160107/27-a.jpg"> + </div> + <script type="text/javascript" src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/js/jquery.min.js"></script> + <script type="text/javascript" src="'.htmlentities((string)Plugin\LCRun3::v($cx, $in, array('jsUrl')), ENT_QUOTES, 'UTF-8').'/cuxiao/js/newuser/coupon2.js"></script> +</body> +</html>'; +} +?> \ No newline at end of file diff --git a/static/huodong/cuxiao/img/newuser/160107/03-a.jpg b/static/huodong/cuxiao/img/newuser/160107/03-a.jpg new file mode 100644 index 0000000..993e44e Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/03-a.jpg differ diff --git a/static/huodong/cuxiao/img/newuser/160107/04-a.jpg b/static/huodong/cuxiao/img/newuser/160107/04-a.jpg new file mode 100644 index 0000000..4ace3b9 Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/04-a.jpg differ diff --git a/static/huodong/cuxiao/img/newuser/160107/05-a.jpg b/static/huodong/cuxiao/img/newuser/160107/05-a.jpg new file mode 100644 index 0000000..0af045d Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/05-a.jpg differ diff --git a/static/huodong/cuxiao/img/newuser/160107/25-1-a.jpg b/static/huodong/cuxiao/img/newuser/160107/25-1-a.jpg new file mode 100644 index 0000000..e401746 Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/25-1-a.jpg differ diff --git a/static/huodong/cuxiao/img/newuser/160107/25-2-a.jpg b/static/huodong/cuxiao/img/newuser/160107/25-2-a.jpg new file mode 100644 index 0000000..fe71526 Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/25-2-a.jpg differ diff --git a/static/huodong/cuxiao/img/newuser/160107/25-3-a.jpg b/static/huodong/cuxiao/img/newuser/160107/25-3-a.jpg new file mode 100644 index 0000000..1ed066b Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/25-3-a.jpg differ diff --git a/static/huodong/cuxiao/img/newuser/160107/26-a.jpg b/static/huodong/cuxiao/img/newuser/160107/26-a.jpg new file mode 100644 index 0000000..e12d413 Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/26-a.jpg differ diff --git a/static/huodong/cuxiao/img/newuser/160107/27-a.jpg b/static/huodong/cuxiao/img/newuser/160107/27-a.jpg new file mode 100644 index 0000000..64bf9e1 Binary files /dev/null and b/static/huodong/cuxiao/img/newuser/160107/27-a.jpg differ diff --git a/template/huodong.m.yohobuy.com/actions/cuxiao/coupon/newuser3.phtml b/template/huodong.m.yohobuy.com/actions/cuxiao/coupon/newuser3.phtml index ac8f526..99fef28 100644 --- a/template/huodong.m.yohobuy.com/actions/cuxiao/coupon/newuser3.phtml +++ b/template/huodong.m.yohobuy.com/actions/cuxiao/coupon/newuser3.phtml @@ -23,11 +23,11 @@ <div id="coupon-container" param="{{activityId}}" isLogged="{{isLogged}}"> <img src="{{jsUrl}}/cuxiao/img/newuser/160107/01.jpg"> <img src="{{jsUrl}}/cuxiao/img/newuser/160107/02.jpg"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/03.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/03-a.jpg"> <img src="{{jsUrl}}/cuxiao/img/newuser/160107/line-1.jpg"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/04.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/04-a.jpg"> <div class="img-box"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/05.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/05-a.jpg"> <a class="href href-right" href="{{url_yohope}}"></a> </div> <div class="img-box"> @@ -98,11 +98,11 @@ </div> <img src="{{jsUrl}}/cuxiao/img/newuser/160107/24-3.jpg"> <img src="{{jsUrl}}/cuxiao/img/newuser/160107/line-2.jpg"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/25-1.jpg"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/25-2.jpg"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/25-3.jpg"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/26.jpg"> - <img src="{{jsUrl}}/cuxiao/img/newuser/160107/27.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/25-1-a.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/25-2-a.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/25-3-a.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/26-a.jpg"> + <img src="{{jsUrl}}/cuxiao/img/newuser/160107/27-a.jpg"> </div> <script type="text/javascript" src="{{jsUrl}}/cuxiao/js/jquery.min.js"></script> <script type="text/javascript" src="{{jsUrl}}/cuxiao/js/newuser/coupon2.js"></script> diff --git a/yaf/Hood/Action.php b/yaf/Hood/Action.php new file mode 100644 index 0000000..085c483 --- /dev/null +++ b/yaf/Hood/Action.php @@ -0,0 +1,185 @@ +<?php +namespace Hood; + +use Yaf\Controller_Abstract; +use Hood\Helper\View as hoodView; +use Yaf; +use Hood\Validator as hoodValidator; + +class Action extends Controller_Abstract +{ + private $_viewLink = array(); + + private $_viewScript = array(); + + private $_headTitle; + + private $_headmeta; + + /** + * Meta + * @return \Hood\Helper\View\Meta + */ + public function _headMeta() + { + if (empty($this->_headmeta)) { + $this->_headmeta = new hoodView\Meta(); + $this->getView()->assign("_headmeta", $this->_headmeta); + } + return $this->_headmeta; + } + + + /** + * Script + * @return \Hood\Helper\View\Script + */ + public function _viewScript($scriptName = '_headScript') + { + if (!isset($this->_viewScript[$scriptName])) { + $this->_viewScript[$scriptName] = new hoodView\Script(); + $this->getView()->assign($scriptName, $this->_viewScript[$scriptName]); + } + return $this->_viewScript[$scriptName]; + } + + /** + * + * @return \Hood\Helper\View\Link + */ + public function _viewLink($linkName = '_headLink') + { + if (!isset($this->_viewLink[$linkName])) { + $this->_viewLink[$linkName] = new hoodView\Link(); + $this->getView()->assign($linkName, $this->_viewLink[$linkName]); + } + return $this->_viewLink[$linkName]; + } + + /** + * Title + * @return \Hood\Helper\View\Title + */ + public function _headTitle($title) + { + if (empty($this->_headTitle)) { + $this->_headTitle = new hoodView\Title(); + $this->getView()->assign("_headTitle", $this->_headTitle); + } + return $this->_headTitle->headTitle($title); + } + + /** + * js 跳转 并 提示 + * + * @param String $url + * @param String $expression + */ + protected function helpJsRedirect($message = '', $script = "history.back();") + { + $html = ''; + if (!empty($message)) { + header("content-type: text/html; charset=utf-8"); + $message = str_replace("\n", "\\n", $message); + $html .= "<script language=\"javascript\">"; + $html .= "alert(\"{$message}\");"; + $html .= "</script>"; + } + $html .= "<script language=\"javascript\">"; + $html .= $script; + $html .= "</script>"; + die($html); + } + + /** + * 跳转 + * @param String $url + */ + protected function helpLocation($url) + { + header('Location: ' . $url); + } + + /** + * refresh跳转 + * @param $url + * @param string $message + */ + protected function helpRefresh($url, $message = '') + { + $html = ''; + if (!empty($message)) { + header("content-type: text/html; charset=utf-8"); + $message = str_replace("\n", "\\n", $message); + $html .= "<script language=\"javascript\">"; + $html .= "alert(\"{$message}\");"; + $html .= "</script>"; + } + $html .= "<script language=\"javascript\">"; + $html .= "window.location.href='{$url}';"; + $html .= "</script>"; + echo $html; + } + + /** + * JSON输出 + * @param $code + * @param null $message + * @param null $data + */ + protected function helpJsonResult($code, $message = null, $data = null) + { + header('Content-type: application/json'); + echo json_encode(array('code' => $code, 'message' => $message, 'data' => $data)); + exit(); + } + + /** + * JSON输出 + * @param array $data + */ + protected function helpJson(array $data) + { + header('Content-type: application/json'); + echo json_encode($data); + exit(); + } + + /** + * JSONP Callback输出,用于远程调用 + * @param $callbackString + * @param $code + * @param null $message + * @param null $data + */ + protected function helpJsonCallbackResult($callbackString, $code, $message = null, $data = null) + { + header('Content-type: application/json'); + echo $callbackString . "("; + echo json_encode(array('code' => $code, 'message' => $message, 'data' => $data)); + echo ")"; + exit(); + } + + + /** + * @param string $namespace + * @return \Hood\Core\Session\SessionNamespace + */ + public function session($namespace = 'session_default', $sessionName = null) + { + return Session::start($namespace, $sessionName); + } + + /** + * 数据校验 + * @param array $data + * @param array $rules + * @param array $messagesAttribute + * @return Helper\Validation\Validator + */ + public function validator(array $data, array $rules, array $messagesAttribute = array()) + { + return hoodValidator::make($data, $rules, $messagesAttribute); + } +} \ No newline at end of file diff --git a/yaf/Hood/Cache.php b/yaf/Hood/Cache.php new file mode 100644 index 0000000..4a90d78 --- /dev/null +++ b/yaf/Hood/Cache.php @@ -0,0 +1,64 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/9 + * Time: 下午7:40 + */ + +namespace Hood; + +use Hood\Cache\Memcached as Mcd; +use Hood\Cache\Memcache as Mc; +use Hood\Cache\CacheRedis; +use Hood\Cache\FileCache; + +class Cache +{ + /** + * + * @param null $node + * @return Mcd + */ + static public function Memcached($node = null, $childNode = 'hosts') + { + $mc = new Mcd(); + $mc->setNode($node)->setChildNodes($childNode); + return $mc; + } + + /** + * @param null $node + * @param string $childNode + * @return Mc + */ + static public function Memcache($node = null, $childNode = 'hosts') + { + $mc = new Mc(); + $mc->setNode($node)->setChildNodes($childNode); + return $mc; + } + + /** + * @return CacheRedis + */ + static public function Redis() + { + $servers = array(); + $persistentID = ''; + return new CacheRedis($servers, $persistentID); + } + + + /** + * + * @param null $childNode + * @param string $node + * @param null $cachePath + * @return FileCache + */ + static public function File($childNode = null, $node = 'cache', $cachePath = null) + { + return new FileCache($childNode, $node, $cachePath); + } +} \ No newline at end of file diff --git a/yaf/Hood/Cache/CacheInterface.php b/yaf/Hood/Cache/CacheInterface.php new file mode 100644 index 0000000..f588a17 --- /dev/null +++ b/yaf/Hood/Cache/CacheInterface.php @@ -0,0 +1,60 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/9 + * Time: 下午10:35 + */ + +namespace Hood\Cache; + +interface CacheInterface +{ + /** + * @param $key + * @return mixed + */ + public function get($key); + + /** + * @param $key + * @param $value + * @param $minutes + * @return mixed + */ + public function add($key, $value, $minutes); + + /** + * @param $key + * @param $value + * @param $minutes + * @return mixed + */ + public function set($key, $value, $minutes); + + /** + * @param $key + * @param int $value + * @return mixed + */ + public function increment($key, $value = 1); + + /** + * @param $key + * @param int $value + * @return mixed + */ + public function decrement($key, $value = 1); + + /** + * @param $key + * @return mixed + */ + public function delete($key); + + /** + * @param $tagName + * @return $this + */ + public function tag($tagName); +} \ No newline at end of file diff --git a/yaf/Hood/Cache/CacheRedis.php b/yaf/Hood/Cache/CacheRedis.php new file mode 100644 index 0000000..a9c728f --- /dev/null +++ b/yaf/Hood/Cache/CacheRedis.php @@ -0,0 +1,1025 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/11/23 + * Time: 上午1:39 + */ + +namespace Hood\Cache; + +use \Redis; + +class CacheRedis +{ + + /** + * + * Enter description here ... + * @var Redis + */ + private $redis; + + private $timeout = 2.5; + + public function __construct(array $servers, $persistentID = '') + { + if (empty($servers)) { + throw new Q_Cache_Exception('redis server is null.'); + } + $this->redis = new Redis(); + $this->redis->connect($servers['host'], $servers['port'], $this->timeout); + } + + /** + * + * 返回key所关联的字符串值,如果key不存在则返回特殊值nil。 + * @param String $key + * @return Mixed or nil + */ + public function get($key) + { + assert(is_string($key)); + return $this->redis->get($key); + } + + /** + * + * 将字符串值value关联到key + * @param String $key + * @param Mixed $val + * @return bool + */ + public function set($key, $val) + { + assert(is_string($key)); + return $this->redis->set($key, $val); + } + + /** + * + * 同时设置一个或多个key-value对。 + * @param array $keys + * @return bool + */ + public function mset(array $keys) + { + return $this->redis->mset($keys); + } + + /** + * + * 返回所有(一个或多个)给定key的值.如果某个指定key不存在,那么返回特殊值nil。因此,该命令永不失败。 + * @param array $keys + * @return Mixed + */ + public function mget(array $keys) + { + return $this->redis->mget($keys); + } + + /** + * + * 返回key中字符串值的子字符串,字符串的截取范围由start和end两个偏移量决定(包括start和end在内)。 + * 负数偏移量表示从字符串最后开始计数,-1表示最后一个字符,-2表示倒数第二个,以此类推 + * @param String $key + * @param Integer $start + * @param Integer $end + * @return String + */ + public function getRange($key, $start, $end) + { + assert(is_string($key)); + return $this->redis->getRange($key, $start, $end); + } + + /** + * + * 删除数据 ( 返回删除个数 ) + * @param String $key + * @return bool + */ + public function del($key) + { + assert(is_string($key)); + return $this->redis->del($key); + } + + /** + * 查找符合给定模式的key。 + * + * 可以使用正则 + * ========================================= + * *命中数据库中所有key + * h?llo命中hello, hallo and hxllo等 + * h*llo命中hllo和heeeeello等 + * h[ae]llo命中hello和hallo,但不命中hillo + * ========================================= + * KEYS的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的key,你最好还是用集合(set)结构。 + * @param String $keys + * @return Mixed + */ + public function keys($keys) + { + return $this->redis->keys($keys); + } + + /** + * + * 选择数据库 + * @param String $db + * @return bool + */ + public function select($db = 9) + { + return $this->redis->select($db); + } + + /** + * + * 获取 hash 集合中的键值 + * @param String $hashName + * @param String $key + * @param Mixed $val + * @return Mixed + */ + public function hget($hashName, $key, $val) + { + assert(is_string($hashName)) && assert(is_string($key)); + return $this->redis->hget($hashName, $key, $val); + } + + /** + * + * 将哈希表key中的域field的值设为value。 + * @param String $hashName + * @param String $key + * @param Mixed $val + * @return bool + */ + public function hset($hashName, $key, $val) + { + assert(is_string($hashName)) && assert(is_string($key)); + return $this->redis->hset($hashName, $key, $val); + } + + /** + * + * 排序 + * + * @param String $key + * @param array $options + * 'by' => 'some_pattern_*', + * 'limit' => array(0, 1), + * 'get' => 'some_other_pattern_*' or an array of patterns, + * 'sort' => 'asc' or 'desc', + * 'alpha' => TRUE, + * 'store' => 'external-key' + * @return array + */ + public function sort($key, array $options = array()) + { + assert(is_string($key)); + return $this->redis->sort($key, $options); + } + + /** + * + * 从当前数据库中随机返回(不删除)一个key。 + * @return String or Mixed + */ + public function randomkey() + { + return $this->redis->randomKey(); + } + + /** + * + * 返回给定key的剩余生存时间(time to live)(以秒为单位)。 + * @param String $key + * @return Integer + */ + public function ttl($key) + { + assert(is_string($key)); + return $this->redis->ttl($key); + } + + /** + * + * 检查给定key是否存在 + * @param String $key + * @return bool + */ + public function exists($key) + { + assert(is_string($key)); + return $this->redis->exists($key); + } + + /** + * + * 移动key 到另外一个数据库 + * @param String $key + * @param Integer $dbName + * @return bool + */ + public function move($key, $dbName) + { + assert(is_string($key)); + return $this->redis->move($key, $dbName); + } + + /** + * + * 将key改名为newkey + * @param String $key + * @param String $newKey + * @return bool + */ + public function rename($key, $newKey) + { + assert(is_string($key)) && assert(is_string($newKey)); + return $this->redis->rename($key, $newKey); + } + + /** + * + * 返回key所储存的值的类型 + * @param String $key + * @return Mixed + * ================================ + * none(key不存在) string(字符串) list(列表) set(集合) zset(有序集) hash(哈希表) + * ================================ + */ + public function type($key) + { + return $this->redis->type($key); + } + + /** + * + * 为给定key设置生存时间 + * @param String $key + * @param Integer $expire + * @return bool + */ + public function setTimeout($key, $expire) + { + assert(is_string($key)) && assert(is_int($expire)); + return $this->redis->setTimeout($key, $expire); + } + + /** + * + * 不同在于EXPIREAT命令接受的时间参数是UNIX时间戳(unix timestamp)。 + * @param String $key + * @param Integer $expire + * @return bool + */ + public function expireAt($key, $expire) + { + assert(is_string($key)) && assert(is_int($expire)); + return $this->redis->expireAt($key, $expire); + } + + /** + * + * 移除给定key的生存时间 + * @param String $key + * @return bool + */ + public function persist($key) + { + assert(is_string($key)); + return $this->redis->persist($key); + } + + /** + * + * 将值value关联到key,并将key的生存时间设为seconds(以秒为单位) + * @param String $key + * @param Mixed $val + * @param Integer $expire + * @return bool + */ + public function setex($key, $val, $expire) + { + assert(is_string($key)) && assert(is_int($expire)); + return $this->redis->setex($key, $expire, $val); + } + + /** + * + * 如果key已经存在并且是一个字符串,APPEND命令将value追加到key原来的值之后 + * @param String $key + * @param Mixed $val + * @return bool + */ + public function append($key, $val) + { + assert(is_string($key)); + return $this->redis->append($key, $val); + } + + /** + * + * 将给定key的值设为value,并返回key的旧值 + * @param String $key + * @param Mixed $val + * @return Mixed + */ + public function getSet($key, $val) + { + assert(is_string($key)); + return $this->redis->getSet($key, $val); + } + + /** + * + * 返回key所储存的字符串值的长度 + * @param String $key + * @return integer + */ + public function strlen($key) + { + return $this->redis->strlen($key); + } + + /** + * + * 将key中储存的数字值减一 + * @param String $key + * @return Integer + */ + public function decr($key) + { + assert(is_string($key)); + return $this->redis->decr($key); + } + + /** + * + * 将key所储存的值减去减量decrement。 + * @param String $key + * @param Integer $value + * @return intger + */ + public function decrBy($key, $value = 1) + { + assert(is_string($key)) && assert(is_int($value)); + return $this->redis->decrBy($key, $value); + } + + /** + * + * 将key中储存的数字值增一 + * @param String $key + * @param Integer $val + * @return Integer + */ + public function incrBy($key, $val = 1) + { + return $this->redis->incrBy($key, $val); + } + + /** + * + * 同时将多个field - value(域-值)对设置到哈希表key中 + * @param String $key + * @param array $vals + * @return bool + */ + public function hMset($hashKey, array $keys) + { + assert(is_string($hashKey)); + return $this->redis->hMset($hashKey, $keys); + } + + /** + * + * 返回哈希表key中,一个或多个给定域的值 + * @param String $hashKey + * @param array $keys + * @return Mixed + */ + public function hmGet($hashKey, array $keys) + { + assert(is_string($hashKey)); + return $this->redis->hmGet($hashKey, $keys); + } + + /** + * + * 返回哈希表key中,所有的域和值 + * @param String $hashKey + * @return Mixed + */ + public function hGetAll($hashKey) + { + assert(is_string($hashKey)); + return $this->redis->hGetAll($hashKey); + } + + /** + * + * 删除哈希表key中的一个或多个指定域 + * @param String $hashKey + * @return bool + */ + public function hDel($hashKey, $hashKey2 = null, $hashKeyN = null) + { + $this->redis->hDel($hashKey, $hashKey2, $hashKeyN); + } + + /** + * + * 返回哈希表key中域的数量 + * @param String $hashKey + * @return Integer + */ + public function hLen($hashKey) + { + return $this->redis->hLen($hashKey); + } + + /** + * + * 查看哈希表key中,给定域field是否存在 + * @param String $hashKey + * @return bool + */ + public function hExists($key, $hashKey) + { + return $this->redis->hExists($key, $hashKey); + } + + /** + * + * 为哈希表key中的域field的值加上增量increment。 + * @param String $hashKey + * @param String $key + * @param Integer $member + * @return Integer + */ + public function hincrby($hashKey, $key, $member) + { + return $this->redis->hIncrBy($hashKey, $key, $member); + } + + /** + * + * 返回哈希表key中的所有域 + * @param String $hashKey + * @return array + */ + public function hKeys($hashKey) + { + return $this->redis->hKeys($hashKey); + } + + /** + * + * 返回哈希表key中的所有值 + * @param String $hashKey + * @return Array + */ + public function hVals($hashKey) + { + return $this->redis->hVals($hashKey); + } + ########################### + # 表 List + ########################### + /** + * + * 将值value插入到列表key的表头 + * @param String $key + * @param Mixed $value + * @return bool + */ + public function lPush($key, $value) + { + assert(is_string($key)); + return $this->redis->lPush($key, $value); + } + + /** + * + * 将值value插入到列表key的表头,当且仅当key存在并且是一个列表 + * @param String $key + * @param Mixed $value + * @return bool + */ + public function lPushx($key, $value) + { + assert(is_string($key)); + return $this->redis->lPushx($key, $value); + } + + /** + * + * 将值value插入到列表key的表尾 + * @param String $key + * @param Mixed $value + * @return bool + */ + public function rPush($key, $value) + { + assert(is_string($key)); + return $this->redis->rPush($key, $value); + } + + /** + * + * 将值value插入到列表key的表尾,当且仅当key存在并且是一个列表 + * @param String $key + * @param Mixed $value + * @return bool + */ + public function rPushx($key, $value) + { + assert(is_string($key)); + return $this->redis->rPushx($key, $value); + } + + /** + * + * 移除并返回列表key的头元素 + * @param String $key + * @return bool or nil + */ + public function lPop($key) + { + return $this->redis->lPop($key); + } + + /** + * + * 移除并返回列表key的尾元素 + * @param String $key + * @return bool or nil + */ + public function rPop($key) + { + return $this->redis->rPop($key); + } + + /** + * + * BLPOP是列表的阻塞式(blocking)弹出原语 + * =================================== + * 类似 Gearman 等待移除 + * =================================== + * @param array $keys + * @param Integer $timeout + * @return array + */ + public function blPop(array $keys, $timeout = 2) + { + return $this->redis->blPop($keys, (int)$timeout); + } + + /** + * + * BRPOP是列表的阻塞式(blocking)弹出原语。 + * =================================== + * 类似 Gearman 等待移除 + * =================================== + * @param array $keys + * @param Integer $timeout + * + */ + public function brPop(array $keys, $timeout = 2) + { + return $this->redis->brPop($keys, (int)$timeout); + } + + /** + * TODO + * 返回列表key的长度。 + */ + public function llen() + { + + } + + /** + * + * 返回列表key中指定区间内的元素,区间以偏移量start和stop指定。 + * @param String $key + * @param Integer $start + * @param Integer $end + * @return array + */ + public function lRange($key, $start = 0, $end = 0) + { + return $this->redis->lRange($key, (int)$start, (int)$end); + } + + /** + * + * 根据参数count的值,移除列表中与参数value相等的元素 + * ============================================ + * count的值可以是以下几种: + * count > 0: 从表头开始向表尾搜索,移除与value相等的元素,数量为count + * count < 0: 从表尾开始向表头搜索,移除与value相等的元素,数量为count的绝对值 + * count = 0: 移除表中所有与value相等的值 + * ============================================ + * @param String $key + * @param String $value + * @param Integer $count + * @return Integer + */ + public function lRem($key, $value, $count) + { + $this->redis->lRem((string)$key, (string)$value, (int)$count); + } + + /** + * + * 将列表key下标为index的元素的值甚至为value + * (当index参数超出范围,或对一个空列表(key不存在)进行LSET时,返回一个错误) + * @param String $key + * @param Integer $index + * @param String $value + * @return bool + */ + public function lSet($key, $index, $value) + { + return $this->redis->lSet((string)$key, (int)$index, (string)$value); + } + + /** + * + * 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 + * @param String $key + * @param Integer $start + * @param Integer $stop + * @return bool + */ + public function lTrim($key, $start, $stop) + { + return $this->redis->lTrim((string)$key, (int)$start, (int)$stop); + } + + /** + * + * 返回列表key中,下标为index的元素 + * @param String $key + * @param Integer $index + * @return bool or nil + */ + public function lGet($key, $index) + { + return $this->redis->lGet((string)$key, (int)$index); + } + + ################################################################## + # SET + ################################################################## + /** + * + * 将一个或多个member元素加入到集合key当中,已经存在于集合的member元素将被忽略 + * @param String $key + * @param Mixed $value + * @return bool + */ + public function sAdd($skey, $value) + { + return $this->redis->sAdd($skey, $value); + } + + /** + * + * ( 扩展 ) 将一个或多个member元素加入到集合key当中,已经存在于集合的member元素将被忽略 + * @param String $key + * @param Mixed $value + * @param Integer $expiration + * @return bool + */ + public function sAdd2($skey, $value, $expiration = 0) + { + $result = $this->redis->sAdd($skey, $value); + $this->redis->setTimeout($skey, $expiration); + return $result; + } + + /** + * + * 移除集合key中的一个或多个member元素,不存在的member元素会被忽略 + * @param String $key + * @param String $member + * @return bool + */ + public function sRem($skey, $member) + { + return $this->redis->sRem((string)$skey, (string)$member); + } + + /** + * + * 返回集合key中的所有成员 + * @param String $key + * @return array + */ + public function sMembers($skey) + { + return $this->redis->sMembers((string)$skey); + } + + /** + * + * 判断member元素是否是集合key的成员 + * @param String $key + * @param String $value + */ + public function sIsMember($skey, $value) + { + return $this->redis->sIsMember((string)$skey, (string)$value); + } + + /** + * + * 返回集合key的基数(集合中元素的数量) + * @param String $skey + * @return Integer + */ + public function sCard($skey) + { + return $this->redis->sCard((string)$skey); + } + + /** + * + * 将member元素从source集合移动到destination集合 + * @param String $srcKey + * @param String $dstKey + * @param String $member + * @return bool + */ + public function sMove($srcKey, $dstKey, $member) + { + return $this->redis->sMove((string)$srcKey, (string)$dstKey, (string)$member); + } + + /** + * + * 移除并返回集合中的一个随机元素 + * @param String $skey + * @return string or bool + */ + public function sPop($skey) + { + return $this->redis->sPop((string)$skey); + } + + /** + * + * 返回集合中的一个随机元素。 + * @param String $skey + * @return array or nil + */ + public function sRandMember($skey) + { + return $this->redis->sRandMember((string)$skey); + } + + ######################################################## + # 有序集(Sorted Set) + ######################################################## + /** + * + * 将一个或多个member元素及其score值加入到有序集key当中 + * @param String $zKey + * @param Integer $score + * @param String $value + * @return Integer + */ + public function zAdd($zKey, $score, $value) + { + assert(is_string($zKey)) && assert(is_int($score)) && assert(is_string($value)); + return $this->redis->zAdd($zKey, $score, $value); + } + + /** + * + * 移除有序集key中的一个或多个成员,不存在的成员将被忽略 + * @param String $zKey + * @param String $member + * @return Integer + */ + public function zRem($zKey, $member) + { + return $this->redis->zRem((string)$zKey, (string)$member); + } + + /** + * + * 返回有序集key的基数 + * @param String $zKey + * @return Integer + */ + public function zSize($zKey) + { + return $this->redis->zSize((string)$zKey); + } + + /** + * + * 返回有序集key中,score值在min和max之间(默认包括score值等于min或max)的成员 + * @param String $zKey + * @param Integer $start + * @param Integer $end + * @return array + */ + public function zCount($zKey, $start, $end) + { + return $this->redis->zCount($zKey, $start, $end); + } + + /** + * + * 返回有序集key中,成员member的score值 + * @param String $zKey + * @param String $member + * @return String + */ + public function zScore($zKey, $member) + { + return $this->redis->zScore($zKey, $member); + } + + /** + * + * 为有序集key的成员member的score值加上增量increment + * @param String $zKey + * @param Integer $value + * @param String $member + * @return Integer + */ + public function zIncrBy($zKey, $value, $member) + { + return $this->redis->zIncrBy($zKey, $value, $member); + } + + /** + * + * 返回有序集key中,指定区间内的成员 + * @param String $zKey + * @param Integer $start + * @param Integer $end + * @param bool $withscores + * @return bool ( 默认False无键值/True有键值 ) + */ + public function zRange($zKey, $start, $end, $withscores = false) + { + return $this->redis->zRange($zKey, $start, $end, $withscores); + } + + /** + * + * 返回有序集key中,指定区间内的成员 + * 其中成员的位置按score值递减(从大到小)来排列 + * @param String $zKey + * @param Integer $start + * @param Integer $end + * @param bool $withscores + * @return bool ( 默认False无键值/True有键值 ) + */ + public function zRevRange($zKey, $start, $end, $withscores = false) + { + return $this->redis->zRevRange($zKey, $start, $end, $withscores); + } + + /** + * + * 返回有序集key中,所有score值介于min和max之间(包括等于min或max)的成员。有序集成员按score值递增(从小到大 or 从大到小)次序排列 + * @param String $zKey + * @param Integer $start + * @param Integer $end + * @param array $options + * @return array + * ========================================================= + * $redis->zRangeByScore('key', 0, 3); + * array('val0', 'val2') + * $redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE); + * array('val0' => 0, 'val2' => 2) + * $redis->zRangeByScore('key', 0, 3, array('limit' => array(1, 1)); + * array('val2' => 2) + * $redis->zRangeByScore('key', 0, 3, array('limit' => array(1, 1)); + * array('val2') + * $redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE, 'limit' => array(1, 1)); + * array('val2' => 2) + * ========================================================= + * + */ + public function zRangeByScore($zKey, $start, $end, array $options) + { + return $this->redis->zRangeByScore($zKey, $start, $end, $options); + } + + /** + * + * 返回有序集key中成员member的排名。其中有序集成员按score值递增(从小到大 or 从大到小)顺序排列 + * @param String $zKey + * @param String $member + * @param String $order ( desc or asc ) + * @return array + */ + public function zRank($zKey, $member, $order = 'desc') + { + return $order == 'desc' ? $this->redis->zRank($zKey, $member) : $this->redis->zRevRank($zKey, $member); + } + + /** + * 移除有序集key中,指定排名(rank)区间内的所有成员 + * 区间分别以下标参数start和stop指出,包含start和stop在内 + * @param String $zKey + * @param Integer $start + * @param Integer $end + * @return Integer + */ + public function zRemRangeByRank($zKey, $start, $end) + { + return $this->redis->zRemRangeByRank($zKey, $start, $end); + } + + public function tag($tagName) + { + } + + /** + * 移除有序集key中,指定(socre)区间内的所有成员 + * 区间分别以下标参数start和stop指出,包含start和stop在内 + * @param String $zKey + * @param Integer $start + * @param Integer $end + * @return Integer + */ + public function zRemRangeByScore($zKey, $start, $end) + { + return $this->redis->zRemRangeByScore($zKey, $start, $end); + } + + public function zRevRangeByScore($zkey, $start, $end, array $options) + { + return $this->redis->zRevRangeByScore($zkey, $start, $end, $options); + } + + /** + * 发布消息 + * + * @param String $channel + * @param String $message + * @return Integer + */ + public function publish($channel, $message) + { + return $this->redis->publish($channel, $message); + } + + /** + * 订阅消息 + * @param String $channel + * @return String + */ + public function subscribe(array $channel, $callback) + { + return $this->redis->subscribe($channel, $callback); + } + + /** + * 退订 + * @param String $channel + */ + public function unsubscribe($channel) + { + return $this->redis->unsubscribe($channel); + } + + /** + * 按照模式匹配订阅多个频道 + * + * @param String $pattern (如:news.* 可订阅news.开头的所有频道) + */ + public function psubscribe($pattern, $callback) + { + return $this->redis->psubscribe($pattern, $callback); + } + + /** + * 退订给定模式的所有渠道 + * + * @param String $pattern + */ + public function punsubscribe($pattern) + { + return $this->redis->punsubscribe($pattern); + } + + public function pubsub($pattern) + { + return $this->redis->pubsub($pattern); + } +} \ No newline at end of file diff --git a/yaf/Hood/Cache/FileCache.php b/yaf/Hood/Cache/FileCache.php new file mode 100644 index 0000000..2e7bc11 --- /dev/null +++ b/yaf/Hood/Cache/FileCache.php @@ -0,0 +1,437 @@ +<?php + +namespace Hood\Cache; + +use Hood\Core\Root; +use Yaf\Exception; + +/** + * Description of cache + * + * @author 13011908 + */ +class FileCache extends Root implements CacheInterface +{ + + const DEFAULT_EXPIRE = 3600; + + protected static $default = 'file'; + protected static $instances = array(); + protected $_cache_dir; + protected $_tag = null; + + private $section = 'file'; + private $node = 'cache'; + + public function __construct($childNode = null, $node = 'cache', $cachePath = null) + { + if ($cachePath == null) { + $server = $this->getServerHost('cache'); + $this->node = ($node == null ? 'cache' : $node); + $_pathList = $cachePath = $server->getServerConfig($this->section, $this->node); + if ($childNode == null && is_array($cachePath)) { + $cachePath = (sys_get_temp_dir() . 'cached' . DIRECTORY_SEPARATOR); + } else { + $cachePath = $_pathList[$childNode]; + } + } + $this->initFileInfo($cachePath); + } + + private function initFileInfo($directory) + { + try { + $this->_cache_dir = new \SplFileInfo($directory); + } // PHP < 5.3 exception handle + catch (ErrorException $e) { + $this->_cache_dir = $this->_make_directory($directory, 0777, TRUE); + } // PHP >= 5.3 exception handle + catch (UnexpectedValueException $e) { + $this->_cache_dir = $this->_make_directory($directory, 0777, TRUE); + } + + // If the defined directory is a file, get outta here + if ($this->_cache_dir->isFile()) { + throw new Exception('Unable to create cache directory as a file already exists : ' . $this->_cache_dir->getRealPath()); + } + + if (!$this->_cache_dir->isDir()) { + $this->_cache_dir = $this->_make_directory($directory, 0777, TRUE); + } + + // Check the read status of the directory + if (!$this->_cache_dir->isReadable()) { + throw new Exception('Unable to read from the cache directory ' . $this->_cache_dir->getRealPath()); + } + + // Check the write status of the directory + if (!$this->_cache_dir->isWritable()) { + throw new Exception('Unable to write to the cache directory ' . $this->_cache_dir->getRealPath()); + } + } + + public function add($key, $value, $minutes) + { + } + + public function increment($key, $value = 1) + { + $tag = $this->_tag; + if ($get = $this->get($key)) { + if (preg_match('/^\d+$/', $get)) { + $value = intval($get) + $value; + } else { + return false; + } + } + if ($tag) { + $this->tag($tag); + } + return $this->set($key, $value); + } + + public function decrement($key, $value = 1) + { + $tag = $this->_tag; + if ($get = $this->get($key)) { + if (preg_match('/^\d+$/', $get)) { + $value = intval($get) - $value; + } else { + return false; + } + } + if ($tag) { + $this->tag($tag); + } + return $this->set($key, $value); + } + + public function tag($tagName = null) + { + if ($tagName) { + $this->_tag = md5($tagName); + return $this; + } elseif (!empty($this->_tag)) { + $tag = $this->_tag; + unset($this->_tag); + return $tag . DIRECTORY_SEPARATOR; + } else { + return ''; + } + } + + public static function instance($group = NULL) + { + // If there is no group supplied + if ($group === NULL) { + // Use the default setting + $group = self::$default; + } + + if (isset(self::$instances[$group])) { + // Return the current group if initiated already + return self::$instances[$group]; + } + // Create a new cache type instance + self::$instances[$group] = new self(); + // Return the instance + return self::$instances[$group]; + } + + /** + * Retrieve a cached value entry by id. + * + * // Retrieve cache entry from file group + * $data = self::instance('file')->get('foo'); + * + * // Retrieve cache entry from file group and return 'bar' if miss + * $data = self::instance('file')->get('foo', 'bar'); + * + * @param string id of cache to entry + * @param string default value to return if cache miss + * @return mixed + * @throws Cache_Exception + */ + public function get($id, $default = NULL) + { + $filename = self::filename($this->_sanitize_id($id)); + $directory = $this->_resolve_directory($filename); + + // Wrap operations in try/catch to handle notices + try { + // Open file + $file = new \SplFileInfo($directory . $filename); + + // If file does not exist + if (!$file->isFile()) { + // Return default value + return $default; + } else { + // Open the file and parse data + $created = $file->getMTime(); + $data = $file->openFile(); + $lifetime = $data->fgets(); + + // If we're at the EOF at this point, corrupted! + if ($data->eof()) { + return false; + throw new Exception(__METHOD__ . ' corrupted cache file!'); + } + + $cache = ''; + + while ($data->eof() === FALSE) { + $cache .= $data->fgets(); + } + + // Test the expiry + if (($created + (int)$lifetime) < time()) { + // Delete the file + $this->_delete_file($file, NULL, TRUE); + return $default; + } else { + return unserialize($cache); + } + } + } catch (ErrorException $e) { + // Handle ErrorException caused by failed unserialization + if ($e->getCode() === E_NOTICE) { + throw new Exception(__METHOD__ . ' failed to unserialize cached object with message : ' . $e->getMessage()); + } + + // Otherwise throw the exception + throw $e; + } + } + + /** + * Set a value to cache with id and lifetime + * + * $data = 'bar'; + * + * // Set 'bar' to 'foo' in file group, using default expiry + * self::instance('file')->set('foo', $data); + * + * // Set 'bar' to 'foo' in file group for 30 seconds + * self::instance('file')->set('foo', $data, 30); + * + * @param string id of cache entry + * @param string data to set to cache + * @param integer lifetime in seconds + * @return boolean + */ + public function set($id, $data, $lifetime = NULL) + { + $filename = self::filename($this->_sanitize_id($id)); + $directory = $this->_resolve_directory($filename); + // If lifetime is NULL + if ($lifetime === NULL) { + // Set to the default expiry + $lifetime = self::DEFAULT_EXPIRE; + } + + // Open directory + $dir = new \SplFileInfo($directory); + + // If the directory path is not a directory + if (!$dir->isDir()) { + // Create the directory + if (!mkdir($directory, 0777, TRUE)) { + throw new Exception(__METHOD__ . ' unable to create directory : ' . $directory); + } + + // chmod to solve potential umask issues + chmod($directory, 0777); + } + + // Open file to inspect + $resouce = new \SplFileInfo($directory . $filename); + $file = $resouce->openFile('w'); + + try { + $data = $lifetime . "\n" . serialize($data); + $file->fwrite($data, strlen($data)); + return (bool)$file->fflush(); + } catch (ErrorException $e) { + // If serialize through an error exception + if ($e->getCode() === E_NOTICE) { + // Throw a caching error + throw new Exception(__METHOD__ . ' failed to serialize data for caching with message : ' . $e->getMessage()); + } + + // Else rethrow the error exception + throw $e; + } + } + + protected static function filename($string) + { + return sha1($string) . '.cache'; + } + + /** + * Delete a cache entry based on id + * + * // Delete 'foo' entry from the file group + * self::instance('file')->delete('foo'); + * + * @param string id to remove from cache + * @return boolean + */ + public function delete($id) + { + $filename = self::filename($this->_sanitize_id($id)); + $directory = $this->_resolve_directory($filename); + + return $this->_delete_file(new \SplFileInfo($directory . $filename), NULL, TRUE); + } + + /** + * Delete all cache entries. + * + * Beware of using this method when + * using shared memory cache systems, as it will wipe every + * entry within the system for all clients. + * + * // Delete all cache entries in the file group + * self::instance('file')->delete_all(); + * + * @return boolean + */ + public function delete_all() + { + return $this->_delete_file($this->_cache_dir, TRUE); + } + + protected function _delete_file(\SplFileInfo $file, $retain_parent_directory = FALSE, $ignore_errors = FALSE, $only_expired = FALSE) + { + // Allow graceful error handling + try { + // If is file + if ($file->isFile()) { + try { + if ($only_expired === FALSE) { + // We want to delete the file + $delete = TRUE; + } // Otherwise... + else { + // Assess the file expiry to flag it for deletion + $json = $file->openFile('r')->current(); + $data = json_decode($json); + $delete = $data->expiry < time(); + } + + // If the delete flag is set delete file + if ($delete === TRUE) + return @unlink($file->getRealPath()); + else + return FALSE; + } catch (ErrorException $e) { + // Catch any delete file warnings + if ($e->getCode() === E_WARNING) { + throw new Exception(__METHOD__ . ' failed to delete file : ' . $file->getRealPath()); + } + } + } // Else, is directory + elseif ($file->isDir()) { + // Create new DirectoryIterator + $files = new DirectoryIterator($file->getPathname()); + + // Iterate over each entry + while ($files->valid()) { + // Extract the entry name + $name = $files->getFilename(); + + // If the name is not a dot + if ($name != '.' AND $name != '..') { + // Create new file resource + $fp = new \SplFileInfo($files->getRealPath()); + // Delete the file + $this->_delete_file($fp); + } + + // Move the file pointer on + $files->next(); + } + + // If set to retain parent directory, return now + if ($retain_parent_directory) { + return TRUE; + } + + try { + // Remove the files iterator + // (fixes Windows PHP which has permission issues with open iterators) + unset($files); + + // Try to remove the parent directory + return rmdir($file->getRealPath()); + } catch (ErrorException $e) { + // Catch any delete directory warnings + if ($e->getCode() === E_WARNING) { + throw new Exception(__METHOD__ . ' failed to delete directory : ' . $file->getRealPath()); + } + throw $e; + } + } else { + // We get here if a file has already been deleted + return FALSE; + } + } // Catch all exceptions + catch (Exception $e) { + // If ignore_errors is on + if ($ignore_errors === TRUE) { + // Return + return FALSE; + } + // Throw exception + throw $e; + } + } + + protected function _resolve_directory($filename) + { + return $this->_cache_dir->getRealPath() . DIRECTORY_SEPARATOR . $this->tag() . $filename[0] . $filename[1] . DIRECTORY_SEPARATOR; + } + + protected function _sanitize_id($id) + { + // Change slashes and spaces to underscores + return str_replace(array('/', '\\', ' '), '_', $id); + } + + /** + * Makes the cache directory if it doesn't exist. Simply a wrapper for + * `mkdir` to ensure DRY principles + * + * @see http://php.net/manual/en/function.mkdir.php + * @param string directory + * @param string mode + * @param string recursive + * @param string context + * @return \SplFileInfo + * @throws Cache_Exception + */ + protected function _make_directory($directory, $mode = 0777, $recursive = FALSE, $context = NULL) + { + if (!mkdir($directory, $mode, $recursive)) { + throw new Exception('Failed to create the defined cache directory : ' . $directory); + } + chmod($directory, $mode); + + return new \SplFileInfo($directory);; + } + + /** + * Garbage collection method that cleans any expired + * cache entries from the cache. + * + * @return void + */ + public function garbage_collect() + { + $this->_delete_file($this->_cache_dir, TRUE, FALSE, TRUE); + return; + } + +} \ No newline at end of file diff --git a/yaf/Hood/Cache/Memcache.php b/yaf/Hood/Cache/Memcache.php new file mode 100644 index 0000000..32f246b --- /dev/null +++ b/yaf/Hood/Cache/Memcache.php @@ -0,0 +1,196 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/28 + * Time: 下午3:16 + */ + +namespace Hood\Cache; + +use Hood\Core\Root; + +class Memcache extends Root implements CacheInterface +{ + private $mcInstances = array(); + + private $persistentIDs = array(); + + private $section = 'memcached'; + + private $node = 'servers'; + + private $tagName = ''; + + private $prefix = ''; + + private $persistentID = 'hood.cache'; + + private $childNodes = 'hosts'; + + public function __construct($prefix = '', $persistentID = 'hood.cache') + { + parent::__construct(); + $this->prefix = $prefix; + $this->persistentIDs[] = $this->persistentID = $persistentID; + } + + public function init() + { + if (isset($this->mcInstances[$this->persistentID])) { + $mc = $this->mcInstances[$this->persistentID]; + } else { + $instance = new \Memcache(); + $server = $this->getServerHost('cache'); + $_serverHosts = $server->getServerConfig($this->section, $this->node); + $mcServers = $this->_makeHosts($server->getServer($_serverHosts[$this->childNodes], 2)); + foreach ($mcServers as $key => $val) { + $weight = 100; + if (count($val) == 3) { + list($host, $port, $weight) = $val; + } else { + list($host, $port) = $val; + } + $instance->addServer($host, $port, $this->persistentID, $weight); + } + $this->mcInstances[$this->persistentID] = $mc = $instance; + } + return $mc; + } + + /** + * 组织host + * @param array $hosts + * @return array + */ + private function _makeHosts(array $hosts) + { + $_server = array(); + foreach ($hosts as $key => $val) { + $_server[] = explode(':', $val); + } + return $_server; + } + + /** + * 设置mc配置的块节点 + * @param $node + * @return $this + */ + public function setNode($node = null) + { + if ($node != null) $this->node = $node; + return $this; + } + + /** + * 设置子节点 + * @param $childNode + * @return $this + */ + public function setChildNodes($childNode) + { + $this->childNodes = $childNode; + return $this; + } + + /** + * 构建tag + * @param bool $mode + * @return string + */ + private function _makeTag($mode = false) + { + if (empty($this->tagName)) return ''; + $_tagVal = $this->init()->get($this->tagName); + if (empty($_tagVal) && $mode == true) { + $_tagVal = md5(microtime() . mt_rand() . uniqid()); + $this->init()->set($this->tagName, $_tagVal, 0); + } + unset($this->tagName); + return empty($_tagVal) ? '' : $_tagVal . '.'; + } + + /** + * 检索一个元素 + * @param $key + * @param callable $flags + * @return mixed + */ + public function get($key, &$flags = \MEMCACHE_COMPRESSED) + { + return $this->init()->get($this->_makeTag() . $key, $flags); + } + + /** + * 向一个新的key下面增加一个元素 + * @param $key + * @param $value + * @param $expiration + * @return bool + */ + public function add($key, $value, $expiration = 0) + { + return $this->init()->add($this->_makeTag(true) . $key, $value, $expiration); + } + + + /** + * 减小数值元素的值 + * @param $key + * @param int $offset + * @return int + */ + public function decrement($key, $offset = 1) + { + return $this->init()->decrement($this->_makeTag() . $key, $offset); + } + + /** + * @param $key + * @param int $time + * @return bool + */ + public function delete($key, $time = 0) + { + return $this->init()->delete($this->_makeTag() . $key, $time); + } + + + /** + * 增加数值元素的值 + * @param $key + * @param int $offset + * @param int $initialValue + * @param int $expiry + * @return int + */ + public function increment($key, $offset = 1, $initialValue = 0, $expiry = 0) + { + return $this->init()->increment($this->_makeTag() . $key, $offset, $initialValue, $expiry); + } + + + /** + * 设置 + * @param $key + * @param $value + * @param int $expiration + * @return bool + */ + public function set($key, $var, $expire = 0, $flag = \MEMCACHE_COMPRESSED) + { + return $this->init()->set($this->_makeTag(true) . $key, $var, $flag, $expire); + } + + /** + * 设置tag + * @param $tagName + * @return $this + */ + public function tag($tagName) + { + $this->tagName = $tagName; + return $this; + } +} \ No newline at end of file diff --git a/yaf/Hood/Cache/Memcached.php b/yaf/Hood/Cache/Memcached.php new file mode 100644 index 0000000..a708e50 --- /dev/null +++ b/yaf/Hood/Cache/Memcached.php @@ -0,0 +1,356 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/11/23 + * Time: 上午1:39 + */ + +namespace Hood\Cache; + +use Hood\Core\Root; +use Hood\Debug\DebugException; + +class Memcached extends Root implements CacheInterface +{ + private $mcInstances = array(); + + private $persistentIDs = array(); + + private $timeout = 150; + + private $section = 'memcached'; + + private $node = 'servers'; + + private $tagName = ''; + + private $prefix = ''; + + private $persistentID = 'hood.cache'; + + private $childNodes = 'hosts'; + + public function __construct($prefix = '', $persistentID = 'hood.cache') + { + parent::__construct(); + $this->prefix = $prefix; + $this->persistentIDs[] = $this->persistentID = $persistentID; + } + + /** + * 设置子节点 + * @param $childNode + * @return $this + */ + public function setChildNodes($childNode) + { + $this->childNodes = $childNode; + return $this; + } + + /** + * 设置前缀 + * @param $prefix + * @return $this + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + return $this; + } + + /** + * 设置共享连接ID + * @param $persistentID + * @return $this + */ + public function setPersistentID($persistentID) + { + $this->persistentID = $persistentID; + return $this; + } + + /** + * @param $persistentID + * @return \Memcached + * @throws \Hood\Debug\DebugException + */ + private function init() + { + if (isset($this->mcInstances[$this->persistentID])) { + $mc = $this->mcInstances[$this->persistentID]; + } else { + $instance = new \Memcached(); + $instance->setOption(\Memcached::OPT_PREFIX_KEY, $this->prefix); + $instance->setOption(\Memcached::OPT_DISTRIBUTION, \Memcached::DISTRIBUTION_CONSISTENT); // 开启一致性哈希 取模(默认)/ 一致性 + $instance->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);//ketama算法兼容 设置为md5并且分布算法将会 采用带有权重的一致性hash分布 + $instance->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->timeout); + if (count($instance->getServerList()) < 1) { + $server = $this->getServerHost('cache'); + $_serverHosts = $server->getServerConfig($this->section, $this->node); + if (empty($_serverHosts[$this->childNodes])) { + throw new DebugException('Memcache Host Config is Null.'); + } + $mcServers = $this->_makeHosts($server->getServer($_serverHosts[$this->childNodes], 2)); + $instance->addServers($mcServers); + unset($mcServers); + } + $this->mcInstances[$this->persistentID] = $mc = $instance; + } + return $mc; + } + + + /** + * 设置mc配置的块 + * @param $section + * @return $this + */ + public function setSection($section) + { + $this->section = $section; + return $this; + } + + /** + * 设置mc配置的块节点 + * @param $node + * @return $this + */ + public function setNode($node = null) + { + if ($node != null) $this->node = $node; + return $this; + } + + /** + * 组织host + * @param array $hosts + * @return array + */ + private function _makeHosts(array $hosts) + { + $_server = array(); + foreach ($hosts as $key => $val) { + $_server[] = explode(':', $val); + } + return $_server; + } + + /** + * 构建tag + * @param bool $mode + * @return string + */ + private function _makeTag($mode = false) + { + if (empty($this->tagName)) return ''; + $_tagVal = $this->init()->get($this->tagName); + if (empty($_tagVal) && $mode == true) { + $_tagVal = md5(microtime() . mt_rand() . uniqid()); + $this->init()->set($this->tagName, $_tagVal, 0); + } + unset($this->tagName); + return empty($_tagVal) ? '' : $_tagVal . '.'; + } + + /** + * 检索一个元素 + * @param $key + * @param callable $cache_cb + * @param float $cas_token + * @return mixed + */ + public function get($key, $cacheCb = null, &$casToken = null) + { + return $this->init()->get($this->_makeTag() . $key, $cacheCb, $casToken); + } + + /** + * 向一个新的key下面增加一个元素 + * @param $key + * @param $value + * @param $expiration + * @return bool + */ + public function add($key, $value, $expiration = 0) + { + return $this->init()->add($this->_makeTag(true) . $key, $value, $expiration); + } + + /** + * 向已存在元素后追加数据 + * @param $key + * @param $value + * @return bool + */ + public function append($key, $value) + { + return $this->init()->append($this->_makeTag(true) . $key, $value); + } + + /** + * 比较并交换值 + * @param $casToken + * @param $key + * @param $value + * @param int $expiration + * @return bool + */ + public function cas($casToken, $key, $value, $expiration = 0) + { + return $this->init()->cas($casToken, $this->_makeTag(true) . $key, $value, $expiration); + } + + /** + * 减小数值元素的值 + * @param $key + * @param int $offset + * @return int + */ + public function decrement($key, $offset = 1) + { + return $this->init()->decrement($this->_makeTag() . $key, $offset); + } + + /** + * @param $key + * @param int $time + * @return bool + */ + public function delete($key, $time = 0) + { + return $this->init()->delete($this->_makeTag() . $key, $time); + } + + /** + * 删除多个数据 + * @param array $keys + * @param int $time + * @return bool + */ + public function deleteMulti(array $keys, $time = 0) + { + return $this->init()->deleteMulti($this->_makeMultiKey($keys), $time); + } + + /** + * 组合多key 数据 + * @param $keys + * @return array + */ + private function _makeMultiKey($keys, $mode = false) + { + $_keys = array(); + $tag = $this->_makeTag($mode); + foreach ($keys as $key) { + $_keys[] = $tag . $key; + } + return $_keys; + } + + /** + * 请求多个元素 + * @param array $keys + * @param null $withCas + * @param callable $valueCb + * @return bool + */ + public function getDelayed(array $keys, $withCas = null, callable $valueCb = null) + { + return $this->init()->getDelayed($this->_makeMultiKey($keys), $withCas, $valueCb); + } + + /** + * 抓取所有剩余的结果 + * @return array + */ + public function fetchAll() + { + return $this->init()->fetchAll(); + } + + /** + * 检索多个元素 + * @param array $keys + * @param array $cas_tokens + * @param null $flags + * @return mixed + */ + public function getMulti(array $keys, array &$casTokens = null, $flags = null) + { + return $this->init()->getMulti($this->_makeMultiKey($keys), $casTokens, $flags); + } + + /** + * 增加数值元素的值 + * @param $key + * @param int $offset + * @param int $initialValue + * @param int $expiry + * @return int + */ + public function increment($key, $offset = 1, $initialValue = 0, $expiry = 0) + { + return $this->init()->increment($this->_makeTag() . $key, $offset, $initialValue, $expiry); + } + + /** + * 检查memcache是否长连接 + * @return bool + */ + public function isPersistent() + { + return $this->init()->isPersistent(); + } + + /** + * 设置 + * @param $key + * @param $value + * @param int $expiration + * @return bool + */ + public function set($key, $value, $expiration = 0) + { + return $this->init()->set($this->_makeTag(true) . $key, $value, $expiration); + } + + /** + * 设置多个数据 + * @param array $items + * @param int $expiration + * @return bool + */ + public function setMulti(array $items, $expiration = 0) + { + $_items = array(); + $tag = $this->_makeTag(true); + foreach ($items as $key => $val) { + $_items[$tag . $key] = $val; + } + return $this->init()->setMulti($_items, $expiration); + } + + /** + * 设置tag + * @param $tagName + * @return $this + */ + public function tag($tagName) + { + $this->tagName = $tagName; + return $this; + } + + /** + * 清除服务列表 + * @return $this + */ + public function resetServerList() + { + $this->init()->resetServerList(); + return $this; + } +} \ No newline at end of file diff --git a/yaf/Hood/Cache/README.md b/yaf/Hood/Cache/README.md new file mode 100644 index 0000000..4e8439e --- /dev/null +++ b/yaf/Hood/Cache/README.md @@ -0,0 +1,18 @@ +#File + 缓存路径配置在cache.config.ini里面,如果config里面不设置路径的话使用系统默认的临时文件路径 + [file] + cache=/tmp/yoho + $file = Cache::File(); + + [file] + cache.path = /tmp/yoho + $file = Cache::File('path'); + + [file] + fileCache.path = /tmp/yoho + $file = Cache::File('path','fileCache'); + + 实现了get、set、del 等方法 + $file = Cache::File('path','cache'); + $file->set('a', '00'); + $file->get('a'); \ No newline at end of file diff --git a/yaf/Hood/Cache/Ssdb/Client.php b/yaf/Hood/Cache/Ssdb/Client.php new file mode 100644 index 0000000..34b8d2c --- /dev/null +++ b/yaf/Hood/Cache/Ssdb/Client.php @@ -0,0 +1,581 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 1/27/15 + * Time: 10:50 PM + */ + +namespace Hood\Cache\Ssdb; + +class Client +{ + private $debug = false; + public $sock = null; + private $_closed = false; + private $recv_buf = ''; + private $_easy = false; + public $last_resp = null; + + function __construct($host, $port, $timeout_ms = 2000) + { + $timeout_f = (float)$timeout_ms / 1000; + $this->sock = @stream_socket_client("$host:$port", $errno, $errstr, $timeout_f); + if (!$this->sock) { + throw new SSDBException("$errno: $errstr"); + } + $timeout_sec = intval($timeout_ms / 1000); + $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000; + @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec); + if (function_exists('stream_set_chunk_size')) { + @stream_set_chunk_size($this->sock, 1024 * 1024); + } + } + + /** + * After this method invoked with yesno=true, all requesting methods + * will not return a Response object. + * And some certain methods like get/zget will return false + * when response is not ok(not_found, etc) + */ + function easy() + { + $this->_easy = true; + } + + function close() + { + if (!$this->_closed) { + @fclose($this->sock); + $this->_closed = true; + $this->sock = null; + } + } + + function closed() + { + return $this->_closed; + } + + private $batch_mode = false; + private $batch_cmds = array(); + + function batch() + { + $this->batch_mode = true; + $this->batch_cmds = array(); + return $this; + } + + function multi() + { + return $this->batch(); + } + + function exec() + { + $ret = array(); + foreach ($this->batch_cmds as $op) { + list($cmd, $params) = $op; + $this->send_req($cmd, $params); + } + foreach ($this->batch_cmds as $op) { + list($cmd, $params) = $op; + $resp = $this->recv_resp($cmd, $params); + $resp = $this->check_easy_resp($cmd, $resp); + $ret[] = $resp; + } + $this->batch_mode = false; + $this->batch_cmds = array(); + return $ret; + } + + function request() + { + $args = func_get_args(); + $cmd = array_shift($args); + return $this->__call($cmd, $args); + } + + private $async_auth_password = null; + + function auth($password) + { + $this->async_auth_password = $password; + return null; + } + + function __call($cmd, $params = array()) + { + $cmd = strtolower($cmd); + if ($this->async_auth_password !== null) { + $pass = $this->async_auth_password; + $this->async_auth_password = null; + $auth = $this->__call('auth', array($pass)); + if ($auth !== true) { + throw new Exception("Authentication failed"); + } + } + + if ($this->batch_mode) { + $this->batch_cmds[] = array($cmd, $params); + return $this; + } + + try { + if ($this->send_req($cmd, $params) === false) { + $resp = new Response('error', 'send error'); + } else { + $resp = $this->recv_resp($cmd, $params); + } + } catch (SSDBException $e) { + if ($this->_easy) { + throw $e; + } else { + $resp = new Response('error', $e->getMessage()); + } + } + + if ($resp->code == 'noauth') { + $msg = $resp->message; + throw new Exception($msg); + } + + $resp = $this->check_easy_resp($cmd, $resp); + return $resp; + } + + private function check_easy_resp($cmd, $resp) + { + $this->last_resp = $resp; + if ($this->_easy) { + if ($resp->not_found()) { + return NULL; + } else if (!$resp->ok() && !is_array($resp->data)) { + return false; + } else { + return $resp->data; + } + } else { + $resp->cmd = $cmd; + return $resp; + } + } + + function multi_set($kvs = array()) + { + $args = array(); + foreach ($kvs as $k => $v) { + $args[] = $k; + $args[] = $v; + } + return $this->__call(__FUNCTION__, $args); + } + + function multi_hset($name, $kvs = array()) + { + $args = array($name); + foreach ($kvs as $k => $v) { + $args[] = $k; + $args[] = $v; + } + return $this->__call(__FUNCTION__, $args); + } + + function multi_zset($name, $kvs = array()) + { + $args = array($name); + foreach ($kvs as $k => $v) { + $args[] = $k; + $args[] = $v; + } + return $this->__call(__FUNCTION__, $args); + } + + function incr($key, $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + function decr($key, $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + function zincr($name, $key, $score = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + function zdecr($name, $key, $score = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + function zadd($key, $score, $value) + { + $args = array($key, $value, $score); + return $this->__call('zset', $args); + } + + function zRevRank($name, $key) + { + $args = func_get_args(); + return $this->__call("zrrank", $args); + } + + function zRevRange($name, $offset, $limit) + { + $args = func_get_args(); + return $this->__call("zrrange", $args); + } + + function hincr($name, $key, $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + function hdecr($name, $key, $val = 1) + { + $args = func_get_args(); + return $this->__call(__FUNCTION__, $args); + } + + private function send_req($cmd, $params) + { + $req = array($cmd); + foreach ($params as $p) { + if (is_array($p)) { + $req = array_merge($req, $p); + } else { + $req[] = $p; + } + } + return $this->send($req); + } + + private function recv_resp($cmd, $params) + { + $resp = $this->recv(); + if ($resp === false) { + return new Response('error', 'Unknown error'); + } else if (!$resp) { + return new Response('disconnected', 'Connection closed'); + } + if ($resp[0] == 'noauth') { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + switch ($cmd) { + case 'dbsize': + case 'ping': + case 'qset': + case 'getbit': + case 'setbit': + case 'countbit': + case 'strlen': + case 'set': + case 'setx': + case 'setnx': + case 'zset': + case 'hset': + case 'qpush': + case 'qpush_front': + case 'qpush_back': + case 'qtrim_front': + case 'qtrim_back': + case 'del': + case 'zdel': + case 'hdel': + case 'hsize': + case 'zsize': + case 'qsize': + case 'hclear': + case 'zclear': + case 'qclear': + case 'multi_set': + case 'multi_del': + case 'multi_hset': + case 'multi_hdel': + case 'multi_zset': + case 'multi_zdel': + case 'incr': + case 'decr': + case 'zincr': + case 'zdecr': + case 'hincr': + case 'hdecr': + case 'zget': + case 'zrank': + case 'zrrank': + case 'zcount': + case 'zsum': + case 'zremrangebyrank': + case 'zremrangebyscore': + if ($resp[0] == 'ok') { + $val = isset($resp[1]) ? intval($resp[1]) : 0; + return new Response($resp[0], $val); + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + case 'zavg': + if ($resp[0] == 'ok') { + $val = isset($resp[1]) ? floatval($resp[1]) : (float)0; + return new Response($resp[0], $val); + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + case 'get': + case 'substr': + case 'getset': + case 'hget': + case 'qget': + case 'qfront': + case 'qback': + if ($resp[0] == 'ok') { + if (count($resp) == 2) { + return new Response('ok', $resp[1]); + } else { + return new Response('server_error', 'Invalid response'); + } + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + break; + case 'qpop': + case 'qpop_front': + case 'qpop_back': + if ($resp[0] == 'ok') { + $size = 1; + if (isset($params[1])) { + $size = intval($params[1]); + } + if ($size <= 1) { + if (count($resp) == 2) { + return new Response('ok', $resp[1]); + } else { + return new Response('server_error', 'Invalid response'); + } + } else { + $data = array_slice($resp, 1); + return new Response('ok', $data); + } + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + break; + case 'keys': + case 'zkeys': + case 'hkeys': + case 'hlist': + case 'zlist': + case 'qslice': + if ($resp[0] == 'ok') { + $data = array(); + if ($resp[0] == 'ok') { + $data = array_slice($resp, 1); + } + return new Response($resp[0], $data); + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + case 'auth': + case 'exists': + case 'hexists': + case 'zexists': + if ($resp[0] == 'ok') { + if (count($resp) == 2) { + return new Response('ok', (bool)$resp[1]); + } else { + return new Response('server_error', 'Invalid response'); + } + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + break; + case 'multi_exists': + case 'multi_hexists': + case 'multi_zexists': + if ($resp[0] == 'ok') { + if (count($resp) % 2 == 1) { + $data = array(); + for ($i = 1; $i < count($resp); $i += 2) { + $data[$resp[$i]] = (bool)$resp[$i + 1]; + } + return new Response('ok', $data); + } else { + return new Response('server_error', 'Invalid response'); + } + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + break; + case 'scan': + case 'rscan': + case 'zscan': + case 'zrscan': + case 'zrange': + case 'zrrange': + case 'hscan': + case 'hrscan': + case 'hgetall': + case 'multi_hsize': + case 'multi_zsize': + case 'multi_get': + case 'multi_hget': + case 'multi_zget': + if ($resp[0] == 'ok') { + if (count($resp) % 2 == 1) { + $data = array(); + for ($i = 1; $i < count($resp); $i += 2) { + if ($cmd[0] == 'z') { + $data[$resp[$i]] = intval($resp[$i + 1]); + } else { + $data[$resp[$i]] = $resp[$i + 1]; + } + } + return new Response('ok', $data); + } else { + return new Response('server_error', 'Invalid response'); + } + } else { + $errmsg = isset($resp[1]) ? $resp[1] : ''; + return new Response($resp[0], $errmsg); + } + break; + default: + return new Response($resp[0], array_slice($resp, 1)); + } + return new Response('error', 'Unknown command: $cmd'); + } + + private function send($data) + { + $ps = array(); + foreach ($data as $p) { + $ps[] = strlen($p); + $ps[] = $p; + } + $s = join("\n", $ps) . "\n\n"; + if ($this->debug) { + echo '> ' . str_replace(array("\r", "\n"), array('\r', '\n'), $s) . "\n"; + } + try { + while (true) { + $ret = @fwrite($this->sock, $s); + if ($ret === false) { + $this->close(); + throw new SSDBException('Connection lost'); + } + $s = substr($s, $ret); + if (strlen($s) == 0) { + break; + } + @fflush($this->sock); + } + } catch (Exception $e) { + $this->close(); + throw new SSDBException($e->getMessage()); + } + return $ret; + } + + private function recv() + { + $this->step = self::STEP_SIZE; + while (true) { + $ret = $this->parse(); + if ($ret === null) { + try { + $data = @fread($this->sock, 1024 * 1024); + if ($this->debug) { + echo '< ' . str_replace(array("\r", "\n"), array('\r', '\n'), $data) . "\n"; + } + } catch (Exception $e) { + $data = ''; + } + if ($data === false || $data === '') { + if (feof($this->sock)) { + $this->close(); + throw new SSDBException('Connection lost'); + } else { + throw new SSDBTimeoutException('Connection timeout'); + } + } + $this->recv_buf .= $data; +# echo "read " . strlen($data) . " total: " . strlen($this->recv_buf) . "\n"; + } else { + return $ret; + } + } + } + + const STEP_SIZE = 0; + const STEP_DATA = 1; + public $resp = array(); + public $step; + public $block_size; + + private function parse() + { + $spos = 0; + $epos = 0; + $buf_size = strlen($this->recv_buf); + // performance issue for large reponse + //$this->recv_buf = ltrim($this->recv_buf); + while (true) { + $spos = $epos; + if ($this->step === self::STEP_SIZE) { + $epos = strpos($this->recv_buf, "\n", $spos); + if ($epos === false) { + break; + } + $epos += 1; + $line = substr($this->recv_buf, $spos, $epos - $spos); + $spos = $epos; + + $line = trim($line); + if (strlen($line) == 0) { // head end + $this->recv_buf = substr($this->recv_buf, $spos); + $ret = $this->resp; + $this->resp = array(); + return $ret; + } + $this->block_size = intval($line); + $this->step = self::STEP_DATA; + } + if ($this->step === self::STEP_DATA) { + $epos = $spos + $this->block_size; + if ($epos <= $buf_size) { + $n = strpos($this->recv_buf, "\n", $epos); + if ($n !== false) { + $data = substr($this->recv_buf, $spos, $epos - $spos); + $this->resp[] = $data; + $epos = $n + 1; + $this->step = self::STEP_SIZE; + continue; + } + } + break; + } + } + + // packet not ready + if ($spos > 0) { + $this->recv_buf = substr($this->recv_buf, $spos); + } + return null; + } +} \ No newline at end of file diff --git a/yaf/Hood/Cache/Ssdb/Response.php b/yaf/Hood/Cache/Ssdb/Response.php new file mode 100644 index 0000000..a2056cc --- /dev/null +++ b/yaf/Hood/Cache/Ssdb/Response.php @@ -0,0 +1,48 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 1/27/15 + * Time: 10:30 PM + */ + +namespace Hood\Cache\Ssdb; + + +class Response +{ + public $cmd; + public $code; + public $data = null; + public $message; + + function __construct($code = 'ok', $data_or_message = null) + { + $this->code = $code; + if ($code == 'ok') { + $this->data = $data_or_message; + } else { + $this->message = $data_or_message; + } + } + + function __toString() + { + if ($this->code == 'ok') { + $s = $this->data === null ? '' : json_encode($this->data); + } else { + $s = $this->message; + } + return sprintf('%-13s %12s %s', $this->cmd, $this->code, $s); + } + + function ok() + { + return $this->code == 'ok'; + } + + function not_found() + { + return $this->code == 'not_found'; + } +} \ No newline at end of file diff --git a/yaf/Hood/Cache/Ssdb/SSDBException.php b/yaf/Hood/Cache/Ssdb/SSDBException.php new file mode 100644 index 0000000..a550044 --- /dev/null +++ b/yaf/Hood/Cache/Ssdb/SSDBException.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 1/27/15 + * Time: 10:51 PM + */ + +namespace Hood\Cache\Ssdb; + + +class SSDBException extends \Exception { + +} \ No newline at end of file diff --git a/yaf/Hood/Cache/Ssdb/SSDBTimeoutException.php b/yaf/Hood/Cache/Ssdb/SSDBTimeoutException.php new file mode 100644 index 0000000..f05463a --- /dev/null +++ b/yaf/Hood/Cache/Ssdb/SSDBTimeoutException.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 1/27/15 + * Time: 10:51 PM + */ + +namespace Hood\Cache\Ssdb; + + +class SSDBTimeoutException extends SSDBException { + +} \ No newline at end of file diff --git a/yaf/Hood/Cache/Ssdb/SimpleSSDB.php b/yaf/Hood/Cache/Ssdb/SimpleSSDB.php new file mode 100644 index 0000000..e65ee53 --- /dev/null +++ b/yaf/Hood/Cache/Ssdb/SimpleSSDB.php @@ -0,0 +1,35 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 1/27/15 + * Time: 11:25 PM + */ + +namespace Hood\Cache\Ssdb; + + +class SimpleSSDB extends Client +{ + + private $node; + + private $childNode; + + function __construct($host, $port, $timeout_ms = 2000) + { + parent::__construct($host, $port, $timeout_ms); + $this->easy(); + } + + public function setNode($node) + { + + } + + public function setChildNodes($childNode) + { + + } + +} \ No newline at end of file diff --git a/yaf/Hood/Concurrent.php b/yaf/Hood/Concurrent.php new file mode 100644 index 0000000..93ad95d --- /dev/null +++ b/yaf/Hood/Concurrent.php @@ -0,0 +1,42 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/16 + * Time: 下午5:05 + */ + +namespace Hood; + +use Hood\Concurrent\Yar; +use Hood\Concurrent\Http; + +class Concurrent +{ + /** + * @param $url + * @return Yar\Client + */ + static public function yarClient($url) + { + return new Yar\Client($url); + } + + /** + * @param $uri + * @return Yar\Concurrent + */ + static public function yarConcurrent($uri) + { + return new Yar\Concurrent($uri); + } + + /** + * + * @return Http\CurlMulti + */ + static public function curlMulti() + { + return new Http\CurlMulti(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Concurrent/ConcurrentInterface.php b/yaf/Hood/Concurrent/ConcurrentInterface.php new file mode 100644 index 0000000..7e4e8d3 --- /dev/null +++ b/yaf/Hood/Concurrent/ConcurrentInterface.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/15 + * Time: 上午2:04 + */ + +namespace Hood\Concurrent; + + +interface ConcurrentInterface { + +} \ No newline at end of file diff --git a/yaf/Hood/Concurrent/Http/Curl.php b/yaf/Hood/Concurrent/Http/Curl.php new file mode 100644 index 0000000..7709e10 --- /dev/null +++ b/yaf/Hood/Concurrent/Http/Curl.php @@ -0,0 +1,71 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/22 + * Time: 下午12:23 + */ + +namespace Hood\Concurrent\Http; + + +class Curl extends CurlAbstract +{ + + /** + * GET方式网络请求 + * @param $url + * @param array $data + * @param int $timeout + * @return mixed + */ + public static function get($url, array $data = array(), $timeout = 20) + { + $ch = curl_init(self::makeUrl($url, $data)); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + curl_close($ch); + return $result; + } + + + /** + * post提交数据 + * @param $url + * @param $data + * @param int $timeout + * @param array $header + * @param array $cookie + * @return mixed + */ + public static function post($url, $data, $timeout = 20, array $header = array(), array $cookie = array()) + { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + if (!empty($header)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $header);// array('Content-Type:application/json;charset=UTF-8')); + } + + if (!empty($cookie)) { + $cookie_str = array(); + foreach ($cookie as $key => $val) { + $cookie_str[] = urlencode($key) . '=' . urlencode($val); + } + curl_setopt($ch, CURLOPT_COOKIE, implode(';', $cookie_str)); + } + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + if (!empty($data)) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + $result = curl_exec($ch); +// $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + return $result; + } + +} diff --git a/yaf/Hood/Concurrent/Http/CurlAbstract.php b/yaf/Hood/Concurrent/Http/CurlAbstract.php new file mode 100644 index 0000000..4aa6945 --- /dev/null +++ b/yaf/Hood/Concurrent/Http/CurlAbstract.php @@ -0,0 +1,29 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/8 + * Time: 下午1:55 + */ + +namespace Hood\Concurrent\Http; + + +class CurlAbstract +{ + public static function makeUrl($url, array $data) + { + $params = ''; + if (!empty($data)) { + $params = http_build_query($data, '', '&'); + } + if (strpos($url, '?') === false) { + $url = $url . '?' . $params; + } else { + if (!empty($params)) { + $url = $url . '&' . $params; + } + } + return $url; + } +} \ No newline at end of file diff --git a/yaf/Hood/Concurrent/Http/CurlMulti.php b/yaf/Hood/Concurrent/Http/CurlMulti.php new file mode 100644 index 0000000..bf0ac57 --- /dev/null +++ b/yaf/Hood/Concurrent/Http/CurlMulti.php @@ -0,0 +1,231 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/7 + * Time: 下午9:15 + */ + +namespace Hood\Concurrent\Http; + +class CurlMulti extends CurlAbstract +{ + + private $_curls = array(); + + private $_handle = NULL; + + private $wait_for_connect = false; + + private $headerData = array(); + + + public function __construct() + { + $this->_handle = curl_multi_init(); + } + + public function __destruct() + { + foreach ($this->_curls as $handle_id => $data) { + curl_multi_remove_handle($this->_handle, $data['handle']); + curl_close($data['handle']); + } + curl_multi_close($this->_handle); + } + + /** + * 设置是否等待 + * @param $wait_for_connect + * @return $this + */ + public function wait($wait_for_connect) + { + $this->wait_for_connect = $wait_for_connect; + return $this; + } + + /** + * 设置header + * @param array $headerData + * @return $this + */ + public function header(array $headerData) + { + $this->headerData = $headerData; + return $this; + } + + /** + * 调用 + * @param $url + * @param $callback + * @param $data + * @return $this + * @throws Exception + */ + public function get($url, $callback, array $data = array()) + { + $ch = curl_init(self::makeUrl($url, $data)); + $this->addHandle($ch, $callback, $data, $this->wait_for_connect); + return $this; + } + + /** + * 调用 + * @param $url + * @param $callback + * @param $data + * @return $this + * @throws Exception + */ + public function post($url, $callback, $data) + { + $this->callRest($url, 'POST', $callback, $data); + return $this; + } + + + /** + * rest 请求方法 + * @param $url + * @param $method + * @param $callback + * @param null $data + * @return $this + * @throws Exception + */ + public function callRest($url, $method, $callback, $data = NULL) + { + switch (strtoupper($method)) { + case 'POST': + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_POST, TRUE); + $this->headerData = array_merge($this->headerData, array("X-HTTP-Method-Override: POST")); + if ($data != null) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + break; + case 'DELETE': + $ch = curl_init(self::makeUrl($url, $data)); + $this->headerData = array_merge($this->headerData, array("X-HTTP-Method-Override: DELETE")); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + break; + case 'PUT': + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + $this->headerData = array_merge($this->headerData, array("X-HTTP-Method-Override: PUT")); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + if ($data != null) { + $params = http_build_query($data, '', '&'); + curl_setopt($ch, CURLOPT_POSTFIELDS, $params); + curl_setopt($ch, CURLOPT_INFILESIZE, strlen($params)); + } + break; + case 'GET': + $ch = curl_init(self::makeUrl($url, $data)); + $this->headerData = array_merge($this->headerData, array("X-HTTP-Method-Override: GET")); + break; + } + $this->addHandle($ch, $callback, $data, $this->wait_for_connect); + return $this; + } + + /** + * 添加一个handle + * @param $curl_handle + * @param $callback + * @param $data + * @param bool $wait_for_connect #是否等待 + * @return bool + * @throws Exception + */ + private function addHandle($curl_handle, $callback, $data, $wait_for_connect = false) + { + if (get_resource_type($curl_handle) !== 'curl' || !is_callable($callback)) { + throw new \Exception("Invalid curl handle or callback"); + } + curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, TRUE); + if (!empty($this->headerData)) { + curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $this->headerData); + } + $this->_curls[(int)$curl_handle] = array( + 'handle' => $curl_handle, + 'callback' => $callback, + 'callback_data' => $data, + ); + curl_multi_add_handle($this->_handle, $curl_handle); + if ($wait_for_connect) { + $this->poll(); + } + return TRUE; + } + + /** + * 移除会话中的hendle + * @param $curl_handle + * @return bool + */ + public function removeHandle($curl_handle) + { + if (!isset($this->_curls[(int)$curl_handle])) { + return FALSE; + } + curl_multi_remove_handle($this->_handle, $curl_handle); + unset($this->_curls[(int)$curl_handle]); + return TRUE; + } + + /** + * 等待所有会话 + * @return bool + */ + public function poll() + { + $still_running = 0; + do { + $result = curl_multi_exec($this->_handle, $still_running); + if ($result == CURLM_OK) { + do { + $messages_in_queue = 0; + $info = curl_multi_info_read($this->_handle, $messages_in_queue); + if ($info && isset($info['handle']) && isset($this->_curls[(int)$info['handle']])) { + $callback_info = $this->_curls[(int)$info['handle']]; + $curl_data = curl_multi_getcontent($info['handle']); + $curl_info = curl_getinfo($info['handle']); + call_user_func($callback_info['callback'], $curl_data, $curl_info); + $this->removeHandle($info['handle']); + curl_close($info['handle']); + } + } while ($messages_in_queue > 0); + } + } while ($result == CURLM_CALL_MULTI_PERFORM && $still_running > 0); + return (boolean)$this->_curls; + } + + /** + * 设置堵塞中的会话超时时间 + * @param float $timeout + * @return bool + */ + public function select($timeout = 1.0) + { + $result = $this->poll(); + if ($result) { + curl_multi_select($this->_handle, $timeout); + $result = $this->poll(); + } + return $result; + } + + /** + * 刷新所有会话超时时间 + * @return bool + */ + public function finish() + { + while ($this->select() === TRUE) { + } + return TRUE; + } +} \ No newline at end of file diff --git a/yaf/Hood/Concurrent/Yar/Client.php b/yaf/Hood/Concurrent/Yar/Client.php new file mode 100644 index 0000000..bffd47f --- /dev/null +++ b/yaf/Hood/Concurrent/Yar/Client.php @@ -0,0 +1,96 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/15 + * Time: 上午2:02 + */ + +namespace Hood\Concurrent\Yar; + +use Hood\Core\Root; + +class Client extends Root +{ + /** + * @var array + */ + private $_instances = array(); + + /** + * @var \Yar_Client + */ + private $client; + + /** + * Set timeout to 3s + * @var int + */ + private $_timeout = 3000; + + /** + * @param $uri + */ + public function __construct($uri) + { + parent::__construct(); + $yarKey = md5($uri); + if (!isset($this->_instances[$yarKey])) { + $this->_instances[$yarKey] = new \Yar_Client($uri); + } + $this->client = $this->_instances[$yarKey]; + } + + /** + * 设置yar参数 + * @param $name + * @param $value + */ + public function setOpt($name, $value) + { + $this->client->setOpt($name, $value); + return $this; + } + + /** + * 数据包类型 + * @param string $packagerType + */ + public function setPackager($packagerType = YAR_PACKAGER_PHP) + { + $this->client->setOpt(YAR_OPT_PACKAGER, $packagerType); + return $this; + } + + /** + * 连接超时(毫秒为单位) + * @param $timeout + */ + public function setConnectTimeout($timeout = 1000) + { + $this->client->setOpt(YAR_OPT_CONNECT_TIMEOUT, $timeout); + return $this; + } + + /** + * Set timeout to 3s + * 处理超时(毫秒为单位) + * @param $timeout + * @return $this + */ + public function setTimeout($timeout = 3000) + { + $this->client->setOpt(YAR_OPT_TIMEOUT, $timeout); + return $this; + } + + /** + * @param $name + * @param array $arguments + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->client->__call($method, $parameters); + } +} \ No newline at end of file diff --git a/yaf/Hood/Concurrent/Yar/Concurrent.php b/yaf/Hood/Concurrent/Yar/Concurrent.php new file mode 100644 index 0000000..d4fca48 --- /dev/null +++ b/yaf/Hood/Concurrent/Yar/Concurrent.php @@ -0,0 +1,91 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/15 + * Time: 上午2:02 + */ + +namespace Hood\Concurrent\Yar; + +use Hood\Core\Root; + +class Concurrent extends Root +{ + private $uri; + + public function __construct($uri) + { + parent::__construct(); + $this->uri = $uri; + } + + /** + * 注册一个并行的服务调用 + * @param $uri + * @param $method + * @param $parameters + * @param string $callback + */ + public function call($uri, $method, $parameters, $callback = '') + { + \Yar_Concurrent_Client::call($uri, $method, $parameters, $callback); + } + + /** + * 链式调用 + * @param $method + * @param $parameters + * @param string $callback + * @return $this + */ + public function calls($method, $parameters, $callback = '') + { + $this->call($this->uri, $method, $parameters, $callback); + return $this; + } + + /** + * 发送所有注册的并行调用 + * @param string $callback + * @param callable $errorCallback + */ + public function loop($callback = '', $errorCallback = '') + { + \Yar_Concurrent_Client::loop($callback, $errorCallback); + } + + /** + * 缓存tag + * @param $tagName + * @return $this + */ + public function tag($tagName) + { + $this->_cacheTagName = $tagName; + return $this; + } + + /** + * 缓存key + * @param string $key + * @param null $prefix + * @return $this + */ + public function key($key) + { + $this->_cacheKey = (string)$key; + return $this; + } + + /** + * 缓存时间 + * @param $expire + * @return $this + */ + public function expire($expire) + { + $this->_cacheExpire = (int)$expire; + return $this; + } +} \ No newline at end of file diff --git a/yaf/Hood/Cookie.php b/yaf/Hood/Cookie.php new file mode 100644 index 0000000..727442a --- /dev/null +++ b/yaf/Hood/Cookie.php @@ -0,0 +1,56 @@ +<?php +namespace Hood; + +use Hood\Debug\DebugException; + +class Cookie +{ + /** + * 取得所有的cookie值 + * + * @return array + * @since 0.2.2 + */ + public static function all() + { + return $_COOKIE; + } + + /** + * @param $name + * @param string $default + * @return string + * @throws DebugException + */ + public static function get($name, $default = '') + { + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new DebugException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + return isset ($_COOKIE [$name]) ? $_COOKIE [$name] : $default; + } + + /** + * 设置cookie + * @param string $name + * @param string $domain + * @param string $value + * @param int $expire (0:Session、-1:删除、time():过期时间 ) + * @param string $path + * @param bool $httponly + * @param bool $secureAuto + */ + public static function set($name, $value, $minutes = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new DebugException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + $expire = $minutes; + if ($minutes == 0) { + $expire = 0; + } else if ($minutes == -1) { + $expire = time() - 3600; + } + setcookie($name, $value, $expire, $path, $domain, (bool)$secure, $httpOnly); + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Root.php b/yaf/Hood/Core/Root.php new file mode 100644 index 0000000..86a0695 --- /dev/null +++ b/yaf/Hood/Core/Root.php @@ -0,0 +1,113 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/7 + * Time: 上午12:45 + */ + +namespace Hood\Core; + +use Hood\Core\Server; + +class Root +{ + + /** + * server file + * @var String + */ + protected $serviceFile; + + /** + * 应用环境 + * @var string + */ + protected $applicationEnv; + + + /** + * 缓存时间 + * @var int + */ + protected $_cacheExpire = 3600; + + /** + * 缓存 + * @var bool + */ + protected $_cacheStatus = true; + + /** + * 缓存key + * @var null + */ + protected $_cacheKey = null; + + /** + * 缓存tagName + * @var string + */ + protected $_cacheTagName = ''; + + + /** + * 删除多个缓存tag + */ + protected $_delTags = array(); + + + public function __construct() + { + defined('APPLICATION_ENV') || define('APPLICATION_ENV', 'developer'); + defined('APPLICATION_SYSTEM_CONFIG') || define('APPLICATION_SYSTEM_CONFIG', '/Data/Code/SystemConfig'); + } + + /** + * 获取应用环境 + * @return string + */ + public function getApplicationEnv() + { + return $this->applicationEnv; + } + + /** + * 设置应用环境 + * @param $env + * @return $this + */ + public function setApplicationEnv($env) + { + define('APPLICATION_ENV', $env); + return $this; + } + + /** + * + * @param $configFile + * @return $this + */ + public function setApplicationSystemConfig($configFile) + { + define('APPLICATION_SYSTEM_CONFIG', $configFile); + return $this; + } + + /** + * 获取server + * @param $serviceName + * @param string $suffix + * @return Server + */ + public function getServerHost($serviceName, $suffix = 'config.ini') + { + $serviceFileArray = array( + $serviceName, + APPLICATION_ENV, + $suffix + ); + $this->serviceFile = $serviceFile = APPLICATION_SYSTEM_CONFIG . DIRECTORY_SEPARATOR . implode('.', $serviceFileArray); + return new Server($serviceFile); + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Security/AuthCode.php b/yaf/Hood/Core/Security/AuthCode.php new file mode 100644 index 0000000..d7b99cb --- /dev/null +++ b/yaf/Hood/Core/Security/AuthCode.php @@ -0,0 +1,376 @@ +<?php + +namespace Hood\Core\Security; +/** + * Class AuthCode + * @package Hood\Core\Security + */ +class AuthCode +{ + + /** + * 验证编码 + * + * @param String $string + * @param String $operation + * @param Integer $expiry + * @param String $key + * @return String + */ + private static function auth($string, $key, $expiry = 0, $operation = 'decode') + { + $ckey_length = 4; + $key = md5($key); + $keya = md5(substr($key, 0, 16)); + $keyb = md5(substr($key, 16, 16)); + $keyc = $ckey_length ? ($operation == 'decode' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : ''; + $cryptkey = $keya . md5($keya . $keyc); + $key_length = strlen($cryptkey); + $string = $operation == 'decode' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string; + $string_length = strlen($string); + + $result = ''; + $box = range(0, 255); + + $rndkey = array(); + for ($i = 0; $i <= 255; $i++) { + $rndkey[$i] = ord($cryptkey[$i % $key_length]); + } + + for ($j = $i = 0; $i < 256; $i++) { + $j = ($j + $box[$i] + $rndkey[$i]) % 256; + $tmp = $box[$i]; + $box[$i] = $box[$j]; + $box[$j] = $tmp; + } + + for ($a = $j = $i = 0; $i < $string_length; $i++) { + $a = ($a + 1) % 256; + $j = ($j + $box[$a]) % 256; + $tmp = $box[$a]; + $box[$a] = $box[$j]; + $box[$j] = $tmp; + $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); + } + + if ($operation == 'decode') { + if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) { + return substr($result, 26); + } else { + return false; + } + } else { + return $keyc . str_replace('=', '', base64_encode($result)); + } + } + + /** + * 解密 + * + * @param String $string + * @param String $key + * @param Integer $expiry + * @return String + */ + public static function decode($string, $key, $expiry = 0) + { + return self::auth($string, $key, $expiry, __FUNCTION__); + } + + /** + * 加密 + * + * @param String $string + * @param String $key + * @param Integer $expiry + * @return String + */ + public static function encode($string, $key, $expiry = 0) + { + return self::auth($string, $key, $expiry, __FUNCTION__); + } + + /** + * 获取编码 + * + * @param String $plaintext + * @param String $salt + * @param String $encryption + * @param bool $show_encrypt + * @return String + */ + public static function getCrypted($plaintext, $salt = '', $encryption = 'md5-hex', $show_encrypt = false) + { + $salt = self::getSalt($encryption, $salt, $plaintext); + switch ($encryption) { + case 'plain' : + return $plaintext; + case 'sha' : + $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext)); + return ($show_encrypt) ? '{SHA}' . $encrypted : $encrypted; + case 'crypt' : + case 'crypt-des' : + case 'crypt-md5' : + case 'crypt-blowfish' : + return ($show_encrypt ? '{crypt}' : '') . crypt($plaintext, $salt); + case 'md5-base64' : + $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext)); + return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted; + case 'ssha' : + $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext . $salt) . $salt); + return ($show_encrypt) ? '{SSHA}' . $encrypted : $encrypted; + case 'smd5' : + $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext . $salt) . $salt); + return ($show_encrypt) ? '{SMD5}' . $encrypted : $encrypted; + case 'aprmd5' : + $length = strlen($plaintext); + $context = $plaintext . '$apr1$' . $salt; + $binary = self::_bin(md5($plaintext . $salt . $plaintext)); + for ($i = $length; $i > 0; $i -= 16) { + $context .= substr($binary, 0, ($i > 16 ? 16 : $i)); + } + for ($i = $length; $i > 0; $i >>= 1) { + $context .= ($i & 1) ? chr(0) : $plaintext[0]; + } + $binary = self::_bin(md5($context)); + + for ($i = 0; $i < 1000; $i++) { + $new = ($i & 1) ? $plaintext : substr($binary, 0, 16); + if ($i % 3) { + $new .= $salt; + } + if ($i % 7) { + $new .= $plaintext; + } + $new .= ($i & 1) ? substr($binary, 0, 16) : $plaintext; + $binary = self::_bin(md5($new)); + } + + $p = array(); + for ($i = 0; $i < 5; $i++) { + $k = $i + 6; + $j = $i + 12; + if ($j == 16) { + $j = 5; + } + $p[] = self::_toAPRMD5((ord($binary[$i]) << 16) | (ord($binary[$k]) << 8) | (ord($binary[$j])), 5); + } + + return '$apr1$' . $salt . '$' . implode('', $p) . self::_toAPRMD5(ord($binary[11]), 3); + + case 'md5-hex' : + + default : + $encrypted = ($salt) ? md5($plaintext . $salt) : md5($plaintext); + return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted; + } + } + + /** + * Enter description here... + * + * @param unknown_type $encryption + * @param unknown_type $seed + * @param unknown_type $plaintext + * @return unknown + */ + public static function getSalt($encryption = 'md5-hex', $seed = '', $plaintext = '') + { + switch ($encryption) { + case 'crypt' : + case 'crypt-des' : + if ($seed) { + return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 2); + } else { + return substr(md5(mt_rand()), 0, 2); + } + break; + + case 'crypt-md5' : + if ($seed) { + return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 12); + } else { + return '$1$' . substr(md5(mt_rand()), 0, 8) . '$'; + } + break; + + case 'crypt-blowfish' : + if ($seed) { + return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 16); + } else { + return '$2$' . substr(md5(mt_rand()), 0, 12) . '$'; + } + break; + + case 'ssha' : + if ($seed) { + return substr(preg_replace('|^{SSHA}|', '', $seed), -20); + } else { + return mhash_keygen_s2k(MHASH_SHA1, $plaintext, substr(pack('h*', md5(mt_rand())), 0, 8), 4); + } + break; + + case 'smd5' : + if ($seed) { + return substr(preg_replace('|^{SMD5}|', '', $seed), -16); + } else { + return mhash_keygen_s2k(MHASH_MD5, $plaintext, substr(pack('h*', md5(mt_rand())), 0, 8), 4); + } + break; + + case 'aprmd5' : + /* 64 characters that are valid for APRMD5 passwords. */ + $APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + if ($seed) { + return substr(preg_replace('/^\$apr1\$(.{8}).*/', '\\1', $seed), 0, 8); + } else { + $salt = ''; + for ($i = 0; $i < 8; $i++) { + $salt .= $APRMD5{rand(0, 63)}; + } + return $salt; + } + break; + + default : + $salt = ''; + if ($seed) { + $salt = $seed; + } + return $salt; + break; + } + } + + public static function genRandom($length = 8) + { + $salt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + $len = strlen($salt); + $makepass = ''; + + $stat = @stat(__FILE__); + if (empty($stat) || !is_array($stat)) + $stat = array( + php_uname() + ); + + mt_srand(crc32(microtime() . implode('|', $stat))); + + for ($i = 0; $i < $length; $i++) { + $makepass .= $salt[mt_rand(0, $len - 1)]; + } + + return $makepass; + } + + public static function _toAPRMD5($value, $count) + { + /* 64 characters that are valid for APRMD5 passwords. */ + $APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + $aprmd5 = ''; + $count = abs($count); + while (--$count) { + $aprmd5 .= $APRMD5[$value & 0x3f]; + $value >>= 6; + } + return $aprmd5; + } + + public static function _bin($hex) + { + $bin = ''; + $length = strlen($hex); + for ($i = 0; $i < $length; $i += 2) { + $tmp = sscanf(substr($hex, $i, 2), '%x'); + $bin .= chr(array_shift($tmp)); + } + return $bin; + } + + /** + * 组合一个安全的密码 + * @param $code + * @return String + */ + public static function makePass($code) + { + $salt = self::genRandom(32); + $crypt = self::getCrypted($code . $salt, $salt); + return $crypt . ':' . $salt; + } + + /** + * 验证密码 + * + * @param String $inputPassword + * @param String $password + * @return bool + */ + public static function authPassword($inputPassword, $password) + { + if (empty($password) || empty($inputPassword)) { + return false; + } + $passwordList = explode(':', trim($password)); + if (count($passwordList) != 2) { + return false; + } + list($crypt, $salt) = $passwordList; + $decode = self::getCrypted($inputPassword . $salt, $salt); + if ($crypt != $decode) { + return false; + } + return true; + } + + ############################ 加密规则 ######################## + /** + * 排序参数 + * @param array $package + * @return array + */ + static function packageSort(array $package) + { + ksort($package); + reset($package); + return $package; + } + + /** + * 组合签名 + * @param array $package + * @return string + */ + static function makeSign(array $package) + { + $packageList = array(); + foreach ($package as $key => $val) { + $packageList[] = trim($key . '=' . $val); + } + return strtolower(md5(implode('&', $packageList))); + } + + /** + * 获取签名 + * + * @param array $package + * @return string + */ + static function getSign(array $package) + { + $package = self::packageSort($package); + return self::makeSign($package); + } + + /** + * 校验签名 + * @param $submitSign + * @param $makeSign + * @return bool + */ + static function verifySign($submitSign, $makeSign) + { + return strtolower($submitSign) == strtolower($makeSign); + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Server.php b/yaf/Hood/Core/Server.php new file mode 100644 index 0000000..cb6c06a --- /dev/null +++ b/yaf/Hood/Core/Server.php @@ -0,0 +1,401 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/7 + * Time: 上午12:11 + */ + +namespace Hood\Core; + +use Hood\Debug\DebugException; + +class Server extends Root +{ + /** + * 服务器随机选择模式 + * @var Integer + */ + const SERVER_SELECT_MODEL_RAND = 1; + + /** + * 服务器选择静态 + * @var Integer + */ + const SERVER_SELECT_MODEL_STATIC = 2; + + /** + * @var string + */ + protected $_sectionSeparator = ':'; + + /** + * 字符分割 + * @var string + */ + protected $_nestSeparator = '.'; + + /** + * 服务器选择模式 + * @var int + */ + private $serverSelectModel = 1; + + /** + * ini 数组 + * @var array + */ + private $iniArray = array(); + + /** + * 检查服务状态 + * @var bool + */ + private $checkServerStatus = true; + + + public function __construct($filename) + { + $this->iniArray = $this->_loadIniFile($filename); + } + + /** + * 设置服务器选择模式 + * @param $model + * @return $this + */ + public function setSelectModel($model) + { + $this->serverSelectModel = $model; + return $this; + } + + /** + * 获取服务器地址 + * @param $section + * @param $node + * @param int $model + * @return array|mixed + * @throws DebugException + */ + public function getServerConfig($section, $node = null) + { + $sectionArray = $this->_processSection($section); + if ($node == null) { + return $sectionArray; + } elseif (isset($sectionArray[$node])) { + return $sectionArray[$node]; + } + return array(); + + } + + /** + * 选择服务器 + * @param $servers + * @param $model + * @return array|mixed + * @throws DebugException + */ + public function getServer($servers, $model = 1) + { + $serverArray = $this->_parseServer($servers); + switch ($model) { + case 1: + if (count($serverArray) <= 1) { + $servers = $serverArray; + } else { + $servers = $this->_randServer($serverArray); + } + break; + case 2: + $servers = $serverArray; + break; + default: + throw new DebugException('Server select model not ' . $model); + } + return $servers; + } + + /** + * 获取服务器Map + * @param $section + * @param $node + * @param int $model + * @return array + * @throws DebugException + */ + public function getServerMap($servers, $model = 1) + { + $_servers = $this->getServer($servers, $model); + if (empty($_servers)) { + return array(); + } + $_serversMap = array(); + if ($model == 1 && count($_servers) == 1) { + $_serversMap = $this->_processHost($_servers[0]); + } elseif ($model == 2) { + foreach ($_servers as $key => $host) { + $_serversMap[] = $this->_processHost($host); + } + } + return $_serversMap; + } + + /** + * 获取所有配置Map + * @param $section + * @param $node + * @return array + */ + public function getSectionConfig($section, $node) + { + $servers = $this->getServerConfig($section, $node); + $_serversMap = array(); + foreach ($servers as $key => $host) { + $_serversMap[] = $this->_processHost($host); + } + return $_serversMap; + } + + /** + * 随机&权重随机 + * @param array $server + * @return mixed + */ + private function _randServer(array $server) + { + if (substr_count($server[0], ':') == 2) { + $_domain = array(); + foreach ($server as $key => $domain) { + $_domain[] = $this->_processHost($domain); + } + $_server = $this->_countWeight($_domain); + $_server = $_server['host'] . ':' . $_server['port'] . ':' . $_server['weight']; + } else { + $_server = $server[array_rand($server)]; + } + return $_server; + } + + /** + * 权重计算 + * @param array $data + * @return mixed + */ + private function _countWeight(array $data) + { + $weight = 0; + $tempArray = array(); + foreach ($data as $v) { + $weight += (int)$v['weight']; + for ($i = 0; $i < $v['weight']; $i++) { + $tempArray[] = $v;//放大数组 + } + } + $int = mt_rand(0, $weight - 1);//获取一个随机数 + return $tempArray[$int]; + } + + /** + * 解析host + * @param $domain + * @return array + */ + private function _processHost($domain) + { + $_hostMap = array(); + $domainArray = explode(':', $domain); + switch (count($domainArray)) { + case 2 : + list($host, $port) = $domainArray; + $_hostMap = array( + 'host' => (string)$host, + 'port' => (int)intval($port) + ); + break; + case 3 : + list($host, $port, $weight) = $domainArray; + $_hostMap = array( + 'host' => (string)$host, + 'port' => (int)intval($port), + 'weight' => (int)intval($weight) + ); + break; + } + return $_hostMap; + } + + /** + * 设置检测服务状态 + * @param $check + * @return $this + */ + public function setCheckServer($check) + { + $this->checkServerStatus = $check; + return $this; + } + + /** + * 检查服务 + * @param array $servers + * @return array + */ + public function checkServer(array $servers) + { + $_servers = array(); + foreach ($servers as $key => $val) { + $status = $this->ping($val['host'], $val['port']); + if ($status > 0) { + $_servers[] = $val; + } + } + return $_servers; + } + + /** + * 解析Server + * @param $ips + * @return array|mixed + */ + protected function _parseServer($serverString) + { + return explode(',', str_replace(array( + ' ', + "\n" + ), '', $serverString)); + } + + public function getSection($section) + { + return $this->_processSection($section); + } + + public function get($name, $section = null) + { + return $this->_processSection($section); + } + + /** + * 加载ini + * @param $filename + * @return array + * @throws DebugException + */ + private function _loadIniFile($filename) + { + $loaded = $this->_parseIniFile($filename); + $iniArray = array(); + foreach ($loaded as $key => $data) { + $pieces = explode($this->_sectionSeparator, $key); + $thisSection = trim($pieces[0]); + switch (count($pieces)) { + case 1: + $iniArray[$thisSection] = $data; + break; + case 2: + $extendedSection = trim($pieces[1]); + $iniArray[$thisSection] = array_merge(array(';extends' => $extendedSection), $data); + break; + default: + throw new DebugException("Section '$thisSection' may not extend multiple sections in $filename"); + } + } + return $iniArray; + } + + /** + * 解析ini + * @param $filename + * @return array + */ + private function _parseIniFile($filename) + { + if (!file_exists($filename)) { + throw new DebugException($filename . ' Not Find,'); + } + $iniArray = parse_ini_file($filename, true); + if ($iniArray == false) { + throw new DebugException($filename . ' Not Array.'); + } + return $iniArray; + } + + /** + * 解析节点 + * @param $section + * @return array + * @throws DebugException + */ + protected function _processSection($section) + { + $config = array(); + $thisSection = array(); + if (isset($this->iniArray[$section])) { + $thisSection = $this->iniArray[$section]; + } + foreach ($thisSection as $key => $value) { + if (strtolower($key) == ';extends') { + if (isset($this->iniArray[$value])) { + $config = $this->iniArray[$value] + $config; + } else { + throw new DebugException("Parent section '$section' cannot be found"); + } + } else { + $config = $this->_processKey($config, $key, $value); + } + } + return $config; + } + + /** + * 解析键值 + * @param array $config + * @param $key + * @param string $val + * @return array + */ + protected function _processKey(array $config, $key, $val = '') + { + if (strpos($key, $this->_nestSeparator) !== false) { + $pieces = explode($this->_nestSeparator, $key, 2); + if (strlen($pieces[0]) && strlen($pieces[1])) { + if (!isset($config[$pieces[0]])) { + if ($pieces[0] === '0' && !empty($config)) { + $config = array($pieces[0] => $config); + } else { + $config[$pieces[0]] = array(); + } + } + $config[$pieces[0]] = $this->_processKey($config[$pieces[0]], $pieces[1], $val); + } + } else { + $config[$key] = $val; + } + return $config; + } + + /** + * ping + * @param $domain + * @param $port + * @param int $timeout + * @return float|int|mixed + */ + public function ping($domain, $port, $timeout = 3) + { + $starttime = microtime(true); + $file = @fsockopen($domain, $port, $errno, $errstr, $timeout); + $stoptime = microtime(true); + $status = -1; + if ($file) { + stream_set_blocking($file, false);//非堵塞 + stream_set_timeout($file, 3); + fclose($file); + $status = ($stoptime - $starttime) * 1000; + $status = floor($status); + } + return $status; + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Session/CacheSession.php b/yaf/Hood/Core/Session/CacheSession.php new file mode 100644 index 0000000..9ec9b13 --- /dev/null +++ b/yaf/Hood/Core/Session/CacheSession.php @@ -0,0 +1,194 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/25 + * Time: 下午4:47 + */ + +namespace Hood\Core\Session; + +use Hood\Core\Support\Str; +use Hood\Core\Root; +use Hood\Cache\Memcached as mcCache; + +class CacheSession extends Root implements SessionHandlerInterface +{ + /** + * session 前缀 + * @var string + */ + private $sessionPrefix = 'session:'; + + /** + * Session数据就自动删除时间 (默认24小时) + * @var int + */ + private $gcMaxLifeTime = 86400; //1800; + + private $sessionOptions = array( + 'use_cookies', + 'auto_start', + 'serialize_handler', + 'cookie_lifetime', + 'cookie_path', + 'cookie_domain', + 'use_cookies', + 'cache_expire', + 'gc_maxlifetime' + ); + + private $mcCache; + + private $cacheNode = 'memcached'; + + private $childNode = 'session'; + + /** + * 初始化 + */ + public function __construct($sessionName = null, $domain = null) + { + session_set_save_handler( + array(&$this, "open"), + array(&$this, "close"), + array(&$this, "read"), + array(&$this, "write"), + array(&$this, "destroy"), + array(&$this, "gc") + ); + if (version_compare(phpversion(), '5.4.0', '>=')) { + register_shutdown_function('session_write_close'); + } + if ($sessionName != null && is_string($sessionName) && strlen($sessionName) > 1) { + ini_set("session.name", $sessionName); + } + ini_set("session.use_cookies", 1); + ini_set('use_only_cookies', 0); + if ($domain == null) { + ini_set("session.cookie_domain", $this->currentDomain()); + } else { + ini_set("session.cookie_domain", '.' . $domain); + } + ini_set('session.cookie_path', '/'); + ini_set('session.gc_maxlifetime', $this->gcMaxLifeTime); + } + + public function currentDomain() + { + if (!isset($_SERVER['HTTP_HOST'])) { + return ''; + } + if (ip2long($_SERVER['HTTP_HOST']) == false) { + return '.' . preg_replace("/^(.*\.)?([^.]*\..*)$/", "$2", $_SERVER['HTTP_HOST']); + } + return $_SERVER['HTTP_HOST']; + } + + /** + * 设置session缓存节点 + * @param string $node + * @param string $childNode + * @return $this + */ + public function setCacheNode($node = 'memcached', $childNode = 'session') + { + $this->cacheNode = $node; + $this->childNode = $childNode; + return $this; + } + + /** + * @return mcCache + */ + private function cache() + { + if (empty($this->mcCache)) { + $this->mcCache = new mcCache(); + $this->mcCache->setSection($this->cacheNode)->setNode($this->childNode)->setPrefix('qinsession'); + } + return $this->mcCache; + } + + /** + * 设置session参数 + * @param array $options + * @return $this + */ + public function setSessionOptions(array $options) + { + foreach ($options as $key => $val) { + if (isset($this->sessionOptions[$key])) { + ini_set('session.' . $key, $val); + } + } + return $this; + } + + /** + * 打开Session + * @param string $savePath + * @param string $sessionID + * @return bool + */ + public function open($savePath, $sessionID) + { + return true; + } + + /** + * 关闭Session + * @return bool + */ + public function close() + { + return true; + } + + /** + * 读取缓存 + * @param string $sessionID + * @return mixed|string + */ + public function read($sessionID) + { + return $this->cache()->get($this->sessionPrefix . $sessionID); + } + + /** + * 写入Session + * @param string $sessionID + * @param string $sessionData + * @return bool + */ + public function write($sessionID, $sessionData) + { + $return = $this->cache()->set($this->sessionPrefix . $sessionID, $sessionData, $this->gcMaxLifeTime); + return $return; + } + + /** + * 注销Session + * @param int $sessionID + * @return bool + */ + public function destroy($sessionID) + { + return $this->cache()->delete($this->sessionPrefix . $sessionID); + } + + public function gc($maxlifetime) + { + return true; + } + + public function __destruct() + { + session_write_close(); + } + + public function generateSessionId() + { + return sha1(uniqid('', true) . Str::random(25) . microtime(true)); + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Session/FileSession.php b/yaf/Hood/Core/Session/FileSession.php new file mode 100644 index 0000000..5f6c0f8 --- /dev/null +++ b/yaf/Hood/Core/Session/FileSession.php @@ -0,0 +1,156 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/25 + * Time: 下午4:47 + */ + +namespace Hood\Core\Session; + +use Hood\Core\Support\Str; +use Hood\Core\Root; +use Hood\Cache; + +class FileSession extends Root implements SessionHandlerInterface +{ + /** + * session 前缀 + * @var string + */ + private $sessionPrefix = 'session:'; + + /** + * Session数据就自动删除时间 + * @var int + */ + private $gcMaxLifeTime = 1800; + + private $sessionOptions = array( + 'use_cookies', + 'auto_start', + 'serialize_handler', + 'cookie_lifetime', + 'cookie_path', + 'cookie_domain', + 'use_cookies', + 'cache_expire', + 'gc_maxlifetime' + ); + + private $fileCache; + + /** + * 初始化 + */ + public function __construct() + { + session_set_save_handler( + array(&$this, "open"), + array(&$this, "close"), + array(&$this, "read"), + array(&$this, "write"), + array(&$this, "destroy"), + array(&$this, "gc") + ); + if (version_compare(phpversion(), '5.4.0', '>=')) { + register_shutdown_function('session_write_close'); + } + ini_set("session.use_cookies", 1); + ini_set("session.cookie_domain", ".yohobuy.com"); + ini_set('session.cookie_path', '/'); + ini_set('session.gc_maxlifetime', $this->gcMaxLifeTime); + } + + /** + * @return fileCache + */ + private function cache() + { + if (empty($this->fileCache)) { + $this->fileCache = Cache::File(); + } + return $this->fileCache; + } + + /** + * 设置session参数 + * @param array $options + * @return $this + */ + public function setSessionOptions(array $options) + { + foreach ($options as $key => $val) { + if (isset($this->sessionOptions[$key])) { + ini_set('session.' . $key, $val); + } + } + return $this; + } + + /** + * 打开Session + * @param string $savePath + * @param string $sessionID + * @return bool + */ + public function open($savePath, $sessionID) + { + return true; + } + + /** + * 关闭Session + * @return bool + */ + public function close() + { + return true; + } + + /** + * 读取缓存 + * @param string $sessionID + * @return mixed|string + */ + public function read($sessionID) + { + return $this->cache()->get($this->sessionPrefix . $sessionID); + } + + /** + * 写入Session + * @param string $sessionID + * @param string $sessionData + * @return bool + */ + public function write($sessionID, $sessionData) + { + return $this->cache()->set($this->sessionPrefix . $sessionID, $sessionData, $this->gcMaxLifeTime); + } + + /** + * 注销Session + * @param int $sessionID + * @return bool + */ + public function destroy($sessionID) + { + return $this->cache()->delete($this->sessionPrefix . $sessionID); + } + + public function gc($maxlifetime) + { + return true; + } + + public function __destruct() + { + session_write_close(); + } + + public function generateSessionId() + { + return sha1(uniqid('', true) . Str::random(25) . microtime(true)); + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Session/Service.php b/yaf/Hood/Core/Session/Service.php new file mode 100644 index 0000000..a221738 --- /dev/null +++ b/yaf/Hood/Core/Session/Service.php @@ -0,0 +1,191 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/7/29 + * Time: 下午11:36 + */ + +namespace Hood\Core\Session; + + +class Service extends Root implements SessionHandlerInterface +{ + /** + * session 前缀 + * @var string + */ + private $sessionPrefix = 'session:'; + + /** + * Session数据就自动删除时间 + * @var int + */ + private $gcMaxLifeTime = 1800; + + private $sessionOptions = array( + 'use_cookies', + 'auto_start', + 'serialize_handler', + 'cookie_lifetime', + 'cookie_path', + 'cookie_domain', + 'use_cookies', + 'cache_expire', + 'gc_maxlifetime' + ); + + private $mcCache; + + private $cacheNode = 'memcached'; + + private $childNode = 'session'; + + /** + * 初始化 + */ + public function __construct($sessionName = null, $domain = null) + { + session_set_save_handler( + array(&$this, "open"), + array(&$this, "close"), + array(&$this, "read"), + array(&$this, "write"), + array(&$this, "destroy"), + array(&$this, "gc") + ); + if (version_compare(phpversion(), '5.4.0', '>=')) { + register_shutdown_function('session_write_close'); + } + if ($sessionName != null && is_string($sessionName) && strlen($sessionName) > 1) { + ini_set("session.name", $sessionName); + } + ini_set("session.use_cookies", 1); + ini_set('use_only_cookies', 0); + if ($domain == null) { + ini_set("session.cookie_domain", $this->currentDomain()); + } else { + ini_set("session.cookie_domain", '.' . $domain); + } + ini_set('session.cookie_path', '/'); + ini_set('session.gc_maxlifetime', $this->gcMaxLifeTime); + } + + public function currentDomain() + { + if (!isset($_SERVER['HTTP_HOST'])) { + return ''; + } + if (ip2long($_SERVER['HTTP_HOST']) == false) { + return '.' . preg_replace("/^(.*\.)?([^.]*\..*)$/", "$2", $_SERVER['HTTP_HOST']); + } + return $_SERVER['HTTP_HOST']; + } + + /** + * 设置session缓存节点 + * @param string $node + * @param string $childNode + * @return $this + */ + public function setCacheNode($node = 'memcached', $childNode = 'session') + { + $this->cacheNode = $node; + $this->childNode = $childNode; + return $this; + } + + /** + * @return mcCache + */ + private function cache() + { + if (empty($this->mcCache)) { + $this->mcCache = new mcCache(); + $this->mcCache->setSection($this->cacheNode)->setNode($this->childNode); + } + return $this->mcCache; + } + + /** + * 设置session参数 + * @param array $options + * @return $this + */ + public function setSessionOptions(array $options) + { + foreach ($options as $key => $val) { + if (isset($this->sessionOptions[$key])) { + ini_set('session.' . $key, $val); + } + } + return $this; + } + + /** + * 打开Session + * @param string $savePath + * @param string $sessionID + * @return bool + */ + public function open($savePath, $sessionID) + { + return true; + } + + /** + * 关闭Session + * @return bool + */ + public function close() + { + return true; + } + + /** + * 读取缓存 + * @param string $sessionID + * @return mixed|string + */ + public function read($sessionID) + { + return $this->cache()->get($this->sessionPrefix . $sessionID); + } + + /** + * 写入Session + * @param string $sessionID + * @param string $sessionData + * @return bool + */ + public function write($sessionID, $sessionData) + { + $return = $this->cache()->set($this->sessionPrefix . $sessionID, $sessionData, $this->gcMaxLifeTime); + return $return; + } + + /** + * 注销Session + * @param int $sessionID + * @return bool + */ + public function destroy($sessionID) + { + return $this->cache()->delete($this->sessionPrefix . $sessionID); + } + + public function gc($maxlifetime) + { + return true; + } + + public function __destruct() + { + session_write_close(); + } + + public function generateSessionId() + { + return sha1(uniqid('', true) . Str::random(25) . microtime(true)); + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Session/SessionHandlerInterface.php b/yaf/Hood/Core/Session/SessionHandlerInterface.php new file mode 100644 index 0000000..627bd1a --- /dev/null +++ b/yaf/Hood/Core/Session/SessionHandlerInterface.php @@ -0,0 +1,97 @@ +<?php + +/** + * Created by PhpStorm. + * User: liuziyang + * Date: 14-2-23 + * Time: 17:45 + */ +namespace Hood\Core\Session; + +interface SessionHandlerInterface +{ + + /** + * PHP >= 5.4.0<br/> + * Close the session + * @link http://php.net/manual/en/sessionhandlerinterafce.close.php + * @return bool <p> + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + * </p> + */ + public function close(); + + /** + * PHP >= 5.4.0<br/> + * Destroy a session + * @link http://php.net/manual/en/sessionhandlerinterafce.destroy.php + * @param int $session_id The session ID being destroyed. + * @return bool <p> + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + * </p> + */ + public function destroy($session_id); + + /** + * PHP >= 5.4.0<br/> + * Cleanup old sessions + * @link http://php.net/manual/en/sessionhandlerinterafce.gc.php + * @param int $maxlifetime <p> + * Sessions that have not updated for + * the last maxlifetime seconds will be removed. + * </p> + * @return bool <p> + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + * </p> + */ + public function gc($maxlifetime); + + /** + * PHP >= 5.4.0<br/> + * Initialize session + * @link http://php.net/manual/en/sessionhandlerinterafce.open.php + * @param string $save_path The path where to store/retrieve the session. + * @param string $session_id The session id. + * @return bool <p> + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + * </p> + */ + public function open($save_path, $session_id); + + + /** + * PHP >= 5.4.0<br/> + * Read session data + * @link http://php.net/manual/en/sessionhandlerinterafce.read.php + * @param string $session_id The session id to read data for. + * @return string <p> + * Returns an encoded string of the read data. + * If nothing was read, it must return an empty string. + * Note this value is returned internally to PHP for processing. + * </p> + */ + public function read($session_id); + + /** + * PHP >= 5.4.0<br/> + * Write session data + * @link http://php.net/manual/en/sessionhandlerinterafce.write.php + * @param string $session_id The session id. + * @param string $session_data <p> + * The encoded session data. This data is the + * result of the PHP internally encoding + * the $_SESSION superglobal to a serialized + * string and passing it as this parameter. + * Please note sessions use an alternative serialization method. + * </p> + * @return bool <p> + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + * </p> + */ + public function write($session_id, $session_data); +} \ No newline at end of file diff --git a/yaf/Hood/Core/Session/SessionNamespace.php b/yaf/Hood/Core/Session/SessionNamespace.php new file mode 100644 index 0000000..b980515 --- /dev/null +++ b/yaf/Hood/Core/Session/SessionNamespace.php @@ -0,0 +1,177 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/25 + * Time: 下午4:44 + */ + +namespace Hood\Core\Session; + +use Hood\Debug\DebugException; + +class SessionNamespace +{ + protected $_namespace = "session_default"; + + protected static $_namespaceLocks = array(); + + protected static $_writable = false; + + public function __construct($namespace = 'session_default', $singleInstance = false) + { + if ($namespace === '') { + throw new DebugException('Session Namespace 不能为空.'); + } + if ($namespace[0] == "_") { + throw new DebugException('Session Namespace 不能已_开头.'); + } + $this->_namespace = $namespace; + } + + /** + * 锁定Session NameSpace + */ + public function lock() + { + self::$_namespaceLocks[$this->_namespace] = true; + } + + /** + * 解锁Session NameSpace + */ + public function unlock() + { + unset(self::$_namespaceLocks[$this->_namespace]); + } + + /** + * 解锁全部Session NameSpace + */ + public static function unlockAll() + { + self::$_namespaceLocks = array(); + } + + /** + * 检查Session NameSpace是否有锁 + * @return bool + */ + public function isLocked() + { + return isset(self::$_namespaceLocks[$this->_namespace]); + } + + /** + * 释放所有session + */ + public function unsetAll() + { + return $this->_namespaceUnset($this->_namespace); + } + + /** + * 释放所有session + * @param $namespace + * @param null $name + */ + private function _namespaceUnset($namespace, $name = null) + { + $name = (string)$name; + if ($name === '') { + unset($_SESSION[$namespace]); + } else { + unset($_SESSION[$namespace][$name]); + } + } + + /** + * 存入一个Session + * @param $name + * @param $value + * @throws Q_Exception + */ + public function __set($name, $value) + { + if (isset(self::$_namespaceLocks[$this->_namespace])) { + throw new DebugException('This session/namespace has been marked as read-only.'); + } + if ($name === '') { + throw new DebugException("The '$name' key must be a non-empty string"); + } + $name = (string)$name; + $_SESSION[$this->_namespace][$name] = $value; + } + + /** + * 获取一个Session + * @param $name + * @return mixed + * @throws Q_Exception + */ + public function __get($name) + { + if (isset(self::$_namespaceLocks[$this->_namespace])) { + throw new DebugException('This session/namespace has been marked as read-only.'); + } + if ($name === '') { + throw new DebugException("The '$name' key must be a non-empty string"); + } + return $this->_namespaceGet($this->_namespace, $name); + } + + /** + * 注销session + * @param $name + * @throws Q_Exception + */ + public function __unset($name) + { + if ($name === '') { + throw new DebugException("The '$name' key must be a non-empty string"); + } + + return $this->_namespaceUnset($this->_namespace, $name); + } + + /** + * @param $namespace + * @param null $name + * @return mixed + */ + private function _namespaceGet($namespace, $name = null) + { + if ($name === null) { + return empty($_SESSION[$namespace]) ? NULL : $_SESSION[$namespace]; + } + return empty($_SESSION[$namespace][$name]) ? NULL : $_SESSION[$namespace][$name]; + } + + /** + * 判断Session是否存在 + * @param $name + * @return bool + * @throws Q_Exception + */ + public function __isset($name) + { + if ($name === '') { + throw new DebugException("The '$name' key must be a non-empty string"); + } + return $this->_namespaceIsset($this->_namespace, $name); + } + + /** + * @param $namespace + * @param null $name + * @return bool + * @throws Q_Exception + */ + private function _namespaceIsset($namespace, $name = null) + { + if ($name === null) { + return isset($_SESSION[$namespace]); + } + return isset($_SESSION[$namespace][$name]); + } +} \ No newline at end of file diff --git a/yaf/Hood/Core/Support/Arr.php b/yaf/Hood/Core/Support/Arr.php new file mode 100644 index 0000000..74b4d01 --- /dev/null +++ b/yaf/Hood/Core/Support/Arr.php @@ -0,0 +1,341 @@ +<?php +/** + * laravel 方法 + * User: Zip + * Date: 15/4/20 + * Time: 下午2:49 + */ + +namespace Hood\Core\Support; + + +class Arr +{ + + + /** + * Add an element to an array using "dot" notation if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function add($array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + + return $array; + } + + /** + * Build a new array using a callback. + * + * @param array $array + * @param \Closure $callback + * @return array + */ + public static function build($array, Closure $callback) + { + $results = array(); + + foreach ($array as $key => $value) { + list($innerKey, $innerValue) = call_user_func($callback, $key, $value); + + $results[$innerKey] = $innerValue; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return array(array_keys($array), array_values($array)); + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = array(); + + foreach ($array as $key => $value) { + if (is_array($value)) { + $results = array_merge($results, static::dot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of items. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function except($array, $keys) + { + return array_diff_key($array, array_flip((array)$keys)); + } + + /** + * Fetch a flattened array of a nested array element. + * + * @param array $array + * @param string $key + * @return array + */ + public static function fetch($array, $key) + { + foreach (explode('.', $key) as $segment) { + $results = array(); + + foreach ($array as $value) { + $value = (array)$value; + + $results[] = $value[$segment]; + } + + $array = array_values($results); + } + + return array_values($results); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param \Closure $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, $callback, $default = null) + { + foreach ($array as $key => $value) { + if (call_user_func($callback, $key, $value)) return $value; + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param \Closure $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, $callback, $default = null) + { + return static::first(array_reverse($array), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @return array + */ + public static function flatten($array) + { + $return = array(); + + array_walk_recursive($array, function ($x) use (&$return) { + $return[] = $x; + }); + + return $return; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original =& $array; + + foreach ((array)$keys as $key) { + $parts = explode('.', $key); + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array =& $array[$part]; + } + } + + unset($array[array_shift($parts)]); + + // clean up after each pass + $array =& $original; + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (is_null($key)) return $array; + if (isset($array[$key])) return $array[$key]; + foreach (explode('.', $key) as $segment) { + if (!is_array($array) || !array_key_exists($segment, $array)) { + return self::value($default); + } + $array = $array[$segment]; + } + return $array; + } + + public static function value($value) + { + return $value instanceof \Closure ? $value() : $value; + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array)$keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string $value + * @param string $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = array(); + foreach ($array as $item) { + $itemValue = is_object($item) ? $item->{$value} : $item[$value]; + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = is_object($item) ? $item->{$key} : $item[$key]; + + $results[$itemKey] = $itemValue; + } + } + return $results; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) return $array = $value; + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = array(); + } + + $array =& $array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Sort the array using the given Closure. + * + * @param array $array + * @param \Closure $callback + * @return array + */ + public static function sort($array, Closure $callback) + { + return Collection::make($array)->sortBy($callback)->all(); + } + + /** + * Filter the array using the given Closure. + * + * @param array $array + * @param \Closure $callback + * @return array + */ + public static function where($array, Closure $callback) + { + $filtered = array(); + + foreach ($array as $key => $value) { + if (call_user_func($callback, $key, $value)) $filtered[$key] = $value; + } + + return $filtered; + } + +} diff --git a/yaf/Hood/Core/Support/Str.php b/yaf/Hood/Core/Support/Str.php new file mode 100644 index 0000000..ab73cfb --- /dev/null +++ b/yaf/Hood/Core/Support/Str.php @@ -0,0 +1,279 @@ +<?php +namespace Hood\Core\Support; +/** + * laravel 方法 + * Class Str + * @package Hood\Core\Support + */ +class Str +{ + + /** + * Convert a value to camel case. + * + * @param string $value + * @return string + */ + public static function camel($value) + { + return lcfirst(static::studly($value)); + } + + /** + * Determine if a given string contains a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains($haystack, $needles) + { + foreach ((array)$needles as $needle) { + if ($needle != '' && strpos($haystack, $needle) !== false) return true; + } + + return false; + } + + /** + * Determine if a given string ends with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function endsWith($haystack, $needles) + { + foreach ((array)$needles as $needle) { + if ($needle == substr($haystack, -strlen($needle))) return true; + } + + return false; + } + + /** + * Cap a string with a single instance of a given value. + * + * @param string $value + * @param string $cap + * @return string + */ + public static function finish($value, $cap) + { + $quoted = preg_quote($cap, '/'); + + return preg_replace('/(?:' . $quoted . ')+$/', '', $value) . $cap; + } + + /** + * Determine if a given string matches a given pattern. + * + * @param string $pattern + * @param string $value + * @return bool + */ + public static function is($pattern, $value) + { + if ($pattern == $value) return true; + $pattern = preg_quote($pattern, '#'); + $pattern = str_replace('\*', '.*', $pattern) . '\z'; + return (bool)preg_match('#^' . $pattern . '#', $value); + } + + /** + * Return the length of the given string. + * + * @param string $value + * @return int + */ + public static function length($value) + { + return mb_strlen($value); + } + + /** + * Limit the number of characters in a string. + * + * @param string $value + * @param int $limit + * @param string $end + * @return string + */ + public static function limit($value, $limit = 100, $end = '...') + { + if (mb_strlen($value) <= $limit) return $value; + + return rtrim(mb_substr($value, 0, $limit, 'UTF-8')) . $end; + } + + /** + * Convert the given string to lower-case. + * + * @param string $value + * @return string + */ + public static function lower($value) + { + return mb_strtolower($value); + } + + /** + * Limit the number of words in a string. + * + * @param string $value + * @param int $words + * @param string $end + * @return string + */ + public static function words($value, $words = 100, $end = '...') + { + preg_match('/^\s*+(?:\S++\s*+){1,' . $words . '}/u', $value, $matches); + + if (!isset($matches[0])) return $value; + + if (strlen($value) == strlen($matches[0])) return $value; + + return rtrim($matches[0]) . $end; + } + + /** + * Parse a Class@method style callback into class and method. + * + * @param string $callback + * @param string $default + * @return array + */ + public static function parseCallback($callback, $default) + { + return static::contains($callback, '@') ? explode('@', $callback, 2) : array($callback, $default); + } + + /** + * Generate a more truly "random" alpha-numeric string. + * + * @param int $length + * @return string + * + * @throws \RuntimeException + */ + public static function random($length = 16) + { + if (function_exists('openssl_random_pseudo_bytes')) { + $bytes = openssl_random_pseudo_bytes($length * 2); + + if ($bytes === false) { + throw new \RuntimeException('Unable to generate random string.'); + } + + return substr(str_replace(array('/', '+', '='), '', base64_encode($bytes)), 0, $length); + } + + return static::quickRandom($length); + } + + /** + * Generate a "random" alpha-numeric string. + * + * Should not be considered sufficient for cryptography, etc. + * + * @param int $length + * @return string + */ + public static function quickRandom($length = 16) + { + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return substr(str_shuffle(str_repeat($pool, 5)), 0, $length); + } + + /** + * Convert the given string to upper-case. + * + * @param string $value + * @return string + */ + public static function upper($value) + { + return mb_strtoupper($value); + } + + /** + * Convert the given string to title case. + * + * @param string $value + * @return string + */ + public static function title($value) + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + + /** + * Generate a URL friendly "slug" from a given string. + * + * @param string $title + * @param string $separator + * @return string + */ + public static function slug($title, $separator = '-') + { + $title = static::ascii($title); + + // Convert all dashes/underscores into separator + $flip = $separator == '-' ? '_' : '-'; + + $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title); + + // Remove all characters that are not the separator, letters, numbers, or whitespace. + $title = preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', mb_strtolower($title)); + + // Replace all separator characters and whitespace by a single separator + $title = preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title); + + return trim($title, $separator); + } + + /** + * Convert a string to snake case. + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake($value, $delimiter = '_') + { + $replace = '$1' . $delimiter . '$2'; + + return ctype_lower($value) ? $value : strtolower(preg_replace('/(.)([A-Z])/', $replace, $value)); + } + + /** + * Determine if a given string starts with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith($haystack, $needles) + { + foreach ((array)$needles as $needle) { + if ($needle != '' && strpos($haystack, $needle) === 0) return true; + } + + return false; + } + + /** + * Convert a value to studly caps case. + * + * @param string $value + * @return string + */ + public static function studly($value) + { + $value = ucwords(str_replace(array('-', '_'), ' ', $value)); + + return str_replace(' ', '', $value); + } + +} diff --git a/yaf/Hood/DB.php b/yaf/Hood/DB.php new file mode 100644 index 0000000..a012136 --- /dev/null +++ b/yaf/Hood/DB.php @@ -0,0 +1,43 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/9 + * Time: 下午10:32 + */ + +namespace Hood; + +use Hood\Dao\Db\PDOMySQL; +use Hood\Dao\MongoDB\MongoCore; + +class DB +{ + /** + * 原生不带缓存 + * @param $database + * @return PDOMySQL\Connection + */ + static function Connection($database) + { + return new PDOMySQL\Connection($database); + } + + /** + * 快速数据存取(带缓存的DB) + * @param $database + * @return PDOMySQL\Quick + */ + static function Quick($database) + { + return new PDOMySQL\Quick($database); + } + + /** + * @return MongoCore + */ + static function Mongo() + { + return new MongoCore(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/Db/Mysqli/MysqliConnection.php b/yaf/Hood/Dao/Db/Mysqli/MysqliConnection.php new file mode 100644 index 0000000..a580865 --- /dev/null +++ b/yaf/Hood/Dao/Db/Mysqli/MysqliConnection.php @@ -0,0 +1,15 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/5/9 + * Time: 下午6:45 + */ + +namespace Hood\Dao\Db\Mysqli; + + +class MysqliConnection +{ + +} \ No newline at end of file diff --git a/yaf/Hood/Dao/Db/Mysqli/MysqliStatement.php b/yaf/Hood/Dao/Db/Mysqli/MysqliStatement.php new file mode 100644 index 0000000..9bc6d7e --- /dev/null +++ b/yaf/Hood/Dao/Db/Mysqli/MysqliStatement.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/5/9 + * Time: 下午7:04 + */ + +namespace Hood\Dao\Db\Mysqli; + + +class MysqliStatement { + +} \ No newline at end of file diff --git a/yaf/Hood/Dao/Db/PDOMySQL/PDOConnection.php b/yaf/Hood/Dao/Db/PDOMySQL/PDOConnection.php new file mode 100644 index 0000000..ae2bf95 --- /dev/null +++ b/yaf/Hood/Dao/Db/PDOMySQL/PDOConnection.php @@ -0,0 +1,703 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/11/28 + * Time: 上午1:28 + */ + +namespace Hood\Dao\Db\PDOMySQL; + +use Hood\Core\Root; +use Hood\Debug\DebugException; +use Hood\Core\Server; +use Hood\Dao\Db\PDOMySQL\Result; +use \Closure; +use Hood\Cache as hoodCache; + +/** + * 适配器 + * Class Connection + * @package Hood\Dao\Db\PDOMySQL + */ +class PDOConnection extends Root +{ + /** + * 数据库连接池 + * @var array + */ + protected $_instances = array(); + + /** + * 缓存驱动 + * @var string + */ + protected $_cacheDrive = array( + 'drive' => 'Memcached', + 'node' => null, + 'child_node' => 'hosts' + ); + + /** + * 属性 + * @var array + */ + private $_attribute = array( + \PDO::ATTR_CASE => \PDO::CASE_NATURAL, + \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION + ); + + + /** + * 设置指定集合字段 + * @var int + */ + private $_columnNumber = 0; + + /** + * 数据库读取模式 + * @var array + */ + private $_modality = null; + + /** + * FETCH_CLASS 专用 class name + * @var null + */ + protected $_className = null; + + /** + * + * @var \PDOStatement + */ + protected $_PDOStatement; + + + /** + * + * @var \PDO + */ + protected $_PDOConn; + + + /** + * 数据类型 + * @var array + */ + protected $paramType = array( + 'bool' => \PDO::PARAM_BOOL, + 'boolean' => \PDO::PARAM_BOOL, + 'null' => \PDO::PARAM_NULL, + 'integer' => \PDO::PARAM_INT, + 'int' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'stmt' => \PDO::PARAM_STMT, + 'object' => \PDO::PARAM_LOB, + 'float' => \PDO::PARAM_STR, + 'double' => \PDO::PARAM_STR, + 'output' => \PDO::PARAM_INPUT_OUTPUT + ); + + protected $_database; + + /** + * 写模式 + */ + const MODALITY_WRITE = 'write'; + + /** + * 读取模式 + */ + const MODALITY_READ = 'read'; + + + public function __construct($database) + { + parent::__construct(); + $this->_database = $database; + } + + /** + * 强制设置数据库连接方式 + * @param $modality + * @return $this + */ + public function setModality($modality) + { + $this->_modality = $modality; + return $this; + } + + /** + * 创建PDO数据库对象 + * @param string $modality (write/read) + * @return \PDO + * @throws DebugException + */ + public function connect($modality = 'write') + { + if ($this->_modality != null) $modality = $this->_modality; + $connectKey = $this->_database . '_' . $modality; + if (!isset($this->instances[$connectKey])) { + $server = $this->getServerHost('db'); + $_serverConfig = $server->getServerConfig('database', $this->_database); + $_dbConfig = $server->getServerConfig('mysql'); + if (!isset($_serverConfig[$modality]) || !in_array($modality, array('write', 'read'))) { + throw new DebugException('MySQL Not Modality ' . $modality); + } + $_server = $server->getServerMap($_serverConfig[$modality], Server::SERVER_SELECT_MODEL_RAND); + if (empty($_server)) { + throw new DebugException('Db Server is null.'); + } + $charset = isset($_dbConfig['charset']) ? $_dbConfig['charset'] : 'UTF8'; + #如果设置了 dbname 使用 配置的 + $dbname = isset($_serverConfig['dbname']) ? $_serverConfig['dbname'] : $this->_database; + $dsn = 'mysql:host=' . $_server['host'] . ';port=' . $_server['port'] . ';dbname=' . $dbname; + + $options = array( + \PDO::ATTR_PERSISTENT => isset($_dbConfig['persistent']) ? $_dbConfig['persistent'] : false, + \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES " . $charset + ); + $_connection = new \PDO($dsn, $_serverConfig['username'], $_serverConfig['passwd'], $options); + foreach ($this->_attribute as $key => $val) { + $_connection->setAttribute($key, $val); + } + $this->instances[$connectKey] = $_connection; + } + $this->_modality = null; + return $this->instances[$connectKey]; + } + + /** + * 设置属性 + * @param $attribute + * @param $value + * @return $this + */ + public function setAttribute($attribute, $value) + { + $this->_attribute[$attribute] = $value; + return $this; + } + + /** + * 单例事务处理 + * @param callable $callback + * @return mixed + * @throws DebugException + * @throws \Exception + */ + public function transaction(Closure $callback) + { + $this->beginTransaction(); + try { + $result = $callback($this); + $this->commit(); + } catch (DebugException $e) { + $this->rollBack(); + } + return $result; + } + + /** + * 开启事务 + * @throws DebugException + */ + public function beginTransaction() + { + $this->connect()->beginTransaction(); + } + + /** + * 提交事务 + * @throws DebugException + */ + public function commit() + { + $this->connect()->commit(); + } + + /** + * 判断是否有事务开启 + * @return bool + * @throws DebugException + */ + public function inTransaction() + { + return $this->connect()->inTransaction(); + } + + /** + * 回滚 + * @throws DebugException + */ + public function rollBack() + { + $this->connect()->rollBack(); + } + + /** + * 禁用的命令 + * @var array + */ + private $disableCommands = array('select', 'insert', 'delete', 'update'); + + /** + * 检查禁用参数 + * @param $str + * @return bool + */ + protected function checkDisableCommands($str) + { + foreach ($this->disableCommands as $command) { + if (stristr($str, $command) !== false) { + return false; + } + } + return true; + } + + /** + * 替换SQL的替换参数 + * @param $sql + * @param array $replaceMap + * @return string + * @throws DebugException + */ + protected function makeReplaceMapToSql($sql, array $replaceMap) + { + $_replaceMap = array(); + foreach ($replaceMap as $key => $value) { + if (!is_string($value)) { + throw new DebugException('替换参数值不能为数组.'); + } + if ($this->checkDisableCommands($value) == false) { + //throw new DebugException('替换参数包括了禁用sql方法.'); + } + $_replaceMap["#" . $key . "#"] = $value; + } + return strtr($sql, $_replaceMap); + } + + /** + * @param $sql + * @param $parameterMap + * @param $replaceMap + * @return \PDOStatement + * @throws DebugException + */ + protected function _statement($sql, $parameterMap, $replaceMap) + { + $sql = $this->makeReplaceMapToSql($sql, $replaceMap); + $this->_PDOConn = $conn = $this->connect(); + $this->_PDOStatement = $stmt = $conn->prepare($sql); + $this->bindValues($parameterMap, $stmt); + $stmt->execute(); + return $stmt; + } + + /** + * 执行SQL + * @param $sql + * @param $parameterMap + * @throws DebugException + */ + public function execute($sql, array $parameterMap = array()) + { + $this->_PDOConn = $conn = $this->connect(); + $this->_PDOStatement = $stmt = $conn->prepare($sql); + $this->bindValues($parameterMap, $stmt); + $stmt->execute(); + } + + /** + * 绑定一组数据 + * @param array $params + * @param \PDOStatement $stmt + * @throws DebugException + */ + private function bindValues(array $params, \PDOStatement $stmt) + { + foreach ($params as $parameter => $value) { + if (is_array($value) || is_object($value)) { + throw new DebugException('Sql绑定参数不能为数组或对象.'); + } + $dataType = $this->paramType [strtolower(gettype($value))]; + $stmt->bindValue($parameter, $value, $dataType); + } + } + + /** + * 插入数据 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return Result + */ + public function insert($sql, array $parameterMap = array(), array $replaceMap = array()) + { + $this->_statement($sql, $parameterMap, $replaceMap); + return new Result($this->_PDOConn, $this->_PDOStatement); + } + + /** + * 更新数据 + * @param string $sql + * @param array $parameterMap + * @param array $replaceMap + * @return Result + */ + public function update($sql, $parameterMap = array(), $replaceMap = array()) + { + $this->_statement($sql, $parameterMap, $replaceMap); + return new Result($this->_PDOConn, $this->_PDOStatement); + } + + /** + * 删除数据 + * @param string $sql + * @param array $parameterMap + * @param array $replaceMap + * @return Result + */ + public function delete($sql, $parameterMap = array(), $replaceMap = array()) + { + $this->_statement($sql, $parameterMap, $replaceMap); + return new Result($this->_PDOConn, $this->_PDOStatement); + } + + /** + * 原生Query + * @param $sql + * @return \PDOStatement + * @throws DebugException + */ + public function query($sql) + { + return $this->connect('read')->query($sql); + } + + /** + * 取回结果集中所有字段的值,作为关联数组返回 第一个字段作为码 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchAssoc($sql, $parameterMap = array(), $replaceMap = array()) + { + $stmt = $this->_statement($sql, $parameterMap, $replaceMap); + $data = array(); + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $tmp = array_values(array_slice($row, 0, 1)); + $data[$tmp[0]] = $row; + } + return $data; + } + + /** + * 设置结果集的Class + * @param null $className + * @return $this + */ + public function setFetchClass($className = null) + { + $this->_className = (string)$className; + return $this; + } + + /** + * 取回结果集中所有字段的值以对象形式输出,按自定义class + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchClass($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->fetchAll(\PDO::FETCH_CLASS, $this->_className); + } + + + /** + * 取回结果集中所有字段的值以对象形式输出,按自定义class + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchClassRow($sql, $parameterMap = array(), $replaceMap = array()) + { + $stmt = $this->_statement($sql, $parameterMap, $replaceMap); + $stmt->setFetchMode(\PDO::FETCH_CLASS, $this->_className); + return $stmt->fetch(); + } + + /** + * 关闭游标,使语句能再次被执行 + * @return $this + */ + public function closeCursor() + { + $this->_PDOStatement->closeCursor(); + return $this; + } + + /** + * 返回结果集中的列数 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return int + */ + public function columnCount($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->columnCount(); + } + + /** + * 打印一条 SQL 预处理命令 + * @param $sql + * @param $parameterMap + * @param $replaceMap + * @return bool + */ + public function debugDumpParams($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->debugDumpParams(); + } + + + /** + * 获取所有数据集合 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchAll($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->fetchAll(\PDO::FETCH_ASSOC); + } + + /** + * 获取一条数据集合 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return mixed + */ + public function fetchRow($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->fetch(\PDO::FETCH_ASSOC); + } + + /** + * 取回一个相关数组 + * 第一个字段值为key + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchPairs($sql, $parameterMap = array(), $replaceMap = array()) + { + $stmt = $this->_statement($sql, $parameterMap, $replaceMap);; + $data = array(); + while ($row = $stmt->fetch(\PDO::FETCH_NUM)) { + $data[$row[0]] = $row[1]; + } + return $data; + } + + /** + * 取回第一个字段值 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return string + */ + public function fetchOne($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->fetchColumn(0); + } + + /** + * 字符转义 + * @param $value + * @return string + */ + public function quoteStr($value) + { + if (is_int($value)) { + return $value; + } elseif (is_float($value)) { + return sprintf('%F', $value); + } + return "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'"; + } + + /** + * 字符转换 + * @param $string + * @param \PDO $parameterType + * @return string + * @throws DebugException + */ + public function quote($string, $parameterType = \PDO::PARAM_STR) + { + return $this->connect('read')->quote($string, $parameterType); + } + + /** + * 获取指定字段值 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return string + */ + public function fetchColumn($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->fetchColumn($this->_columnNumber); + } + + /** + * 取回所有结果行的第一个字段名 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return string + */ + public function fetchCol($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->fetchAll(\PDO::FETCH_COLUMN, 0); + } + + /** + * 设置指定集合数据字段 + * @param $columnNumber + * @return $this + */ + public function setFetchColumn($columnNumber) + { + $this->_columnNumber = (int)$columnNumber; + return $this; + } + + /** + * 取回结果集中所有字段的值 以对象形式输出 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return mixed + */ + public function fetchObject($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_statement($sql, $parameterMap, $replaceMap)->fetchObject($this->_className); + } + + /** + * 获取SQL + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return mixed|string + * @throws DebugException + */ + public function getSQL($sql, $parameterMap = array(), $replaceMap = array()) + { + $sql = $this->makeReplaceMapToSql($sql, $replaceMap); + if (strstr($sql, ':')) { + $matches_s = array(); + foreach ($parameterMap as $key => $val) { + if (is_string($val)) { + $val = "'{$val}'"; + } + $matches_s[':' . $key] = $val; + } + $asSql = strtr($sql, $matches_s); + } else { + $asSql = $sql; + foreach ($parameterMap as $val) { + $strPos = strpos($asSql, '?'); + if (is_string($val)) { + $val = "'{$val}'"; + } + $asSql = substr_replace($asSql, $val, $strPos, 1); + } + } + return $asSql; + } + + /** + * 缓存tag + * @param $tagName + * @return $this + */ + public function tag($tagName) + { + $this->_cacheTagName = $tagName; + return $this; + } + + + /** + * 删除多个tag时用到 + */ + public function delTags() + { + $this->_delTags = func_get_args(); + } + + /** + * 缓存key + * @param string $key + * @param null $prefix + * @return $this + */ + public function key($key) + { + $this->_cacheKey = (string)$key; + return $this; + } + + /** + * 缓存时间 + * @param $expire + * @return $this + */ + public function expire($expire) + { + $this->_cacheExpire = (int)$expire; + return $this; + } + + /** + * 设置缓存 + * @param bool $status + * @param string $drive + * @return $this + */ + public function cache($status = true, array $drive = array()) + { + if (isset($drive['drive'])) { + $this->_cacheDrive['drive'] = $drive['drive']; + } + if (isset($drive['drive'])) { + $this->_cacheDrive['node'] = $drive['node']; + } + if (isset($drive['drive'])) { + $this->_cacheDrive['child_node'] = $drive['child_node']; + } + $this->_cacheStatus = (bool)$status; + return $this; + } + + /** + * 重置参数 + */ + private function _resetParameter() + { + + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/Db/PDOMySQL/Quick.php b/yaf/Hood/Dao/Db/PDOMySQL/Quick.php new file mode 100644 index 0000000..a57037c --- /dev/null +++ b/yaf/Hood/Dao/Db/PDOMySQL/Quick.php @@ -0,0 +1,256 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/15 + * Time: 上午9:55 + */ + +namespace Hood\Dao\Db\PDOMySQL; + +use Hood\Cache as hoodCache; +use Hood\Debug\DebugException; +use Hood\Dao\Db\PDOMySQL\Result; + +class Quick extends PDOConnection +{ + /** + * + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @param $fun + * @return Result + */ + private function _execute($sql, $parameterMap = array(), $replaceMap = array()) + { + if ($this->_cacheStatus === true) { + if (!empty($this->_cacheTagName) && !empty($this->_cacheKey)) { + $this->dbCache()->tag($this->_cacheTagName)->delete($this->_cacheKey); + } else if (!empty($this->_delTags)) { + $this->dbCache()->deleteMulti($this->_delTags); + } elseif (!empty($this->_cacheTagName)) { + $this->dbCache()->delete($this->_cacheTagName); + } elseif (!empty($this->_cacheKey)) { + $this->dbCache()->delete($this->_cacheKey); + } + $this->_resetParameter(); + } + $this->_statement($sql, $parameterMap, $replaceMap); + return new Result($this->_PDOConn, $this->_PDOStatement); + } + + /** + * 快速取数据 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @param $fun + * @return mixed + */ + private function quicken($sql, $parameterMap = array(), $replaceMap = array(), $fun) + { + if ($this->_cacheStatus === true) { + if (empty($this->_cacheKey)) { + $this->_cacheKey = md5($this->getSQL($sql, $parameterMap, $replaceMap)); + } + if (!empty($this->_cacheTagName)) { + $cacheData = $this->dbCache()->tag($this->_cacheTagName)->get($this->_cacheKey); + } else { + $cacheData = $this->dbCache()->get($this->_cacheKey); + } + if (!empty($cacheData)) { + $this->_resetParameter(); + return $cacheData; + } + } + $cacheData = parent::$fun($sql, $parameterMap, $replaceMap); + if ($this->_cacheStatus === true) { + if (!empty($this->_cacheTagName)) { + $this->dbCache()->tag($this->_cacheTagName)->set($this->_cacheKey, $cacheData, $this->_cacheExpire); + } else { + $this->dbCache()->set($this->_cacheKey, $cacheData, $this->_cacheExpire); + } + } + $this->_resetParameter(); + return $cacheData; + } + + /** + * 取回结果集中所有字段的值,作为关联数组返回 第一个字段作为码 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchAssoc($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + + /** + * 取回结果集中所有字段的值以对象形式输出,按自定义class + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchClass($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * 获取所有数据集合 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchAll($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * 获取一条数据集合 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return mixed + */ + public function fetchRow($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * 取回一个相关数组 + * 第一个字段值为key + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchPairs($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * 取回第一个字段值 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return int + */ + public function fetchOne($sql, $parameterMap = array(), $replaceMap = array()) + { + $one = $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + return is_numeric($one) ? $one : 0; + } + + /** + * 获取指定字段值 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return string + */ + public function fetchColumn($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * 取回结果集中所有字段的值 以对象形式输出 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return mixed + */ + public function fetchObject($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * 插入数据 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return Result + */ + public function insert($sql, array $parameterMap = array(), array $replaceMap = array()) + { + return $this->_execute($sql, $parameterMap, $replaceMap); + } + + /** + * 更新数据 + * @param string $sql + * @param array $parameterMap + * @param array $replaceMap + * @return Result + */ + public function update($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_execute($sql, $parameterMap, $replaceMap); + } + + /** + * 删除数据 + * @param string $sql + * @param array $parameterMap + * @param array $replaceMap + * @return Result + */ + public function delete($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->_execute($sql, $parameterMap, $replaceMap); + } + + /** + * 取回结果集中所有字段的值以对象形式输出,按自定义class + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return array + */ + public function fetchClassRow($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * 取回所有结果行的第一个字段名 + * @param $sql + * @param array $parameterMap + * @param array $replaceMap + * @return string + */ + public function fetchCol($sql, $parameterMap = array(), $replaceMap = array()) + { + return $this->quicken($sql, $parameterMap, $replaceMap, __FUNCTION__); + } + + /** + * db cache 对象 + * @return \Hood\Cache\CacheInterface + */ + private function dbCache() + { + $cacheDriveName = $this->_cacheDrive['drive']; + return hoodCache::$cacheDriveName($this->_cacheDrive['node'], $this->_cacheDrive['child_node']); + } + + private function _resetParameter() + { + $this->_cacheTagName = ''; + $this->_cacheKey = ''; + $this->_className = ''; + $this->_cacheStatus = true; + $this->_delTags = array(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/Db/PDOMySQL/Result.php b/yaf/Hood/Dao/Db/PDOMySQL/Result.php new file mode 100644 index 0000000..ecee09a --- /dev/null +++ b/yaf/Hood/Dao/Db/PDOMySQL/Result.php @@ -0,0 +1,111 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/11/28 + * Time: 上午1:32 + */ + +namespace Hood\Dao\Db\PDOMySQL; + +/** + * 结果 + * Class Result + * @package Hood\Dao\Db\PDOMySQL + */ +class Result +{ + /** + * + * @var \PDO + */ + private $_PDOConn; + + /** + * @var \PDOStatement + */ + private $_PDOStatement; + + /** + * @param $PDOConn + * @param $PDOStatement + */ + public function __construct($PDOConn, $PDOStatement) + { + $this->_PDOConn = $PDOConn; + $this->_PDOStatement = $PDOStatement; + } + + /** + * 获取插入的ID + * @param null $name + * @return int + */ + public function lastInsertId($name = null) + { + return $this->_PDOConn->lastInsertId($name); + } + + /** + * 在一个多行集语句句柄中推进到下一个行集 + * @return bool + */ + public function nextRowset() + { + return $this->_PDOStatement->nextRowset(); + } + + /** + * 获取更新数量 + * @return int + */ + public function rowCount() + { + return $this->_PDOStatement->rowCount(); + } + + /** + * 状态 + * @return bool + */ + public function status() + { + return $this->_PDOStatement->errorCode() === '00000'; + } + + /** + * 获取statement Error Code + * @return string + */ + public function statementErrorCode() + { + return $this->_PDOStatement->errorCode(); + } + + /** + * 获取statement Error Info + * @return array + */ + public function statementErrorInfo() + { + return $this->_PDOStatement->errorInfo(); + } + + /** + * 获取pdo error code + * @return mixed + */ + public function pdoErrorCode() + { + return $this->_PDOConn->errorCode(); + } + + /** + * 获取PDO Error Info + * @return array + */ + public function pdoErrorInfo() + { + return $this->_PDOConn->errorInfo(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/Db/PostgreSQL/Connection.php b/yaf/Hood/Dao/Db/PostgreSQL/Connection.php new file mode 100644 index 0000000..5a39afb --- /dev/null +++ b/yaf/Hood/Dao/Db/PostgreSQL/Connection.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/1/5 + * Time: 下午6:31 + */ + +namespace Hood\Dao\Db\PostgreSQL; + + +class Connection { + +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/Collection.php b/yaf/Hood/Dao/MongoDB/Collection.php new file mode 100644 index 0000000..2724e40 --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/Collection.php @@ -0,0 +1,199 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/6/13 + * Time: 下午4:50 + */ + +namespace Hood\Dao\MongoDB; + +class Collection extends Cursor +{ + + /** + * Enter description here... + * + * @var MongoCore + */ + private $_db; + + /** + * @var string + */ + private $_dbName; + + /** + * 返回的结果集 + * @var array + */ + private $_fields = array(); + + /** + * @var string + */ + private $_collectionName; + + private $_set = false; + + public function __construct(\MongoClient $mongo, $dbName, $collection) + { + $this->_dbName = $dbName; + $this->_collectionName = $collection; + $this->_db = $mongo->selectDB($this->_dbName); + $this->_collection = $mongo->selectCollection($this->_dbName, $this->_collectionName); + } + + /** + * 插入数据 + * @param $attrs + * @param array $options + * @return MongoResult + */ + public function insert($attrs, array $options = array()) + { + $result = new MongoResult(); + $result->status = $bool = $this->_collection->insert($attrs, $options); + $result->attrs = $attrs; + if ($bool) { + $result->_id = $attrs["_id"]; + } + return $result; + } + + /** + * 查询所有数据 + * @param array $query + * @param array $fields + * @return array + */ + public function find(array $query = array(), array $fields = array()) + { + return $this->_collection->find($query, $fields); + } + + /** + * 查询所有符合条件的数据 + * @return \MongoCursor + */ + public function findAll() + { + return $this->_cursor(); + } + + /** + * 查找一行数据 + * @param array $query + * @param array $fields + * @return array|null + */ + public function findOne(array $query = array(), array $fields = array()) + { + $_fields = empty($fields) ? $this->_fields : $fields; + return $this->_collection->findOne($query, $_fields); + } + + /** + * 设置返回字段 + * @param array $fields + * @return $this + */ + public function fields(array $fields) + { + $this->_fields = $fields; + return $this; + } + + /** + * 更新 + * @param array $criteria + * @param array $newobj + * @param array $options + * @return bool + */ + public function update(array $criteria, array $newobj, array $options = array()) + { + if ($this->_set && !isset($newobj['$set'])) { + $newobj = array('$set' => $newobj); + } + return $this->_collection->update($criteria, $newobj, $options); + } + + /** + * 从集合中删除记录 + * @param array $criteria + * @param array $options + * @return array|bool + */ + public function remove(array $criteria = array(), array $options = array()) + { + return $this->_collection->remove($criteria, $options); + } + + + public function set() + { + $this->_set = true; + return $this; + } + + /** + * 查找一行数据,但只返回ID数据 + * @param $id + * @return array|null + */ + public function findID($id, array $fields = array()) + { + $_fields = empty($fields) ? $this->_fields : $fields; + if (($id instanceof \MongoId) == false) { + $id = new \MongoId($id); + } + return $this->_collection->findOne(array('_id' => $id), $_fields); + } + + /** + * 保存一个文档到集合 + * 如果对象来自数据库,则更新现有的数据库对象,否则插入对象 + * @param $a + * @param array $options + * @return array|bool + */ + public function save($a, array $options = array()) + { + return $this->_collection->save($a, $options); + } + + /** + * 查询并更新数据 + * @param array $query + * @param array $update + * @param array $fields + * @param array $options + * @return array + */ + public function findAndModify(array $query, array $update = NULL, array $fields = NULL, array $options = NULL) + { + return $this->_collection->findAndModify($query, $update, $fields, $options); + } + + /** + * 获取总数 + * @param array $query + * @return int + */ + public function count($query = array()) + { + return $this->_collection->count($query); + } + + /** + * 批量插入一组新的数据 + * @param array $a + * @param array $options + * @return mixed + */ + public function batchInsert(array $a, array $options = array()) + { + return $this->_collection->batchInsert($a, $options); + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/Cursor.php b/yaf/Hood/Dao/MongoDB/Cursor.php new file mode 100644 index 0000000..7d4bac8 --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/Cursor.php @@ -0,0 +1,143 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/6/13 + * Time: 下午4:51 + */ + +namespace Hood\Dao\MongoDB; + + +class Cursor +{ + + /** + * @var \MongoCursor + */ + protected $_cursor; + + /** + * @var \MongoCollection + */ + protected $_collection; + + /** + * 条件 + * @var array + */ + protected $_attrs = array(); + + protected $_conds = array(); + + protected $_offset = -1; + + protected $_limit = 0; + + protected $_sort = array(); + + protected $_hints = array(); + + protected $_results = array(); + + protected function _cursor() + { + $cursor = $this->_collection->find($this->criteria(), $this->_results); + if ($this->_offset >= 0) { + $cursor->skip($this->_offset); + } + if ($this->_limit > 0) { + $cursor->limit($this->_limit); + } + if ($this->_sort) { + $cursor->sort($this->_sort); + } + if (!empty($this->_hints)) { + foreach ($this->_hints as $hint) { + $cursor->hint($hint); + } + } + return $cursor; + } + + /** + * 组合查询条件 + * @return array + */ + private function criteria() + { + $attrs = $this->_attrs; + foreach ($this->_attrs as $attr => $values) { + if (!empty($values)) { + if (count($values) == 1) { + $attrs[$attr] = $values[0]; + } else { + $attrs[$attr]['$in'] = $values; + } + } + } + foreach ($this->_conds as $key => $value) { + $attrs[$key] = $value; + } + return $attrs; + } + + + public function offset($num) + { + $this->_offset = (int)$num; + return $this; + } + + public function limit($num) + { + $this->_limit = (int)$num; + return $this; + } + + public function skip($num) + { + $this->_offset = (int)$num; + return $this; + } + + public function sort(array $fields) + { + foreach ($fields as $key => $val) { + if (is_string($key)) { + $this->_sort[$key] = $val; + } + } + return $this; + } + + /** + * 设置正排序条件 + * + * @param string $attr 需要排序的属性 + * @return $this + */ + public function asc($attr = "_id") + { + $this->_sort[$attr] = 1; + return $this; + } + + /** + * 设置倒排序条件 + * + * @param string $attr 需要排序的属性 + * @return $this + */ + public function desc($attr = "_id") + { + $this->_sort[$attr] = -1; + return $this; + } + + public function hint($hint) + { + $this->_hints[] = $hint; + return $this; + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/MongoCore.php b/yaf/Hood/Dao/MongoDB/MongoCore.php new file mode 100644 index 0000000..8a2af1d --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/MongoCore.php @@ -0,0 +1,385 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/6/10 + * Time: 上午10:55 + */ + +namespace Hood\Dao\MongoDB; + +use Hood\Core\Root; + +class MongoCore extends Root +{ + private static $_lastId; + + /** + * @var \MongoClient + */ + private $_mongo = null; + + private $_dbname = ''; + + private $targetOptions = array( + 'connectTimeoutMS' => 3, + 'connect' => true + ); + + public function __construct() + { + parent::__construct(); + } + + /** + * @param array $options + * @return $this + */ + public function setTargetOptions(array $options = array()) + { + $this->targetOptions = array_merge($this->targetOptions, $options); + return $this; + } + + private function _connect($dbname, $modality = 'proxy') + { + $this->_dbname = $dbname; + $server = $this->getServerHost('nosql'); + $_serverConfig = $server->getServerConfig('database', $this->_dbname); + if (empty($_serverConfig)) { + throw new MongoException('Mongo Servers Config Not DBName "' . $this->_dbname . '" <br />Config File: ' . $this->serviceFile); + } + $options = $server->getServerConfig('mongo'); + $this->targetOptions = array_merge($this->targetOptions, $options); + $targetOptions = array(); + if (!empty($this->targetOptions['target_sock']) && !empty($_serverConfig['username']) && !empty($_serverConfig['passwd'])) { + $targetOptions["username"] = $_serverConfig['username']; + $targetOptions["password"] = $_serverConfig['passwd']; + } + unset($this->targetOptions['target_sock']); + switch ($modality) { + case 'write': + $hosts = $_serverConfig['write']; + break; + case 'read': + $hosts = $_serverConfig['read']; + break; + case 'proxy': + $hosts = $_serverConfig['proxy']; + break; + default: + throw new MongoException('Not Mongo Hosts.'); + } + $server = null; + if (isset($_serverConfig['sock']) && !empty($_serverConfig['sock'])) { + $server = "mongodb://" . $_serverConfig['sock']; + } else { + $server = "mongodb://" . $hosts; + } + if (class_exists("MongoClient")) { + $this->_mongo = new \MongoClient($server, $this->targetOptions); + } else { + $this->_mongo = new \Mongo($server, $this->targetOptions); + } + } + + /** + * 关闭 + * @param $connection + * @return bool + */ + public function close($connection) + { + return $this->_mongo->close($connection); + } + + /** + * 创建连接 + * @return bool + */ + public function connect() + { + return $this->_mongo->connect(); + } + + /** + * 清理DB + * @param $db + * @return mixed + */ + public function dropDB($db) + { + if (!is_object($db)) { + $db = $this->selectDB($db); + } + if (method_exists($db, "drop")) { + return $db->drop(); + } + if (method_exists($this->_mongo, "dropDB")) { + $this->_mongo->dropDB($db); + } + } + + /** + * + * @return bool + */ + public function forceError() + { + if (method_exists($this->_mongo, "forceError")) { + return $this->_mongo->forceError(); + } + return false; + } + + /** + * @param $dbname The database name + * @return \MongoDB + */ + public function __get($dbname) + { + return $this->_mongo->$dbname; + } + + /** + * 获取Hosts + * @return array + */ + public function getHosts() + { + if (method_exists($this->_mongo, "getHosts")) { + return $this->_mongo->getHosts(); + } + return array(); + } + + /** + * Get the read preference for this connection + * @return array + */ + public function getReadPreference() + { + if (method_exists($this->_mongo, "getReadPreference")) { + return $this->_mongo->getReadPreference(); + } + return array(); + } + + /** + * 最后一个错误 + * @return array|null + */ + public function lastError() + { + if (method_exists($this->_mongo, "lastError")) { + return $this->_mongo->lastError(); + } + return array(); + } + + /** + * Lists all of the databases available + * @return array + */ + public function listDBs() + { + return $this->_mongo->listDBs(); + } + + /** + * Connect pair servers + * + * @return boolean + */ + public function pairConnect() + { + if (method_exists($this->_mongo, "pairConnect")) { + return $this->_mongo->pairConnect(); + } + return false; + } + + /** + * Create pair persist connection + * + * @param string $username + * @param string $password + * @return boolean + */ + public function pairPersistConnect($username = "", $password = "") + { + if (method_exists($this->_mongo, "pairPersistConnect")) { + return $this->_mongo->pairPersistConnect($username, $password); + } + return false; + } + + /** + * Create persist connection + * + * @param string $username Username + * @param string $password Password + * @return boolean + */ + public function persistConnect($username = "", $password = "") + { + if (method_exists($this->_mongo, "persistConnect")) { + return $this->_mongo->persistConnect($username, $password); + } + return false; + } + + /** + * Get previous error + * + * @return array + */ + public function prevError() + { + if (method_exists($this->_mongo, "prevError")) { + return $this->_mongo->prevError(); + } + return array(); + } + + /** + * Reset error + * + * @return array + */ + public function resetError() + { + if (method_exists($this->_mongo, "resetError")) { + return $this->_mongo->resetError(); + } + return array(); + } + + /** + * + * @param $db + * @param $collection + * @return \MongoCollection + */ + public function selectCollection($dbname, $collection) + { + if ($this->_mongo == null) { + $this->_connect($dbname); + } + return $this->_mongo->selectCollection($dbname, $collection); + } + + /** + * @return Collection + */ + public function collection($dbname, $collection) + { + if ($this->_mongo == null) { + $this->_connect($dbname); + } + $collection = new Collection($this->_mongo, $dbname, $collection); + return $collection; + } + + /** + * Gets a database + * + * @param string $db The database name + * @return \MongoDB + */ + public function selectDB($dbname) + { + if ($this->_mongo == null) { + $this->_connect($dbname); + } + return $this->_mongo->selectDB($dbname); + } + + /** + * 为该连接设置读取选项 + * @param $readPreference mode: Mongo::RP_PRIMARY, Mongo::RP_PRIMARY_PREFERRED, Mongo::RP_SECONDARY, Mongo::RP_SECONDARY_PREFERRED, or Mongo::RP_NEAREST + * @param array $tags + * @return bool + */ + public function setReadPreference($readPreference, array $tags = array()) + { + if (method_exists($this->_mongo, "setReadPreference")) { + return $this->_mongo->setReadPreference($readPreference, $tags); + } + return false; + } + + /** + * Change slaveOkay setting for this connection + * + * @param boolean $ok If reads should be sent to secondary members of a replica set for all possible queries using this Mongo instance + * @return boolean + */ + public function setSlaveOkay($ok) + { + if (method_exists($this->_mongo, "setSlaveOkay")) { + return $this->_mongo->setSlaveOkay($ok); + } + return false; + } + + /** + * + * 输出Mongo对象 + * @return string + */ + public function __toString() + { + return $this->_mongo->__toString(); + } + + /** + * + * 获取版本号 + * @return string + */ + public static function getVersion() + { + if (class_exists("MongoClient")) { + return \MongoClient::VERSION; + } + if (class_exists("Mongo")) { + return \Mongo::VERSION; + } + return "0"; + } + + /** + * Compare another version with current version + * + * @param string $version Version to compare + * @return integer -1,0,1 + * @since 1.1.4 + */ + public static function compareVersion($version) + { + $currentVersion = self::getVersion(); + preg_match("/^[\\.\\d]+/", $currentVersion, $match); + $number = $match[0]; + return version_compare($number, $version); + } + + /** + * 设置最后插入的ID + * @param $lastId + */ + static function setLastInsertId($lastId) + { + self::$_lastId = $lastId; + } + + /** + * + * 获取插入刚刚插入的ID + * @return string + */ + static function lastInsertId() + { + return self::$_lastId; + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/MongoException.php b/yaf/Hood/Dao/MongoDB/MongoException.php new file mode 100644 index 0000000..2e5ca76 --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/MongoException.php @@ -0,0 +1,15 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/6/10 + * Time: 下午11:50 + */ + +namespace Hood\Dao\MongoDb; + + +class MongoException extends \MongoException +{ + +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/MongoObject.php b/yaf/Hood/Dao/MongoDB/MongoObject.php new file mode 100644 index 0000000..0a9ccdc --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/MongoObject.php @@ -0,0 +1,84 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/6/14 + * Time: 上午12:07 + */ + +namespace Hood\Dao\MongoDB; + + +class MongoObject implements \ArrayAccess +{ + private $_attrs = array(); + + private $_id; + + public function setID($id) + { + $this->_id = new \MongoId($id); + return $this; + } + + public function offsetExists($index) + { + return !is_null($this->attr($index)); + } + + public function offsetGet($index) + { + return $this->attr($index); + } + + public function offsetSet($index, $item) + { + $this->setAttr($index, $item); + } + + public function offsetUnset($index) + { + $this->setAttr($index, null); + } + + public function __get($name) + { + return $this->attr($name); + } + + public function attr($name) + { + return $this->arrayGet($this->_attrs, $name); + } + + function arrayGet(array $array, $keys) + { + if (is_array($keys) && empty($keys)) { + return $array; + } + if (!is_array($keys)) { + if (strstr($keys, "`")) { + $keys = preg_replace_callback("/`(.+)`/U", create_function('$match', 'return str_replace(".", "\.", $match[1]);'), $keys); + } + $keys = preg_split("/(?<!\\\\)\\./", $keys); + } + if (count($keys) == 1) { + $firstKey = array_pop($keys); + $firstKey = str_replace("\\.", ".", $firstKey); + return array_key_exists($firstKey, $array) ? $array[$firstKey] : null; + } + $lastKey = array_pop($keys); + $lastKey = str_replace("\\.", ".", $lastKey); + $lastArray = $array; + foreach ($keys as $key) { + $key = str_replace("\\.", ".", $key); + if (is_array($lastArray) && array_key_exists($key, $lastArray)) { + $lastArray = $lastArray[$key]; + } else { + return null; + } + } + + return (is_array($lastArray) && array_key_exists($lastKey, $lastArray)) ? $lastArray[$lastKey] : null; + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/MongoPage.php b/yaf/Hood/Dao/MongoDB/MongoPage.php new file mode 100644 index 0000000..15d4f99 --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/MongoPage.php @@ -0,0 +1,497 @@ +<?php + +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/6/10 + * Time: 下午11:51 + */ + +abstract class MongoPage +{ + private $keyword; + private $total; + private $path; + private $size; + private $properties; + private $query; + private $length; + private $pageSetSize = 11; + private $rows = array(); + + /** + * 是否加载了本地化对象 + * + * @var boolean + * @since 1.0 + */ + private $localeLoaded = false; + + /** + * 语言配置 + * + * @var array + * @since 1.0 + */ + private $messages = array(); + + /** + * 分页中代码当前页码的常量 + * + */ + const PAGER_VARIABLE_STRING = "%{PAGE_NO}"; + + /** + * 构造器 + * + * @since 2.0 + */ + function __construct() + { + $this->path = $_SERVER["PHP_SELF"]; + } + + /** + * 取得当前页码,第一页为1 + * + * @return integer + */ + function current() + { + $keyword = $this->keyword(); + $pageNo = intval(x($keyword)); + if ($pageNo <= 0) { + $pageNo = 1; + } + return min($pageNo, $this->length()); + } + + /** + * 取得下一页页码 + * + * @return integer + */ + function next() + { + $length = $this->length(); + $current = $this->current(); + return $current < $length ? ($current + 1) : $length; + } + + /** + * 取得上一页页码 + * + * @return integer + */ + function prev() + { + $length = $this->length(); + $current = $this->current(); + return $current > 1 ? ($current - 1) : 1; + } + + /** + * 取得记录开始的偏移量 + * + * @return integer + */ + function offset() + { + $offset = $this->size() * ($this->current() - 1); + if ($offset < 0) { + $offset = 0; + } + if ($offset >= $this->total()) { + $offset = max($this->size() * ($this->length() - 1), 0); + } + return $offset; + } + + /** + * 设置内容总数 + * + * @param integer $total 内容总数 + * @return MongoPage + */ + function setTotal($total) + { + $this->total = intval($total); + if ($this->total < 0) { + throw new \Exception("content total '{$total}' can't be small than 0"); + } + return $this; + } + + /** + * 数据总数 + * + * @return integer + * @since 1.0 + */ + function total() + { + return $this->total; + } + + /** + * 设置分页链接中的关键字 + * + * @param string $keyword 关键字 + * @return MongoPage + */ + function setKeyword($keyword) + { + $this->keyword = $keyword; + return $this; + } + + /** + * 取得分页用的关键字 + * + * 从1.0开始,如果没有关键字,则默认为page + * + * @return string + */ + function keyword() + { + if (!$this->keyword) { + $this->keyword = "page"; + } + return $this->keyword; + } + + /** + * 设置每页记录数 + * + * @param integer $size 大于0的数字 + * @return MongoPage + */ + function setSize($size) + { + $this->size = intval($size); + if ($this->size < 1) { + throw new \Exception("page size '{$size}' can't be small than 1"); + } + return $this; + } + + /** + * 取得每页记录数 + * + * @return integer + */ + function size() + { + if ($this->size < 1) { + $this->size = 10; + } + return $this->size; + } + + /** + * 设置链接的路径 + * + * @param string $path 路径 + * @return MongoPage + */ + function setPath($path) + { + $this->path = $path; + return $this; + } + + /** + * 取得程序路径 + * + * @return string + * @since 1.0 + */ + function path() + { + return $this->path; + } + + /** + * 设置属性 + * + * @param array $properties 属性列表 + * @return MongoPage + */ + function setProperties(array $properties) + { + $this->properties = $properties; + return $this; + } + + /** + * 取得设置的属性 + * + * @return array + * @since 1.0 + */ + function properties() + { + return $this->properties; + } + + /** + * 设置查询 + * + * @param mixed $query string|array + * @return MongoPage + */ + function setQuery($query) + { + if (is_array($query)) { + $_query = array(); + foreach ($query as $key => $value) { + if ($key == $this->keyword()) { + continue; + } + if (is_array($value)) { + foreach ($value as $key1 => $value1) { + $_query[] = "{$key}[]=" . urlencode($value1); + } + } else { + $_query[] = "{$key}=" . urlencode($value); + } + } + $query = implode("&", $_query); + } + $this->query = $query; + return $this; + } + + /** + * 添加查询条件 + * + * <code> + * $page->addQuery(array( + * "e" => 5, + * "f" => 6 + * )); + * $page->addQuery("g=7"); + * </code> + * + * @param mixed $query string|array + * @return MongoPage + */ + function addQuery($query) + { + if (is_array($query)) { + $_query = array(); + foreach ($query as $key => $value) { + if ($key == $this->keyword()) { + continue; + } + if (is_array($value)) { + foreach ($value as $key1 => $value1) { + $_query[] = "{$key}[]=" . urlencode($value1); + } + } else { + $_query[] = "{$key}=" . urlencode($value); + } + } + $query = implode("&", $_query); + } + $this->query .= ($this->query ? "&" : "") . $query; + return $this; + } + + /** + * 开启自动构造查询条件功能 + * + * @param boolean $bool 是否开启该功能 + * @param string|array $except 要去除的参数名 + * @param string|array $only 限制的参数名 + * @return MongoPage + */ + function setAutoQuery($bool = true, $except = "", $only = "") + { + if (!is_array($except)) { + $except = preg_split("/\\s+,\\s+/", $except); + } + if (!is_array($only) && strlen($only) > 0) { + $only = preg_split("/\\s+,\\s+/", $only); + } + if ($bool) { + $x = xn(); + foreach ($x as $name => $value) { + if ($except && in_array($name, $except)) { + unset($x[$name]); + } + if ($only && !in_array($name, $only)) { + unset($x[$name]); + } + } + $this->setQuery($x); + } + return $this; + } + + /** + * 取得查询 + * + * @return array + * @since 1.0 + */ + function query() + { + return $this->query; + } + + /** + * 取得一个分页好号对应的URL + * + * @param integer $pageNo 分页号 + * @return string + * @since 1.0 + */ + function url($pageNo) + { + $query = $this->query(); + if (strstr($query, self::PAGER_VARIABLE_STRING)) { + $query = str_replace(self::PAGER_VARIABLE_STRING, $pageNo, $query); + } else { + if ($query == "") { + $query = $this->keyword() . "=" . $pageNo; + } else { + $query .= "&" . $this->keyword() . "=" . $pageNo; + } + } + return $this->path() . "?" . $query; + } + + /** + * 取得总分页数 + * + * @return integer + * @since 1.0 + */ + function length() + { + if ($this->size() == 0) { + return 0; + } + return ceil($this->total() / $this->size()); + } + + /** + * 添加记录 + * + * @param mixed $row 记录 + * @return MongoPage + */ + function addRow($row) + { + $this->rows[] = $row; + return $this; + } + + /** + * 添加记录集 + * + * @param array $rows 记录集 + * @return MongoPage + */ + function addRows(array $rows) + { + foreach ($rows as $row) { + $this->rows[] = $row; + } + return $this; + } + + /** + * 取得记录集 + * + * @return array + */ + function rows() + { + return $this->rows; + } + + /** + * 设置记录集 + * + * @param array|iterable $rows 记录集 + * @return MongoPage + */ + function setRows($rows) + { + $this->rows = $rows; + return $this; + } + + /** + * 取得键值对应的消息文本 + * + * @param string $key 键值 + * @return string + * @since 1.0 + */ + protected function message($key) + { + if (!$this->localeLoaded) { + $locale = __LANG__; + if (!$locale) { + $locale = "default"; + } + + $message = x("~" . $key); + if ($message) { + return $message; + } + + //简写 + $dirname = dirname(__FILE__) . "/lang"; + $langFile = $dirname . "/" . $locale . ".php"; + if (is_file($langFile)) { + require($langFile); + $this->messages = $message; + } + $this->localeLoaded = true; + } + if (is_array($this->messages) && array_key_exists($key, $this->messages)) { + return $this->messages[$key]; + } + return null; + } + + /** + * 转换成字符串 + * + * @return string + */ + public abstract function __toString(); + + /** + * 设置分页集尺寸 + * + * @param integer $num 大于1 + * @return MongoPage + * @since 1.0 + */ + function setPageSetNum($num) + { + $this->pageSetSize = $num; + return $this; + } + + /** + * 取得分页集尺寸 + * + * @return integer + * @since 1.0 + */ + function pageSetNum() + { + return $this->pageSetSize; + } + + static function pageWithStyle($style, array $params = null) + { + exit(__METHOD__ . " need to be implemented."); + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/MongoResult.php b/yaf/Hood/Dao/MongoDB/MongoResult.php new file mode 100644 index 0000000..0cf983a --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/MongoResult.php @@ -0,0 +1,120 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/6/14 + * Time: 上午12:38 + */ + +namespace Hood\Dao\MongoDB; + + +class MongoResult +{ + private $_attrs = array(); + + public function lastInsertId() + { + return $this->__get('_id')->__toString(); + } + + public function lastInsertMongoID() + { + return $this->__get('_id'); + } + + /** + * @return bool + */ + public function getStatus() + { + $status = $this->__get('status'); + if (!empty($status['ok']) && $status['ok'] == 1) { + return true; + } + return false; + } + + public function getStatusData() + { + return $this->__get('status'); + } + + public function getAttrs() + { + return $this->__get('attrs'); + } + + public function __get($name) + { + return $this->arrayGet($this->_attrs, $name); + } + + public function __set($name, $val) + { + $this->_attrs = $this->arraySet($this->_attrs, $name, $val); + return $this; + } + + private function arraySet(array $array, $keys, $value) + { + if (is_array($keys) && empty($keys)) { + return $array; + } + if (!is_array($keys)) { + if (strstr($keys, "`")) { + $keys = preg_replace_callback("/`(.+)`/U", create_function('$match', 'return str_replace(".", "\.", $match[1]);'), $keys); + } + $keys = preg_split("/(?<!\\\\)\\./", $keys); + } + if (count($keys) == 1) { + $firstKey = array_pop($keys); + $firstKey = str_replace("\\.", ".", $firstKey); + $array[$firstKey] = $value; + return $array; + } + $lastKey = array_pop($keys); + $lastKey = str_replace("\\.", ".", $lastKey); + $lastConfig = &$array; + foreach ($keys as $key) { + $key = str_replace("\\.", ".", $key); + if (!isset($lastConfig[$key]) || !is_array($lastConfig[$key])) { + $lastConfig[$key] = array(); + } + $lastConfig = &$lastConfig[$key]; + } + $lastConfig[$lastKey] = $value; + return $array; + } + + private function arrayGet(array $array, $keys) + { + if (is_array($keys) && empty($keys)) { + return $array; + } + if (!is_array($keys)) { + if (strstr($keys, "`")) { + $keys = preg_replace_callback("/`(.+)`/U", create_function('$match', 'return str_replace(".", "\.", $match[1]);'), $keys); + } + $keys = preg_split("/(?<!\\\\)\\./", $keys); + } + if (count($keys) == 1) { + $firstKey = array_pop($keys); + $firstKey = str_replace("\\.", ".", $firstKey); + return array_key_exists($firstKey, $array) ? $array[$firstKey] : null; + } + $lastKey = array_pop($keys); + $lastKey = str_replace("\\.", ".", $lastKey); + $lastArray = $array; + foreach ($keys as $key) { + $key = str_replace("\\.", ".", $key); + if (is_array($lastArray) && array_key_exists($key, $lastArray)) { + $lastArray = $lastArray[$key]; + } else { + return null; + } + } + + return (is_array($lastArray) && array_key_exists($lastKey, $lastArray)) ? $lastArray[$lastKey] : null; + } +} \ No newline at end of file diff --git a/yaf/Hood/Dao/MongoDB/README.md b/yaf/Hood/Dao/MongoDB/README.md new file mode 100644 index 0000000..06d4bbd --- /dev/null +++ b/yaf/Hood/Dao/MongoDB/README.md @@ -0,0 +1,89 @@ +### 插入数据 +```php + DB::Mongo()->selectCollection('loggers','coupons')->insert(array('a'=>1)); +``` + +### 添加数据并获取添加数据和返回的数据 +```php + $attrs = DB::Mongo()->collection('loggers','coupons')->insert(array('a'=>1))->getAttrs(); +``` + +### 添加数据并获取状态 +```php + + $status = DB::Mongo()->collection('loggers','coupons')->insert(array('a'=>1))->getStatus(); + 原生返回状态数据 + ->getStatusData() +``` + +###添加数据并获取插入的ID +```php + $id = DB::Mongo()->collection('loggers','coupons')->insert(array('a'=>1))->lastInsertId(); + 原生插入返回ID对象 + ->lastInsertMongoID() +``` + +###分页显示 +```php + $data = DB::Mongo()->collection('loggers', 'coupons')->offset(1)->limit(2)->findAll(); +``` + +###获取总数 +```php + $data = DB::Mongo()->collection('loggers', 'coupons')->count(array('a'=1)); +``` + +###查询所有数据 +```php + SQL等于 select * from loggers.coupons + $data = DB::Mongo()->collection('loggers', 'coupons')->find(); +``` + +###查询所有符合条件数据 +```php + SQL等于 select a from loggers.coupons where a=1 + $data = DB::Mongo()->collection('loggers', 'coupons')->find(array('a' => 1), array('a')); + foreach ($data as $key => $val) { + print_r($val); + } + + SQL 等于 select a from loggers.coupons where a=1 or a=2 + $data = DB::Mongo()->collection('loggers', 'coupons')->find(array('$or' => array(array('a' => 1), array('a' => 2))), array('a')); + + SQL 等于 select log_type from loggers.coupons where id='554ab308becb29dd820041a9' + $data = DB::Mongo()->collection('loggers', 'coupons')->findOne(array('_id' => new \MongoId('554ab308becb29dd820041a9')), array('log_type')); + + 查询ID + $data = DB::Mongo()->collection('loggers', 'coupons')->findID('554ab308becb29dd820041a9'); +``` + +###更新 +```php + 更新a=3的数据为name="YOHO" + $data = DB::Mongo()->collection('loggers', 'coupons')->update(array('a' => 3), array('name' => "YOHO")); + + SQL 等于 update loggers.coupons set test1='Mongo1' where test='Mongo' + $data = DB::Mongo()->collection('loggers', 'coupons')->set()->update(array('test' => "Mongo"), array("test1" => "Mongo1")); +``` + +###删除 +```php + SQL 等于 delete from loggers.coupons where _id='55795b4cbecb297f610041a7' + $data = DB::Mongo()->collection('loggers', 'coupons')->remove(array('_id' => new \MongoId('55795b4cbecb297f610041a7'))); +``` + +###save +```php + $mo = DB::Mongo()->collection('loggers', 'coupons'); + $data = array('a' => 1, 'b' => 2); + $mo->insert($data); + $data['c'] = 1; + $data = $mo->save($data); + + $mo = DB::Mongo()->collection('loggers', 'coupons'); + $data = $mo->find(); + foreach ($data as $val) { + $val['yoho1'] = 'Me'; + $mo->save($val); + } +``` \ No newline at end of file diff --git a/yaf/Hood/Debug.php b/yaf/Hood/Debug.php new file mode 100644 index 0000000..905bac5 --- /dev/null +++ b/yaf/Hood/Debug.php @@ -0,0 +1,39 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/9 + * Time: 下午10:32 + */ + +namespace Hood; + +use Hood\Debug\ErrorHandler; +use Hood\Debug\ExceptionHandler; + +class Debug +{ + /** + * @return ErrorHandler + */ + static public function errorHandler() + { + set_error_handler(array('\\Hood\\Debug\\HoodError', 'errorHandler')); + + } + + /** + * @return ExceptionHandler + */ + static public function exceptionHandler() + { + set_exception_handler(array('\\Hood\\Debug\\HoodException', 'exceptionHandler')); + } + + + static public function handler() + { + self::errorHandler(); + self::exceptionHandler(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/DebugException.php b/yaf/Hood/Debug/DebugException.php new file mode 100644 index 0000000..f428227 --- /dev/null +++ b/yaf/Hood/Debug/DebugException.php @@ -0,0 +1,21 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/7 + * Time: 下午2:48 + */ + +namespace Hood\Debug; +use Hood\Debug\Debugbar\Handler; +class DebugException extends \Exception +{ + public function __construct($message = '', $code = 0, \Exception $previous = null) + { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + parent::__construct($message, (int)$code); + } else { + parent::__construct($message, (int)$code, $previous); + } + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/Bridge/Bar.php b/yaf/Hood/Debug/Debugbar/Bridge/Bar.php new file mode 100644 index 0000000..6b2e0b3 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/Bridge/Bar.php @@ -0,0 +1,20 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午4:47 + */ + +namespace Hood\Debug\Debugbar\Bridge; + + +class Bar implements InterfaceBridge +{ + public function send($debugLogs) + { + register_shutdown_function(function ($debugLogs) { + include_once "barTemplate.phtml"; + }, $debugLogs); + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/Bridge/InterfaceBridge.php b/yaf/Hood/Debug/Debugbar/Bridge/InterfaceBridge.php new file mode 100644 index 0000000..2343603 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/Bridge/InterfaceBridge.php @@ -0,0 +1,15 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午4:48 + */ + +namespace Hood\Debug\Debugbar\Bridge; + + +Interface InterfaceBridge +{ + public function send($debugLogs); +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/Bridge/Message.php b/yaf/Hood/Debug/Debugbar/Bridge/Message.php new file mode 100644 index 0000000..8996c67 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/Bridge/Message.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午1:53 + */ + +namespace Hood\Debug\Debugbar\Bridge; + + +class Message { + +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/Bridge/barTemplate.phtml b/yaf/Hood/Debug/Debugbar/Bridge/barTemplate.phtml new file mode 100644 index 0000000..f61b434 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/Bridge/barTemplate.phtml @@ -0,0 +1,502 @@ +<style type="text/css"> + .phpdebugbar { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + border-top: 0; + font-family: arial, sans-serif; + background: #fff; + z-index: 10000; + color: #000; + text-align: left; + } + + .phpdebugbar-closed { + width: auto; + } + + .phpdebugbar * { + + -moz-box-sizing: content-box; + box-sizing: content-box; + } + + .phpdebugbar code, .phpdebugbar pre { + background: none; + font-family: monospace; + font-size: 1em; + border: 0; + padding: 0; + margin: 0; + } + + .phpdebugbar a.phpdebugbar-restore-btn { + float: left; + padding: 5px 8px; + font-size: 14px; + color: #555; + text-decoration: none; + border-right: 1px solid #ddd; + } + + .phpdebugbar-resize-handle { + display: none; + height: 4px; + margin-top: -4px; + width: 100%; + background: none; + border-bottom: 1px solid #ccc; + cursor: n-resize; + } + + .phpdebugbar-closed, .phpdebugbar-minimized { + border-top: 1px solid #ccc; + } + + /* -------------------------------------- */ + + .phpdebugbar-header, a.phpdebugbar-restore-btn { + background: #efefef; + } + + .phpdebugbar-header { + min-height: 26px; + line-height: 16px; + } + + .phpdebugbar-header:before, .phpdebugbar-header:after { + display: table; + line-height: 0; + content: ""; + } + + .phpdebugbar-header:after { + clear: both; + } + + .phpdebugbar-header-left { + float: left; + } + + .phpdebugbar-header-right { + float: right; + } + + .phpdebugbar-header > div > * { + padding: 5px 8px; + font-size: 14px; + color: #555; + text-decoration: none; + } + + .phpdebugbar-header-left > * { + float: left; + } + + .phpdebugbar-header-right > * { + float: right; + } + + .phpdebugbar-header-right > select { + padding: 0; + } + + /* -------------------------------------- */ + + span.phpdebugbar-indicator, + a.phpdebugbar-indicator, + a.phpdebugbar-close-btn { + border-right: 1px solid #ddd; + } + + a.phpdebugbar-tab.phpdebugbar-active { + background: #ccc; + color: #444; + background-image: linear-gradient(bottom, rgb(173, 173, 173) 41%, rgb(209, 209, 209) 71%); + background-image: -o-linear-gradient(bottom, rgb(173, 173, 173) 41%, rgb(209, 209, 209) 71%); + background-image: -moz-linear-gradient(bottom, rgb(173, 173, 173) 41%, rgb(209, 209, 209) 71%); + background-image: -webkit-linear-gradient(bottom, rgb(173, 173, 173) 41%, rgb(209, 209, 209) 71%); + background-image: -ms-linear-gradient(bottom, rgb(173, 173, 173) 41%, rgb(209, 209, 209) 71%); + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.41, rgb(173, 173, 173)), color-stop(0.71, rgb(209, 209, 209))); + } + + a.phpdebugbar-tab span.phpdebugbar-badge { + display: none; + margin-left: 5px; + font-size: 11px; + line-height: 14px; + padding: 0px 6px; + background: #ccc; + border-radius: 4px; + color: #555; + font-weight: normal; + text-shadow: none; + vertical-align: middle; + } + + a.phpdebugbar-tab i { + display: none; + vertical-align: middle; + } + + a.phpdebugbar-tab span.phpdebugbar-badge.phpdebugbar-important { + background: #ed6868; + color: white; + } + + a.phpdebugbar-close-btn, a.phpdebugbar-open-btn, a.phpdebugbar-restore-btn { + width: 16px; + height: 16px; + } + + a.phpdebugbar-close-btn { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuM4zml1AAAADDSURBVDhPxZCxCsIwFEUzuGdwCvQD7BIIcehUXDqVfGM/wsG/iG4ifkzMlRuSPLo4eeFBue8c6Iv6b4wxW557Hs0KnWa3seqDxTiOyVqbhmF4UND4Rofdruyce3rvE6bIRSo9GOI1McbLPM/vVm4l7MAQr0kpHaQsJTDE+6zrepym6SVFdNgR69M+hBTLzWCI10gJvydvBkO8ZlmWayvhJnkzGOI1+fBTCOHWPkT7YNiBId4HizxnCKy+r81uX/otSn0A7dioI/vYX+8AAAAASUVORK5CYII=) no-repeat 9px 6px; + } + + a.phpdebugbar-open-btn { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAOCAYAAADJ7fe0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfdCgYULwwNKp3GAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4wLjOM5pdQAAAA1UlEQVQ4T2OgKpCUlOQH4vdA/B8Jv4dKEwYgDdLS0v8NDQ3/GxsbwzGIj2YoGEO1oQJkjcRgqDZUAJKwsrJ6/v//fwdiMFQbKgAZkpGR0QR0ajy60wlgRJhBXSGhpqb2CNnZhHBkZORcqBEMDFBX2BsYGGBVjAv39vZaQ41gYIC6Ygs2hbiwr6/vdqA+DqgR4CiW19bWxqoYF87Ly4uFaocAZWXlydgU4sJ2dna3ga4QgGqHAC0trY/YFOPCKSkpDVCtCAA01QaIsaYJHFgCqpVagIEBACGlF2c3r4ViAAAAAElFTkSuQmCC) no-repeat 8px 6px; + } + + .phpdebugbar-indicator { + position: relative; + cursor: pointer; + } + + .phpdebugbar-indicator span.phpdebugbar-text { + margin-left: 5px; + } + + .phpdebugbar-indicator span.phpdebugbar-tooltip { + display: none; + position: absolute; + top: -30px; + background: #efefef; + opacity: .7; + border: 1px solid #ccc; + color: #555; + font-size: 11px; + padding: 2px 3px; + z-index: 1000; + text-align: center; + width: 200%; + right: 0; + } + + .phpdebugbar-indicator:hover span.phpdebugbar-tooltip:not(.phpdebugbar-disabled) { + display: block; + } + + select.phpdebugbar-datasets-switcher { + float: right; + display: none; + margin: 2px 0 0 7px; + max-width: 200px; + max-height: 23px; + padding: 0; + } + + /* -------------------------------------- */ + + .phpdebugbar-body { + border-top: 1px solid #ccc; + display: none; + position: relative; + height: 300px; + } + + /* -------------------------------------- */ + + .phpdebugbar-panel { + display: none; + height: 100%; + overflow: auto; + width: 100%; + } + + .phpdebugbar-panel.phpdebugbar-active { + display: block; + } + + /* -------------------------------------- */ + + .phpdebugbar-mini-design a.phpdebugbar-tab { + position: relative; + border-right: 1px solid #ddd; + } + + .phpdebugbar-mini-design a.phpdebugbar-tab span.phpdebugbar-text { + display: none; + } + + .phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text { + display: block; + position: absolute; + top: -30px; + background: #efefef; + opacity: .7; + border: 1px solid #ccc; + color: #555; + font-size: 11px; + padding: 2px 3px; + z-index: 1000; + text-align: center; + right: 0; + } + + .phpdebugbar-mini-design a.phpdebugbar-tab i { + display: inline-block; + } + + /* -------------------------------------- */ + + .phpdebugbar-widgets-list { + margin: 0; + padding: 0; + list-style: none; + font-family: monospace; + } + .phpdebugbar-widgets-list .list-item { + padding: 3px 6px; + border-bottom: 1px solid #eee; + position: relative; + } + + .phpdebugbar-widgets-exceptions .list-item .message { + display: block; + color: red; + } + + .phpdebugbar-widgets-exceptions .list-item .filename { + display: block; + font-style: italic; + color: #555; + } + + .phpdebugbar-widgets-exceptions .list-item .type { + display: block; + position: absolute; + right: 4px; + top: 4px; + font-weight: bold; + } + + .phpdebugbar-widgets-exceptions .list-item .file { + display: none; + margin: 10px; + padding: 5px; + border: 1px solid #ddd; + font-family: monospace; + } + +</style> +<?php +$exception = $debugLogs['Exception']; +$error = $debugLogs['Error']; +?> +<div class="phpdebugbar" id="phpdebugbar"> + <div class="phpdebugbar-resize-handle" id="phpdebugbarResizeHandle" style="display: block;"> + </div> + <div class="phpdebugbar-header"> + <div class="phpdebugbar-header-left"> + <a href="javascript:" onclick="phpdebugbarTab(this.id);" class="phpdebugbar-tab phpdebugbar-active" id="phpdebugbar-Exceptions"> + <i class="fa fa-bug"></i> + <span class="phpdebugbar-text"> + Exceptions + </span> + <span class="phpdebugbar-badge" style="display: none;"> + </span> + </a> + <a href="javascript:" onclick="phpdebugbarTab(this.id);" class="phpdebugbar-tab" id="phpdebugbar-Error"> + <i class="fa fa-tags"></i> + <span class="phpdebugbar-text"> + Error + </span> + <span class="phpdebugbar-badge"></span> + </a> + <!--a href="javascript:" onclick="phpdebugbarTab(this.id);" class="phpdebugbar-tab" id="phpdebugbar-Messages"> + <i class="fa fa-list-alt"></i> + <span class="phpdebugbar-text"> + Messages + </span> + <span class="phpdebugbar-badge" style="display: inline;"> + 1 + </span> + </a> + <a href="javascript:" onclick="phpdebugbarTab(this.id);" class="phpdebugbar-tab" id="phpdebugbar-Timeline"> + <i class="fa fa-tasks"></i> + <span class="phpdebugbar-text"> + Timeline + </span> + <span class="phpdebugbar-badge"> + </span> + </a--> + </div> + <div class="phpdebugbar-header-right"> + <a href="javascript:" class="phpdebugbar-close-btn"></a> + <a href="javascript:" class="phpdebugbar-open-btn" style="display: none;"></a> + <select class="phpdebugbar-datasets-switcher"> + <option value="d55d200ec7013fafbbc7e5368dbf0e27"> + #1 demo (17:55:57) + </option> + </select> + <span class="phpdebugbar-indicator"> + <i class="fa fa-clock-o"> + </i> + <span class="phpdebugbar-text"> + 3.96ms + </span> + <span class="phpdebugbar-tooltip"> + Request Duration + </span> + </span> + <span class="phpdebugbar-indicator"> + <i class="fa fa-cogs"> + </i> + <span class="phpdebugbar-text"> + <?php echo memory_get_usage(true);?>KB + </span> + <span class="phpdebugbar-tooltip"> + Memory Usage + </span> + </span> + </div> + </div> + <div class="phpdebugbar-body" id="phpdebugbarBody" style="display: block;"> + <div class="phpdebugbar-panel" id="phpdebugbar-Error-Body"> + <div class="phpdebugbar-widgets-messages"> + <?php if(!empty($error)){?> + <ul class="phpdebugbar-widgets-list"> + <li class="phpdebugbar-widgets-list-item"> + <table style="width: 100%;"> + <tbody> + <tr style="background-color: #efefef;min-height: 15px;"><td>Error</td><td>Location</td></td></tr> + <?php foreach($error as $key =>$val){?> + <tr> + <td><?php echo $val['message'];?></td> + <td><?php echo $val['backtrace']['error_file'],' # ',$val['backtrace']['error_line'];?></td> + </tr> + <?php }?> + </tbody> + </table> + </li> + </ul> + <?php }?> + </div> + </div> + <div class="phpdebugbar-panel" id="phpdebugbar-Messages-Body"> + <ul class="phpdebugbar-widgets-timeline"> + </ul> + </div> + <div class="phpdebugbar-panel" id="phpdebugbar-Timeline-Body"> + <ul class="phpdebugbar-widgets-timeline"> + </ul> + </div> + <div class="phpdebugbar-panel phpdebugbar-active" id="phpdebugbar-Exceptions-Body"> + <div class="phpdebugbar-widgets-exceptions"> + <?php if(!empty($exception['message'])){ ?> + <ul class="phpdebugbar-widgets-list"> + <li class="list-item"> + <span class="message" style="margin-bottom: 10px;"><?php echo $exception['message'];?></span> + <span class="filename"><?php echo $exception['file'],' # ',$exception['line'];?></span> + <span class="type"><?php echo $exception['type'];?></span> + <div style="display: block;"> + <div> + <?php + $surroundingHtml = ''; + foreach($exception['surrounding_lines'] as $linesKey => $linesVal){ + $surroundingHtml .= ($linesKey+1) . $linesVal; + } + echo highlight_string($surroundingHtml,true); + ?> + </div> + <table style="width: 100%;"><tbody> + <tr style="background-color: #efefef;min-height: 15px;"><td>Function</td><td>Location</td></td></tr> + <?php + $html = ''; + foreach($exception['exception_list'] as $key =>$val){ + $className = $val['class'].$val['type'].$val['function']; + $args = array(); + if(isset($val['args'])) { + if (is_array($val['args'])) { + foreach ($val['args'] as $argsKey => $argsVal) { + if(is_array($argsVal)){ + foreach($argsVal as $_k=>$_v){ + $args[] = $_v; + } + }else{ + $args[]= $argsVal; + } + } + } + } + $argsStr = count($args) >0 ? ('('.implode(',',$args).')') : ''; + $html .= '<tr><td style="color: #0000BB;">' . $className .$argsStr. ';</td><td>' . $val['file'] .' # '. $val['line'] . '</td></tr>'; + } + echo $html; + ?> + </tbody></table> + </div> + </li> + </ul> + <?php }?> + </div> + </div> + </div> +</div> +<script type="text/javascript"> + function phpdebugbarTab(tabName){ + //,'phpdebugbar-Messages','phpdebugbar-Timeline' + var tabList = ['phpdebugbar-Exceptions','phpdebugbar-Error']; + for(var i=0;i<tabList.length;i++){ + phpdebugbarRemoveClass(document.getElementById(tabList[i]),'phpdebugbar-active'); + phpdebugbarRemoveClass(document.getElementById(tabList[i]+"-Body"),'phpdebugbar-active'); + } + phpdebugbarAddClass(document.getElementById(tabName),'phpdebugbar-active'); + phpdebugbarAddClass(document.getElementById(tabName+"-Body"),'phpdebugbar-active'); + return false; + } + function phpdebugbarHasClass(ele,cls) { + return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)')); + } + + function phpdebugbarAddClass(ele,cls) { + if (!this.phpdebugbarHasClass(ele,cls)) ele.className += " "+cls; + } + + function phpdebugbarRemoveClass(ele,cls) { + if (phpdebugbarHasClass(ele,cls)) { + var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)'); + ele.className=ele.className.replace(reg,' '); + } + } + + function hoodDebug(id) { + return document.getElementById(id) + } + window.onload = function() { + var oBox = document.body, oBottom = hoodDebug("phpdebugbar"), oLine = hoodDebug("phpdebugbarResizeHandle"); + oLine.onmousedown = function(e) { + var disY = (e || event).clientY; + oLine.top = oLine.offsetTop; + document.onmousemove = function(e) { + var iT = oLine.top + ((e || event).clientY - disY); + var maxT = document.body.clientHeight - oLine.offsetHeight; + oLine.style.margin = 0; + iT < 0 && (iT = 0); + iT > maxT && (iT = maxT); + oLine.style.top = oBottom.style.top = iT + "px"; + window.console && console.log(oBottom); + return false + }; + document.onmouseup = function() { + document.onmousemove = null; + document.onmouseup = null; + oLine.releaseCapture && oLine.releaseCapture() + }; + oLine.setCapture && oLine.setCapture(); + return false + }; + }; + +</script> \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/CollectorInterface/Error.php b/yaf/Hood/Debug/Debugbar/CollectorInterface/Error.php new file mode 100644 index 0000000..e7c5d3d --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/CollectorInterface/Error.php @@ -0,0 +1,17 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/27 + * Time: 下午4:08 + */ + +namespace Hood\Debug\Debugbar\CollectorInterface; + + +class Error +{ + public $message; + + public $backtrace = array(); +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/ErrorCollector.php b/yaf/Hood/Debug/Debugbar/ErrorCollector.php new file mode 100644 index 0000000..18be7c6 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/ErrorCollector.php @@ -0,0 +1,35 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/27 + * Time: 下午3:59 + */ + +namespace Hood\Debug\Debugbar; + +use Hood\Debug\Debugbar\CollectorInterface; + +class ErrorCollector implements InterfaceCollector +{ + + private $debugType = 'Error'; + + public function __construct(CollectorInterface\Error $error) + { + $this->collector[] = array( + 'message' => $error->message, + 'backtrace' => $error->backtrace + ); + } + + public function getCollector() + { + return $this->collector; + } + + public function getDebugType() + { + return $this->debugType; + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/ExceptionCollector.php b/yaf/Hood/Debug/Debugbar/ExceptionCollector.php new file mode 100644 index 0000000..3300b5c --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/ExceptionCollector.php @@ -0,0 +1,73 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午12:26 + */ + +namespace Hood\Debug\Debugbar; + + +class ExceptionCollector implements InterfaceCollector +{ + + private $collector = array(); + + private $debugType = 'Exception'; + + public function __construct(\Exception $exception) + { + $errorList = $this->formatExceptionData($exception); + foreach ($exception->getTrace() as $key => $val) { + $_error = array( + 'file' => empty($val['file']) ? '↑' : $val['file'], + 'function' => $val['function'], + 'line' => empty($val['line']) ? '' : $val['line'], + 'class' => empty($val['class']) ? '' : $val['class'], + 'type' => empty($val['type']) ? '' : $val['type'] + ); + if (!empty($val['args'])) { + $args = array(); + foreach ($val['args'] as $argsKey => $argsVal) { + if (empty($argsVal)) { + continue; + } + $args[] = $argsVal; + } + $_error['args'] = $args; + } + $errorList['exception_list'][] = $_error; + } + $this->collector = $errorList; + } + + public function formatExceptionData(\Exception $e) + { + $filePath = $e->getFile(); + $lines = array(); + if ($filePath && file_exists($filePath)) { + $lines = file($filePath); + $start = $e->getLine() - 4; + $lines = array_slice($lines, $start < 0 ? 0 : $start, 7, true); + } + return array( + 'type' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $filePath, + 'line' => $e->getLine(), + 'surrounding_lines' => $lines + ); + } + + public function getDebugType() + { + return $this->debugType; + } + + public function getCollector() + { + return $this->collector; + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/Handler.php b/yaf/Hood/Debug/Debugbar/Handler.php new file mode 100644 index 0000000..380448a --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/Handler.php @@ -0,0 +1,36 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午1:32 + */ + +namespace Hood\Debug\Debugbar; + +use Hood\Debug\Debugbar\Bridge; +use Hood\Debug\Debugbar\CollectorInterface; + +class Handler extends HandlerAbstract +{ + public function addExceptionCollector(\Exception $exception) + { + $this->addCollector(new ExceptionCollector($exception)); + } + + public function addErrorCollector(CollectorInterface\Error $error) + { + $this->addCollector(new ErrorCollector($error)); + } + + public function render() + { + return $this->debugLogs; + } + + public function bridge($bridge = 'bar') + { + $_bridge = new Bridge\Bar(); + $_bridge->send($this->debugLogs); + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/HandlerAbstract.php b/yaf/Hood/Debug/Debugbar/HandlerAbstract.php new file mode 100644 index 0000000..92e2d34 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/HandlerAbstract.php @@ -0,0 +1,20 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午2:31 + */ + +namespace Hood\Debug\Debugbar; + + +class HandlerAbstract +{ + protected $debugLogs = array(); + + public function addCollector(InterfaceCollector $collector) + { + $this->debugLogs[$collector->getDebugType()] = $collector->getCollector(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/InterfaceCollector.php b/yaf/Hood/Debug/Debugbar/InterfaceCollector.php new file mode 100644 index 0000000..fa01951 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/InterfaceCollector.php @@ -0,0 +1,17 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午4:37 + */ + +namespace Hood\Debug\Debugbar; + + +interface InterfaceCollector +{ + public function getCollector(); + + public function getDebugType(); +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/MemoryCollector.php b/yaf/Hood/Debug/Debugbar/MemoryCollector.php new file mode 100644 index 0000000..36744ad --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/MemoryCollector.php @@ -0,0 +1,24 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午1:46 + */ + +namespace Hood\Debug\Debugbar; + + +class MemoryCollector +{ + function echo_memory_usage() + { + $memUsage = memory_get_usage(true); + if ($memUsage < 1024) + return $memUsage . " bytes"; + elseif ($memUsage < 1048576) + return round($memUsage / 1024, 2) . " KB"; + else + return round($memUsage / 1048576, 2) . " MB"; + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/MessagesCollector.php b/yaf/Hood/Debug/Debugbar/MessagesCollector.php new file mode 100644 index 0000000..a1db91b --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/MessagesCollector.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午1:46 + */ + +namespace Hood\Debug\Debugbar; + + +class MessagesCollector { + +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/RequestDataCollector.php b/yaf/Hood/Debug/Debugbar/RequestDataCollector.php new file mode 100644 index 0000000..a8e7794 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/RequestDataCollector.php @@ -0,0 +1,15 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午1:53 + */ + +namespace Hood\Debug\Debugbar; + + +class RequestDataCollector +{ + +} \ No newline at end of file diff --git a/yaf/Hood/Debug/Debugbar/TimeDataCollector.php b/yaf/Hood/Debug/Debugbar/TimeDataCollector.php new file mode 100644 index 0000000..f890733 --- /dev/null +++ b/yaf/Hood/Debug/Debugbar/TimeDataCollector.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/26 + * Time: 下午1:53 + */ + +namespace Hood\Debug\Debugbar; + + +class TimeDataCollector { + +} \ No newline at end of file diff --git a/yaf/Hood/Debug/HoodError.php b/yaf/Hood/Debug/HoodError.php new file mode 100644 index 0000000..7d8cc68 --- /dev/null +++ b/yaf/Hood/Debug/HoodError.php @@ -0,0 +1,32 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/27 + * Time: 下午1:35 + */ + +namespace Hood\Debug; + +use Hood\Debug\Debugbar\CollectorInterface; +use Hood\Debug\Debugbar\Handler; + +class HoodError +{ + public static $collector = null; + + public static function errorHandler($errno, $errorStr, $errorFile, $errorLine) + { + $error = new CollectorInterface\Error(); + $error->message = $errorStr; + $error->backtrace = array( + 'error_file' => $errorFile, + 'error_line' => $errorLine + ); + if (self::$collector == null) { + self::$collector = $handler = new Handler(); + } + self::$collector->addErrorCollector($error); + self::$collector->bridge(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/HoodException.php b/yaf/Hood/Debug/HoodException.php new file mode 100644 index 0000000..2d381e3 --- /dev/null +++ b/yaf/Hood/Debug/HoodException.php @@ -0,0 +1,21 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/7 + * Time: 下午2:48 + */ + +namespace Hood\Debug; + +use Hood\Debug\Debugbar\Handler; + +class HoodException +{ + public static function exceptionHandler(\Exception $Exception) + { + $handler = new Handler(); + $handler->addExceptionCollector($Exception); + $handler->bridge(); + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/XHProf/Runs.php b/yaf/Hood/Debug/XHProf/Runs.php new file mode 100644 index 0000000..6538b57 --- /dev/null +++ b/yaf/Hood/Debug/XHProf/Runs.php @@ -0,0 +1,106 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 2/22/15 + * Time: 3:02 PM + */ + +namespace Hood\Debug\XHProf; + + +class Runs +{ + private $dir = ''; + + private $suffix = 'xhprof'; + + public function __construct($dir = null) + { + if (empty($dir)) { + $dir = ini_get("xhprof.output_dir"); + if (empty($dir)) { + $dir = "/tmp"; + $this->xhprof_error("Warning: Must specify directory location for XHProf runs. " . + "Trying {$dir} as default. You can either pass the " . + "directory location as an argument to the constructor " . + "for XHProfRuns_Default() or set xhprof.output_dir " . + "ini param."); + } + } + $this->dir = $dir; + } + + private function gen_run_id($type) + { + return md5(uniqid('', true) . posix_getpid() . gethostname() . mt_rand(1, 0x7FFFFF)); + } + + private function file_name($run_id, $type) + { + $file = "$run_id.$type." . $this->suffix; + if (!empty($this->dir)) { + $file = $this->dir . "/" . $file; + } + $dir = dirname($file); + if (file_exists($dir) == false) { + mkdir($dir, 0, true); + } + return $file; + } + + + public function get_run($run_id, $type, &$run_desc) + { + $file_name = $this->file_name($run_id, $type); + if (!file_exists($file_name)) { + $this->xhprof_error("Could not find file $file_name"); + $run_desc = "Invalid Run Id = $run_id"; + return null; + } + $contents = file_get_contents($file_name); + $run_desc = "XHProf Run (Namespace=$type)"; + return unserialize($contents); + } + + public function save_run($xhprof_data, $type, $run_id = null) + { + $xhprof_data = serialize($xhprof_data); + if ($run_id === null) { + $run_id = $this->gen_run_id($type); + } + $file_name = $this->file_name($run_id, $type); + $file = fopen($file_name, 'w'); + if ($file) { + fwrite($file, $xhprof_data); + fclose($file); + } else { + $this->xhprof_error("Could not open $file_name\n"); + } + // echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n"; + return $run_id; + } + + function list_runs() + { + if (is_dir($this->dir)) { + echo "<hr/>Existing runs:\n<ul>\n"; + $files = glob("{$this->dir}/*.{$this->suffix}"); + usort($files, create_function('$a,$b', 'return filemtime($b) - filemtime($a);')); + foreach ($files as $file) { + list($run, $source) = explode('.', basename($file)); + echo '<li><a href="' . htmlentities($_SERVER['SCRIPT_NAME']) + . '?run=' . htmlentities($run) . '&source=' + . htmlentities($source) . '">' + . htmlentities(basename($file)) . "</a><small> " + . date("Y-m-d H:i:s", filemtime($file)) . "</small></li>\n"; + } + echo "</ul>\n"; + } + } + + public function xhprof_error($message) + { + error_log($message); + } +} \ No newline at end of file diff --git a/yaf/Hood/Debug/XHProf/display/typeahead_common.php b/yaf/Hood/Debug/XHProf/display/typeahead_common.php new file mode 100644 index 0000000..0cd304d --- /dev/null +++ b/yaf/Hood/Debug/XHProf/display/typeahead_common.php @@ -0,0 +1,79 @@ +<?php +// Copyright (c) 2009 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/** + * AJAX endpoint for XHProf function name typeahead is implemented + * as a thin wrapper around this file. The wrapper must set up + * the global $xhprof_runs_impl to correspond to an object that + * implements the iXHProfRuns interface. + * + * @author(s) Kannan Muthukkaruppan + * Changhao Jiang + */ + + +require_once $GLOBALS['XHPROF_LIB_ROOT'].'/utils/xhprof_lib.php'; + +// param name, its type, and default value +$params = array('q' => array(XHPROF_STRING_PARAM, ''), + 'run' => array(XHPROF_STRING_PARAM, ''), + 'run1' => array(XHPROF_STRING_PARAM, ''), + 'run2' => array(XHPROF_STRING_PARAM, ''), + 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), + ); + +// pull values of these params, and create named globals for each param +xhprof_param_init($params); + +if (!empty($run)) { + + // single run mode + $raw_data = $xhprof_runs_impl->get_run($run, $source, $desc_unused); + $functions = xhprof_get_matching_functions($q, $raw_data); + +} else if (!empty($run1) && !empty($run2)) { + + // diff mode + $raw_data = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); + $functions1 = xhprof_get_matching_functions($q, $raw_data); + + $raw_data = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); + $functions2 = xhprof_get_matching_functions($q, $raw_data); + + + $functions = array_unique(array_merge($functions1, $functions2)); + asort($functions); +} else { + xhprof_error("no valid runs specified to typeahead endpoint"); + $functions = array(); +} + +// If exact match is present move it to the front +if (in_array($q, $functions)) { + $old_functions = $functions; + + $functions = array($q); + foreach ($old_functions as $f) { + // exact match case has already been added to the front + if ($f != $q) { + $functions[] = $f; + } + } +} + +foreach ($functions as $f) { + echo $f."\n"; +} diff --git a/yaf/Hood/Debug/XHProf/display/xhprof.php b/yaf/Hood/Debug/XHProf/display/xhprof.php new file mode 100644 index 0000000..a57ec17 --- /dev/null +++ b/yaf/Hood/Debug/XHProf/display/xhprof.php @@ -0,0 +1,1439 @@ +<?php +// Copyright (c) 2009 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// +// XHProf: A Hierarchical Profiler for PHP +// +// XHProf has two components: +// +// * This module is the UI/reporting component, used +// for viewing results of XHProf runs from a browser. +// +// * Data collection component: This is implemented +// as a PHP extension (XHProf). +// +// @author Kannan Muthukkaruppan +// + +if (!isset($GLOBALS['XHPROF_LIB_ROOT'])) { + // by default, the parent directory is XHPROF lib root + $GLOBALS['XHPROF_LIB_ROOT'] = realpath(dirname(__FILE__) . '/..'); +} + +require_once $GLOBALS['XHPROF_LIB_ROOT'].'/utils/xhprof_lib.php'; +require_once $GLOBALS['XHPROF_LIB_ROOT'].'/utils/callgraph_utils.php'; +require_once $GLOBALS['XHPROF_LIB_ROOT'].'/utils/xhprof_runs.php'; + + +/** + * Our coding convention disallows relative paths in hrefs. + * Get the base URL path from the SCRIPT_NAME. + */ +$base_path = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\'); + + +/** + * Generate references to required stylesheets & javascript. + * + * If the calling script (such as index.php) resides in + * a different location that than 'xhprof_html' directory the + * caller must provide the URL path to 'xhprof_html' directory + * so that the correct location of the style sheets/javascript + * can be specified in the generated HTML. + * + */ +function xhprof_include_js_css($ui_dir_url_path = null) { + + if (empty($ui_dir_url_path)) { + $ui_dir_url_path = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\'); + } + + // style sheets + echo "<link href='$ui_dir_url_path/css/xhprof.css' rel='stylesheet' ". + " type='text/css' />"; + echo "<link href='$ui_dir_url_path/jquery/jquery.tooltip.css' ". + " rel='stylesheet' type='text/css' />"; + echo "<link href='$ui_dir_url_path/jquery/jquery.autocomplete.css' ". + " rel='stylesheet' type='text/css' />"; + + // javascript + echo "<script src='$ui_dir_url_path/jquery/jquery-1.2.6.js'>". + "</script>"; + echo "<script src='$ui_dir_url_path/jquery/jquery.tooltip.js'>". + "</script>"; + echo "<script src='$ui_dir_url_path/jquery/jquery.autocomplete.js'>" + ."</script>"; + echo "<script src='$ui_dir_url_path/js/xhprof_report.js'></script>"; +} + + +/* + * Formats call counts for XHProf reports. + * + * Description: + * Call counts in single-run reports are integer values. + * However, call counts for aggregated reports can be + * fractional. This function will print integer values + * without decimal point, but with commas etc. + * + * 4000 ==> 4,000 + * + * It'll round fractional values to decimal precision of 3 + * 4000.1212 ==> 4,000.121 + * 4000.0001 ==> 4,000 + * + */ +function xhprof_count_format($num) { + $num = round($num, 3); + if (round($num) == $num) { + return number_format($num); + } else { + return number_format($num, 3); + } +} + +function xhprof_percent_format($s, $precision = 1) { + return sprintf('%.'.$precision.'f%%', 100 * $s); +} + +/** + * Implodes the text for a bunch of actions (such as links, forms, + * into a HTML list and returns the text. + */ +function xhprof_render_actions($actions) { + $out = array(); + + if (count($actions)) { + $out[] = '<ul class="xhprof_actions">'; + foreach ($actions as $action) { + $out[] = '<li>'.$action.'</li>'; + } + $out[] = '</ul>'; + } + + return implode('', $out); +} + + +/** + * @param html-str $content the text/image/innerhtml/whatever for the link + * @param raw-str $href + * @param raw-str $class + * @param raw-str $id + * @param raw-str $title + * @param raw-str $target + * @param raw-str $onclick + * @param raw-str $style + * @param raw-str $access + * @param raw-str $onmouseover + * @param raw-str $onmouseout + * @param raw-str $onmousedown + * @param raw-str $dir + * @param raw-str $rel + */ +function xhprof_render_link($content, $href, $class='', $id='', $title='', + $target='', + $onclick='', $style='', $access='', $onmouseover='', + $onmouseout='', $onmousedown='') { + + if (!$content) { + return ''; + } + + if ($href) { + $link = '<a href="' . ($href) . '"'; + } else { + $link = '<span'; + } + + if ($class) { + $link .= ' class="' . ($class) . '"'; + } + if ($id) { + $link .= ' id="' . ($id) . '"'; + } + if ($title) { + $link .= ' title="' . ($title) . '"'; + } + if ($target) { + $link .= ' target="' . ($target) . '"'; + } + if ($onclick && $href) { + $link .= ' onclick="' . ($onclick) . '"'; + } + if ($style && $href) { + $link .= ' style="' . ($style) . '"'; + } + if ($access && $href) { + $link .= ' accesskey="' . ($access) . '"'; + } + if ($onmouseover) { + $link .= ' onmouseover="' . ($onmouseover) . '"'; + } + if ($onmouseout) { + $link .= ' onmouseout="' . ($onmouseout) . '"'; + } + if ($onmousedown) { + $link .= ' onmousedown="' . ($onmousedown) . '"'; + } + + $link .= '>'; + $link .= $content; + if ($href) { + $link .= '</a>'; + } else { + $link .= '</span>'; + } + + return $link; +} + + +// default column to sort on -- wall time +$sort_col = "wt"; + +// default is "single run" report +$diff_mode = false; + +// call count data present? +$display_calls = true; + +// The following column headers are sortable +$sortable_columns = array("fn" => 1, + "ct" => 1, + "wt" => 1, + "excl_wt" => 1, + "ut" => 1, + "excl_ut" => 1, + "st" => 1, + "excl_st" => 1, + "mu" => 1, + "excl_mu" => 1, + "pmu" => 1, + "excl_pmu" => 1, + "cpu" => 1, + "excl_cpu" => 1, + "samples" => 1, + "excl_samples" => 1 + ); + +// Textual descriptions for column headers in "single run" mode +$descriptions = array( + "fn" => "Function Name", + "ct" => "Calls", + "Calls%" => "Calls%", + + "wt" => "Incl. Wall Time<br>(microsec)", + "IWall%" => "IWall%", + "excl_wt" => "Excl. Wall Time<br>(microsec)", + "EWall%" => "EWall%", + + "ut" => "Incl. User<br>(microsecs)", + "IUser%" => "IUser%", + "excl_ut" => "Excl. User<br>(microsec)", + "EUser%" => "EUser%", + + "st" => "Incl. Sys <br>(microsec)", + "ISys%" => "ISys%", + "excl_st" => "Excl. Sys <br>(microsec)", + "ESys%" => "ESys%", + + "cpu" => "Incl. CPU<br>(microsecs)", + "ICpu%" => "ICpu%", + "excl_cpu" => "Excl. CPU<br>(microsec)", + "ECpu%" => "ECPU%", + + "mu" => "Incl.<br>MemUse<br>(bytes)", + "IMUse%" => "IMemUse%", + "excl_mu" => "Excl.<br>MemUse<br>(bytes)", + "EMUse%" => "EMemUse%", + + "pmu" => "Incl.<br> PeakMemUse<br>(bytes)", + "IPMUse%" => "IPeakMemUse%", + "excl_pmu" => "Excl.<br>PeakMemUse<br>(bytes)", + "EPMUse%" => "EPeakMemUse%", + + "samples" => "Incl. Samples", + "ISamples%" => "ISamples%", + "excl_samples" => "Excl. Samples", + "ESamples%" => "ESamples%", + ); + +// Formatting Callback Functions... +$format_cbk = array( + "fn" => "", + "ct" => "xhprof_count_format", + "Calls%" => "xhprof_percent_format", + + "wt" => "number_format", + "IWall%" => "xhprof_percent_format", + "excl_wt" => "number_format", + "EWall%" => "xhprof_percent_format", + + "ut" => "number_format", + "IUser%" => "xhprof_percent_format", + "excl_ut" => "number_format", + "EUser%" => "xhprof_percent_format", + + "st" => "number_format", + "ISys%" => "xhprof_percent_format", + "excl_st" => "number_format", + "ESys%" => "xhprof_percent_format", + + "cpu" => "number_format", + "ICpu%" => "xhprof_percent_format", + "excl_cpu" => "number_format", + "ECpu%" => "xhprof_percent_format", + + "mu" => "number_format", + "IMUse%" => "xhprof_percent_format", + "excl_mu" => "number_format", + "EMUse%" => "xhprof_percent_format", + + "pmu" => "number_format", + "IPMUse%" => "xhprof_percent_format", + "excl_pmu" => "number_format", + "EPMUse%" => "xhprof_percent_format", + + "samples" => "number_format", + "ISamples%" => "xhprof_percent_format", + "excl_samples" => "number_format", + "ESamples%" => "xhprof_percent_format", + ); + + +// Textual descriptions for column headers in "diff" mode +$diff_descriptions = array( + "fn" => "Function Name", + "ct" => "Calls Diff", + "Calls%" => "Calls<br>Diff%", + + "wt" => "Incl. Wall<br>Diff<br>(microsec)", + "IWall%" => "IWall<br> Diff%", + "excl_wt" => "Excl. Wall<br>Diff<br>(microsec)", + "EWall%" => "EWall<br>Diff%", + + "ut" => "Incl. User Diff<br>(microsec)", + "IUser%" => "IUser<br>Diff%", + "excl_ut" => "Excl. User<br>Diff<br>(microsec)", + "EUser%" => "EUser<br>Diff%", + + "cpu" => "Incl. CPU Diff<br>(microsec)", + "ICpu%" => "ICpu<br>Diff%", + "excl_cpu" => "Excl. CPU<br>Diff<br>(microsec)", + "ECpu%" => "ECpu<br>Diff%", + + "st" => "Incl. Sys Diff<br>(microsec)", + "ISys%" => "ISys<br>Diff%", + "excl_st" => "Excl. Sys Diff<br>(microsec)", + "ESys%" => "ESys<br>Diff%", + + "mu" => "Incl.<br>MemUse<br>Diff<br>(bytes)", + "IMUse%" => "IMemUse<br>Diff%", + "excl_mu" => "Excl.<br>MemUse<br>Diff<br>(bytes)", + "EMUse%" => "EMemUse<br>Diff%", + + "pmu" => "Incl.<br> PeakMemUse<br>Diff<br>(bytes)", + "IPMUse%" => "IPeakMemUse<br>Diff%", + "excl_pmu" => "Excl.<br>PeakMemUse<br>Diff<br>(bytes)", + "EPMUse%" => "EPeakMemUse<br>Diff%", + + "samples" => "Incl. Samples Diff", + "ISamples%" => "ISamples Diff%", + "excl_samples" => "Excl. Samples Diff", + "ESamples%" => "ESamples Diff%", + ); + +// columns that'll be displayed in a top-level report +$stats = array(); + +// columns that'll be displayed in a function's parent/child report +$pc_stats = array(); + +// Various total counts +$totals = 0; +$totals_1 = 0; +$totals_2 = 0; + +/* + * The subset of $possible_metrics that is present in the raw profile data. + */ +$metrics = null; + +/** + * Callback comparison operator (passed to usort() for sorting array of + * tuples) that compares array elements based on the sort column + * specified in $sort_col (global parameter). + * + * @author Kannan + */ +function sort_cbk($a, $b) { + global $sort_col; + global $diff_mode; + + if ($sort_col == "fn") { + + // case insensitive ascending sort for function names + $left = strtoupper($a["fn"]); + $right = strtoupper($b["fn"]); + + if ($left == $right) + return 0; + return ($left < $right) ? -1 : 1; + + } else { + + // descending sort for all others + $left = $a[$sort_col]; + $right = $b[$sort_col]; + + // if diff mode, sort by absolute value of regression/improvement + if ($diff_mode) { + $left = abs($left); + $right = abs($right); + } + + if ($left == $right) + return 0; + return ($left > $right) ? -1 : 1; + } +} + +/** + * Get the appropriate description for a statistic + * (depending upon whether we are in diff report mode + * or single run report mode). + * + * @author Kannan + */ +function stat_description($stat) { + global $descriptions; + global $diff_descriptions; + global $diff_mode; + + if ($diff_mode) { + return $diff_descriptions[$stat]; + } else { + return $descriptions[$stat]; + } +} + + +/** + * Analyze raw data & generate the profiler report + * (common for both single run mode and diff mode). + * + * @author: Kannan + */ +function profiler_report ($url_params, + $rep_symbol, + $sort, + $run1, + $run1_desc, + $run1_data, + $run2 = 0, + $run2_desc = "", + $run2_data = array()) { + global $totals; + global $totals_1; + global $totals_2; + global $stats; + global $pc_stats; + global $diff_mode; + global $base_path; + + // if we are reporting on a specific function, we can trim down + // the report(s) to just stuff that is relevant to this function. + // That way compute_flat_info()/compute_diff() etc. do not have + // to needlessly work hard on churning irrelevant data. + if (!empty($rep_symbol)) { + $run1_data = xhprof_trim_run($run1_data, array($rep_symbol)); + if ($diff_mode) { + $run2_data = xhprof_trim_run($run2_data, array($rep_symbol)); + } + } + + if ($diff_mode) { + $run_delta = xhprof_compute_diff($run1_data, $run2_data); + $symbol_tab = xhprof_compute_flat_info($run_delta, $totals); + $symbol_tab1 = xhprof_compute_flat_info($run1_data, $totals_1); + $symbol_tab2 = xhprof_compute_flat_info($run2_data, $totals_2); + } else { + $symbol_tab = xhprof_compute_flat_info($run1_data, $totals); + } + + $run1_txt = sprintf("<b>Run #%s:</b> %s", + $run1, $run1_desc); + + $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, + 'symbol'), + 'all'); + + $top_link_query_string = "$base_path/?" . http_build_query($base_url_params); + + if ($diff_mode) { + $diff_text = "Diff"; + $base_url_params = xhprof_array_unset($base_url_params, 'run1'); + $base_url_params = xhprof_array_unset($base_url_params, 'run2'); + $run1_link = xhprof_render_link('View Run #' . $run1, + "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', + $run1))); + $run2_txt = sprintf("<b>Run #%s:</b> %s", + $run2, $run2_desc); + + $run2_link = xhprof_render_link('View Run #' . $run2, + "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', + $run2))); + } else { + $diff_text = "Run"; + } + + // set up the action links for operations that can be done on this report + $links = array(); + $links [] = xhprof_render_link("View Top Level $diff_text Report", + $top_link_query_string); + + if ($diff_mode) { + $inverted_params = $url_params; + $inverted_params['run1'] = $url_params['run2']; + $inverted_params['run2'] = $url_params['run1']; + + // view the different runs or invert the current diff + $links [] = $run1_link; + $links [] = $run2_link; + $links [] = xhprof_render_link('Invert ' . $diff_text . ' Report', + "$base_path/?". + http_build_query($inverted_params)); + } + + // lookup function typeahead form + $links [] = '<input class="function_typeahead" ' . + ' type="input" size="40" maxlength="100" />'; + + echo xhprof_render_actions($links); + + + echo + '<dl class=phprof_report_info>' . + ' <dt>' . $diff_text . ' Report</dt>' . + ' <dd>' . ($diff_mode ? + $run1_txt . '<br><b>vs.</b><br>' . $run2_txt : + $run1_txt) . + ' </dd>' . + ' <dt>Tip</dt>' . + ' <dd>Click a function name below to drill down.</dd>' . + '</dl>' . + '<div style="clear: both; margin: 3em 0em;"></div>'; + + // data tables + if (!empty($rep_symbol)) { + if (!isset($symbol_tab[$rep_symbol])) { + echo "<hr>Symbol <b>$rep_symbol</b> not found in XHProf run</b><hr>"; + return; + } + + /* single function report with parent/child information */ + if ($diff_mode) { + $info1 = isset($symbol_tab1[$rep_symbol]) ? + $symbol_tab1[$rep_symbol] : null; + $info2 = isset($symbol_tab2[$rep_symbol]) ? + $symbol_tab2[$rep_symbol] : null; + symbol_report($url_params, $run_delta, $symbol_tab[$rep_symbol], + $sort, $rep_symbol, + $run1, $info1, + $run2, $info2); + } else { + symbol_report($url_params, $run1_data, $symbol_tab[$rep_symbol], + $sort, $rep_symbol, $run1); + } + } else { + /* flat top-level report of all functions */ + full_report($url_params, $symbol_tab, $sort, $run1, $run2); + } +} + +/** + * Computes percentage for a pair of values, and returns it + * in string format. + */ +function pct($a, $b) { + if ($b == 0) { + return "N/A"; + } else { + $res = (round(($a * 1000 / $b)) / 10); + return $res; + } +} + +/** + * Given a number, returns the td class to use for display. + * + * For instance, negative numbers in diff reports comparing two runs (run1 & run2) + * represent improvement from run1 to run2. We use green to display those deltas, + * and red for regression deltas. + */ +function get_print_class($num, $bold) { + global $vbar; + global $vbbar; + global $vrbar; + global $vgbar; + global $diff_mode; + + if ($bold) { + if ($diff_mode) { + if ($num <= 0) { + $class = $vgbar; // green (improvement) + } else { + $class = $vrbar; // red (regression) + } + } else { + $class = $vbbar; // blue + } + } + else { + $class = $vbar; // default (black) + } + + return $class; +} + +/** + * Prints a <td> element with a numeric value. + */ +function print_td_num($num, $fmt_func, $bold=false, $attributes=null) { + + $class = get_print_class($num, $bold); + + if (!empty($fmt_func) && is_numeric($num) ) { + $num = call_user_func($fmt_func, $num); + } + + print("<td $attributes $class>$num</td>\n"); +} + +/** + * Prints a <td> element with a pecentage. + */ +function print_td_pct($numer, $denom, $bold=false, $attributes=null) { + global $vbar; + global $vbbar; + global $diff_mode; + + $class = get_print_class($numer, $bold); + + if ($denom == 0) { + $pct = "N/A%"; + } else { + $pct = xhprof_percent_format($numer / abs($denom)); + } + + print("<td $attributes $class>$pct</td>\n"); +} + +/** + * Print "flat" data corresponding to one function. + * + * @author Kannan + */ +function print_function_info($url_params, $info, $sort, $run1, $run2) { + static $odd_even = 0; + + global $totals; + global $sort_col; + global $metrics; + global $format_cbk; + global $display_calls; + global $base_path; + + // Toggle $odd_or_even + $odd_even = 1 - $odd_even; + + if ($odd_even) { + print("<tr>"); + } + else { + print('<tr bgcolor="#e5e5e5">'); + } + + $href = "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'symbol', $info["fn"])); + + print('<td>'); + print(xhprof_render_link($info["fn"], $href)); + print_source_link($info); + print("</td>\n"); + + if ($display_calls) { + // Call Count.. + print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct")); + print_td_pct($info["ct"], $totals["ct"], ($sort_col == "ct")); + } + + // Other metrics.. + foreach ($metrics as $metric) { + // Inclusive metric + print_td_num($info[$metric], $format_cbk[$metric], + ($sort_col == $metric)); + print_td_pct($info[$metric], $totals[$metric], + ($sort_col == $metric)); + + // Exclusive Metric + print_td_num($info["excl_" . $metric], + $format_cbk["excl_" . $metric], + ($sort_col == "excl_" . $metric)); + print_td_pct($info["excl_" . $metric], + $totals[$metric], + ($sort_col == "excl_" . $metric)); + } + + print("</tr>\n"); +} + +/** + * Print non-hierarchical (flat-view) of profiler data. + * + * @author Kannan + */ +function print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit) { + + global $stats; + global $sortable_columns; + global $vwbar; + global $base_path; + + $size = count($flat_data); + if (!$limit) { // no limit + $limit = $size; + $display_link = ""; + } else { + $display_link = xhprof_render_link(" [ <b class=bubble>display all </b>]", + "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'all', 1))); + } + + print("<h3 align=center>$title $display_link</h3><br>"); + + print('<table border=1 cellpadding=2 cellspacing=1 width="90%" ' + .'rules=rows bordercolor="#bdc7d8" align=center>'); + print('<tr bgcolor="#bdc7d8" align=right>'); + + foreach ($stats as $stat) { + $desc = stat_description($stat); + if (array_key_exists($stat, $sortable_columns)) { + $href = "$base_path/?" + . http_build_query(xhprof_array_set($url_params, 'sort', $stat)); + $header = xhprof_render_link($desc, $href); + } else { + $header = $desc; + } + + if ($stat == "fn") + print("<th align=left><nobr>$header</th>"); + else print("<th " . $vwbar . "><nobr>$header</th>"); + } + print("</tr>\n"); + + if ($limit >= 0) { + $limit = min($size, $limit); + for ($i = 0; $i < $limit; $i++) { + print_function_info($url_params, $flat_data[$i], $sort, $run1, $run2); + } + } else { + // if $limit is negative, print abs($limit) items starting from the end + $limit = min($size, abs($limit)); + for ($i = 0; $i < $limit; $i++) { + print_function_info($url_params, $flat_data[$size - $i - 1], $sort, $run1, $run2); + } + } + print("</table>"); + + // let's print the display all link at the bottom as well... + if ($display_link) { + echo '<div style="text-align: left; padding: 2em">' . $display_link . '</div>'; + } + +} + +/** + * Generates a tabular report for all functions. This is the top-level report. + * + * @author Kannan + */ +function full_report($url_params, $symbol_tab, $sort, $run1, $run2) { + global $vwbar; + global $vbar; + global $totals; + global $totals_1; + global $totals_2; + global $metrics; + global $diff_mode; + global $descriptions; + global $sort_col; + global $format_cbk; + global $display_calls; + global $base_path; + + $possible_metrics = xhprof_get_possible_metrics(); + + if ($diff_mode) { + + $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, + 'run1'), + 'run2'); + $href1 = "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', $run1)); + $href2 = "$base_path/?" . + http_build_query(xhprof_array_set($base_url_params, + 'run', $run2)); + + print("<h3><center>Overall Diff Summary</center></h3>"); + print('<table border=1 cellpadding=2 cellspacing=1 width="30%" ' + .'rules=rows bordercolor="#bdc7d8" align=center>' . "\n"); + print('<tr bgcolor="#bdc7d8" align=right>'); + print("<th></th>"); + print("<th $vwbar>" . xhprof_render_link("Run #$run1", $href1) . "</th>"); + print("<th $vwbar>" . xhprof_render_link("Run #$run2", $href2) . "</th>"); + print("<th $vwbar>Diff</th>"); + print("<th $vwbar>Diff%</th>"); + print('</tr>'); + + if ($display_calls) { + print('<tr>'); + print("<td>Number of Function Calls</td>"); + print_td_num($totals_1["ct"], $format_cbk["ct"]); + print_td_num($totals_2["ct"], $format_cbk["ct"]); + print_td_num($totals_2["ct"] - $totals_1["ct"], $format_cbk["ct"], true); + print_td_pct($totals_2["ct"] - $totals_1["ct"], $totals_1["ct"], true); + print('</tr>'); + } + + foreach ($metrics as $metric) { + $m = $metric; + print('<tr>'); + print("<td>" . str_replace("<br>", " ", $descriptions[$m]) . "</td>"); + print_td_num($totals_1[$m], $format_cbk[$m]); + print_td_num($totals_2[$m], $format_cbk[$m]); + print_td_num($totals_2[$m] - $totals_1[$m], $format_cbk[$m], true); + print_td_pct($totals_2[$m] - $totals_1[$m], $totals_1[$m], true); + print('<tr>'); + } + print('</table>'); + + $callgraph_report_title = '[View Regressions/Improvements using Callgraph Diff]'; + + } else { + print("<p><center>\n"); + + print('<table cellpadding=2 cellspacing=1 width="30%" ' + .'bgcolor="#bdc7d8" align=center>' . "\n"); + echo "<tr>"; + echo "<th style='text-align:right'>Overall Summary</th>"; + echo "<th></th>"; + echo "</tr>"; + + foreach ($metrics as $metric) { + echo "<tr>"; + echo "<td style='text-align:right; font-weight:bold'>Total " + . str_replace("<br>", " ", stat_description($metric)) . ":</td>"; + echo "<td>" . number_format($totals[$metric]) . " " + . $possible_metrics[$metric][1] . "</td>"; + echo "</tr>"; + } + + if ($display_calls) { + echo "<tr>"; + echo "<td style='text-align:right; font-weight:bold'>Number of Function Calls:</td>"; + echo "<td>" . number_format($totals['ct']) . "</td>"; + echo "</tr>"; + } + + echo "</table>"; + print("</center></p>\n"); + + $callgraph_report_title = '[View Full Callgraph]'; + } + + print("<center><br><h3>" . + xhprof_render_link($callgraph_report_title, + "$base_path/callgraph.php" . "?" . http_build_query($url_params)) + . "</h3></center>"); + + + $flat_data = array(); + foreach ($symbol_tab as $symbol => $info) { + $tmp = $info; + $tmp["fn"] = $symbol; + $flat_data[] = $tmp; + } + usort($flat_data, 'sort_cbk'); + + print("<br>"); + + if (!empty($url_params['all'])) { + $all = true; + $limit = 0; // display all rows + } else { + $all = false; + $limit = 100; // display only limited number of rows + } + + $desc = str_replace("<br>", " ", $descriptions[$sort_col]); + + if ($diff_mode) { + if ($all) { + $title = "Total Diff Report: ' + .'Sorted by absolute value of regression/improvement in $desc"; + } else { + $title = "Top 100 <i style='color:red'>Regressions</i>/" + . "<i style='color:green'>Improvements</i>: " + . "Sorted by $desc Diff"; + } + } else { + if ($all) { + $title = "Sorted by $desc"; + } else { + $title = "Displaying top $limit functions: Sorted by $desc"; + } + } + print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit); +} + + +/** + * Return attribute names and values to be used by javascript tooltip. + */ +function get_tooltip_attributes($type, $metric) { + return "type='$type' metric='$metric'"; +} + +/** + * Print info for a parent or child function in the + * parent & children report. + * + * @author Kannan + */ +function pc_info($info, $base_ct, $base_info, $parent) { + global $sort_col; + global $metrics; + global $format_cbk; + global $display_calls; + + if ($parent) + $type = "Parent"; + else $type = "Child"; + + if ($display_calls) { + $mouseoverct = get_tooltip_attributes($type, "ct"); + /* call count */ + print_td_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct"), $mouseoverct); + print_td_pct($info["ct"], $base_ct, ($sort_col == "ct"), $mouseoverct); + } + + /* Inclusive metric values */ + foreach ($metrics as $metric) { + print_td_num($info[$metric], $format_cbk[$metric], + ($sort_col == $metric), + get_tooltip_attributes($type, $metric)); + print_td_pct($info[$metric], $base_info[$metric], ($sort_col == $metric), + get_tooltip_attributes($type, $metric)); + } +} + +function print_pc_array($url_params, $results, $base_ct, $base_info, $parent, + $run1, $run2) { + global $base_path; + + // Construct section title + if ($parent) { + $title = 'Parent function'; + } + else { + $title = 'Child function'; + } + if (count($results) > 1) { + $title .= 's'; + } + + print("<tr bgcolor='#e0e0ff'><td>"); + print("<b><i><center>" . $title . "</center></i></b>"); + print("</td></tr>"); + + $odd_even = 0; + foreach ($results as $info) { + $href = "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'symbol', $info["fn"])); + + $odd_even = 1 - $odd_even; + + if ($odd_even) { + print('<tr>'); + } + else { + print('<tr bgcolor="#e5e5e5">'); + } + + print("<td>" . xhprof_render_link($info["fn"], $href)); + print_source_link($info); + print("</td>"); + pc_info($info, $base_ct, $base_info, $parent); + print("</tr>"); + } +} + +function print_source_link($info) { + if (strncmp($info['fn'], 'run_init', 8) && $info['fn'] !== 'main()') { + if (defined('XHPROF_SYMBOL_LOOKUP_URL')) { + $link = xhprof_render_link( + 'source', + XHPROF_SYMBOL_LOOKUP_URL . '?symbol='.rawurlencode($info["fn"])); + print(' ('.$link.')'); + } + } +} + + +function print_symbol_summary($symbol_info, $stat, $base) { + + $val = $symbol_info[$stat]; + $desc = str_replace("<br>", " ", stat_description($stat)); + + print("$desc: </td>"); + print(number_format($val)); + print(" (" . pct($val, $base) . "% of overall)"); + if (substr($stat, 0, 4) == "excl") { + $func_base = $symbol_info[str_replace("excl_", "", $stat)]; + print(" (" . pct($val, $func_base) . "% of this function)"); + } + print("<br>"); +} + +/** + * Generates a report for a single function/symbol. + * + * @author Kannan + */ +function symbol_report($url_params, + $run_data, $symbol_info, $sort, $rep_symbol, + $run1, + $symbol_info1 = null, + $run2 = 0, + $symbol_info2 = null) { + global $vwbar; + global $vbar; + global $totals; + global $pc_stats; + global $sortable_columns; + global $metrics; + global $diff_mode; + global $descriptions; + global $format_cbk; + global $sort_col; + global $display_calls; + global $base_path; + + $possible_metrics = xhprof_get_possible_metrics(); + + if ($diff_mode) { + $diff_text = "<b>Diff</b>"; + $regr_impr = "<i style='color:red'>Regression</i>/<i style='color:green'>Improvement</i>"; + } else { + $diff_text = ""; + $regr_impr = ""; + } + + if ($diff_mode) { + + $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, + 'run1'), + 'run2'); + $href1 = "$base_path?" + . http_build_query(xhprof_array_set($base_url_params, 'run', $run1)); + $href2 = "$base_path?" + . http_build_query(xhprof_array_set($base_url_params, 'run', $run2)); + + print("<h3 align=center>$regr_impr summary for $rep_symbol<br><br></h3>"); + print('<table border=1 cellpadding=2 cellspacing=1 width="30%" ' + .'rules=rows bordercolor="#bdc7d8" align=center>' . "\n"); + print('<tr bgcolor="#bdc7d8" align=right>'); + print("<th align=left>$rep_symbol</th>"); + print("<th $vwbar><a href=" . $href1 . ">Run #$run1</a></th>"); + print("<th $vwbar><a href=" . $href2 . ">Run #$run2</a></th>"); + print("<th $vwbar>Diff</th>"); + print("<th $vwbar>Diff%</th>"); + print('</tr>'); + print('<tr>'); + + if ($display_calls) { + print("<td>Number of Function Calls</td>"); + print_td_num($symbol_info1["ct"], $format_cbk["ct"]); + print_td_num($symbol_info2["ct"], $format_cbk["ct"]); + print_td_num($symbol_info2["ct"] - $symbol_info1["ct"], + $format_cbk["ct"], true); + print_td_pct($symbol_info2["ct"] - $symbol_info1["ct"], + $symbol_info1["ct"], true); + print('</tr>'); + } + + + foreach ($metrics as $metric) { + $m = $metric; + + // Inclusive stat for metric + print('<tr>'); + print("<td>" . str_replace("<br>", " ", $descriptions[$m]) . "</td>"); + print_td_num($symbol_info1[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); + print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); + print('</tr>'); + + // AVG (per call) Inclusive stat for metric + print('<tr>'); + print("<td>" . str_replace("<br>", " ", $descriptions[$m]) . " per call </td>"); + $avg_info1 = 'N/A'; + $avg_info2 = 'N/A'; + if ($symbol_info1['ct'] > 0) { + $avg_info1 = ($symbol_info1[$m] / $symbol_info1['ct']); + } + if ($symbol_info2['ct'] > 0) { + $avg_info2 = ($symbol_info2[$m] / $symbol_info2['ct']); + } + print_td_num($avg_info1, $format_cbk[$m]); + print_td_num($avg_info2, $format_cbk[$m]); + print_td_num($avg_info2 - $avg_info1, $format_cbk[$m], true); + print_td_pct($avg_info2 - $avg_info1, $avg_info1, true); + print('</tr>'); + + // Exclusive stat for metric + $m = "excl_" . $metric; + print('<tr style="border-bottom: 1px solid black;">'); + print("<td>" . str_replace("<br>", " ", $descriptions[$m]) . "</td>"); + print_td_num($symbol_info1[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m], $format_cbk[$m]); + print_td_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], true); + print_td_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], true); + print('</tr>'); + } + + print('</table>'); + } + + print("<br><h4><center>"); + print("Parent/Child $regr_impr report for <b>$rep_symbol</b>"); + + $callgraph_href = "$base_path/callgraph.php?" + . http_build_query(xhprof_array_set($url_params, 'func', $rep_symbol)); + + print(" <a href='$callgraph_href'>[View Callgraph $diff_text]</a><br>"); + + print("</center></h4><br>"); + + print('<table border=1 cellpadding=2 cellspacing=1 width="90%" ' + .'rules=rows bordercolor="#bdc7d8" align=center>' . "\n"); + print('<tr bgcolor="#bdc7d8" align=right>'); + + foreach ($pc_stats as $stat) { + $desc = stat_description($stat); + if (array_key_exists($stat, $sortable_columns)) { + + $href = "$base_path/?" . + http_build_query(xhprof_array_set($url_params, + 'sort', $stat)); + $header = xhprof_render_link($desc, $href); + } else { + $header = $desc; + } + + if ($stat == "fn") + print("<th align=left><nobr>$header</th>"); + else print("<th " . $vwbar . "><nobr>$header</th>"); + } + print("</tr>"); + + print("<tr bgcolor='#e0e0ff'><td>"); + print("<b><i><center>Current Function</center></i></b>"); + print("</td></tr>"); + + print("<tr>"); + // make this a self-reference to facilitate copy-pasting snippets to e-mails + print("<td><a href=''>$rep_symbol</a>"); + print_source_link(array('fn' => $rep_symbol)); + print("</td>"); + + if ($display_calls) { + // Call Count + print_td_num($symbol_info["ct"], $format_cbk["ct"]); + print_td_pct($symbol_info["ct"], $totals["ct"]); + } + + // Inclusive Metrics for current function + foreach ($metrics as $metric) { + print_td_num($symbol_info[$metric], $format_cbk[$metric], ($sort_col == $metric)); + print_td_pct($symbol_info[$metric], $totals[$metric], ($sort_col == $metric)); + } + print("</tr>"); + + print("<tr bgcolor='#ffffff'>"); + print("<td style='text-align:right;color:blue'>" + ."Exclusive Metrics $diff_text for Current Function</td>"); + + if ($display_calls) { + // Call Count + print("<td $vbar></td>"); + print("<td $vbar></td>"); + } + + // Exclusive Metrics for current function + foreach ($metrics as $metric) { + print_td_num($symbol_info["excl_" . $metric], $format_cbk["excl_" . $metric], + ($sort_col == $metric), + get_tooltip_attributes("Child", $metric)); + print_td_pct($symbol_info["excl_" . $metric], $symbol_info[$metric], + ($sort_col == $metric), + get_tooltip_attributes("Child", $metric)); + } + print("</tr>"); + + // list of callers/parent functions + $results = array(); + if ($display_calls) { + $base_ct = $symbol_info["ct"]; + } else { + $base_ct = 0; + } + foreach ($metrics as $metric) { + $base_info[$metric] = $symbol_info[$metric]; + } + foreach ($run_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if (($child == $rep_symbol) && ($parent)) { + $info_tmp = $info; + $info_tmp["fn"] = $parent; + $results[] = $info_tmp; + } + } + usort($results, 'sort_cbk'); + + if (count($results) > 0) { + print_pc_array($url_params, $results, $base_ct, $base_info, true, + $run1, $run2); + } + + // list of callees/child functions + $results = array(); + $base_ct = 0; + foreach ($run_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if ($parent == $rep_symbol) { + $info_tmp = $info; + $info_tmp["fn"] = $child; + $results[] = $info_tmp; + if ($display_calls) { + $base_ct += $info["ct"]; + } + } + } + usort($results, 'sort_cbk'); + + if (count($results)) { + print_pc_array($url_params, $results, $base_ct, $base_info, false, + $run1, $run2); + } + + print("</table>"); + + // These will be used for pop-up tips/help. + // Related javascript code is in: xhprof_report.js + print("\n"); + print('<script language="javascript">' . "\n"); + print("var func_name = '\"" . $rep_symbol . "\"';\n"); + print("var total_child_ct = " . $base_ct . ";\n"); + if ($display_calls) { + print("var func_ct = " . $symbol_info["ct"] . ";\n"); + } + print("var func_metrics = new Array();\n"); + print("var metrics_col = new Array();\n"); + print("var metrics_desc = new Array();\n"); + if ($diff_mode) { + print("var diff_mode = true;\n"); + } else { + print("var diff_mode = false;\n"); + } + $column_index = 3; // First three columns are Func Name, Calls, Calls% + foreach ($metrics as $metric) { + print("func_metrics[\"" . $metric . "\"] = " . round($symbol_info[$metric]) . ";\n"); + print("metrics_col[\"". $metric . "\"] = " . $column_index . ";\n"); + print("metrics_desc[\"". $metric . "\"] = \"" . $possible_metrics[$metric][2] . "\";\n"); + + // each metric has two columns.. + $column_index += 2; + } + print('</script>'); + print("\n"); + +} + +/** + * Generate the profiler report for a single run. + * + * @author Kannan + */ +function profiler_single_run_report ($url_params, + $xhprof_data, + $run_desc, + $rep_symbol, + $sort, + $run) { + + init_metrics($xhprof_data, $rep_symbol, $sort, false); + + profiler_report($url_params, $rep_symbol, $sort, $run, $run_desc, + $xhprof_data); +} + + + +/** + * Generate the profiler report for diff mode (delta between two runs). + * + * @author Kannan + */ +function profiler_diff_report($url_params, + $xhprof_data1, + $run1_desc, + $xhprof_data2, + $run2_desc, + $rep_symbol, + $sort, + $run1, + $run2) { + + + // Initialize what metrics we'll display based on data in Run2 + init_metrics($xhprof_data2, $rep_symbol, $sort, true); + + profiler_report($url_params, + $rep_symbol, + $sort, + $run1, + $run1_desc, + $xhprof_data1, + $run2, + $run2_desc, + $xhprof_data2); +} + + +/** + * Generate a XHProf Display View given the various URL parameters + * as arguments. The first argument is an object that implements + * the iXHProfRuns interface. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + *. + * @param array $url_params Array of non-default URL params. + * + * @param string $source Category/type of the run. The source in + * combination with the run id uniquely + * determines a profiler run. + * + * @param string $run run id, or comma separated sequence of + * run ids. The latter is used if an aggregate + * report of the runs is desired. + * + * @param string $wts Comma separate list of integers. + * Represents the weighted ratio in + * which which a set of runs will be + * aggregated. [Used only for aggregate + * reports.] + * + * @param string $symbol Function symbol. If non-empty then the + * parent/child view of this function is + * displayed. If empty, a flat-profile view + * of the functions is displayed. + * + * @param string $run1 Base run id (for diff reports) + * + * @param string $run2 New run id (for diff reports) + * + */ +function displayXHProfReport($xhprof_runs_impl, $url_params, $source, + $run, $wts, $symbol, $sort, $run1, $run2) { + + if ($run) { // specific run to display? + + // run may be a single run or a comma separate list of runs + // that'll be aggregated. If "wts" (a comma separated list + // of integral weights is specified), the runs will be + // aggregated in that ratio. + // + $runs_array = explode(",", $run); + + if (count($runs_array) == 1) { + $xhprof_data = $xhprof_runs_impl->get_run($runs_array[0], + $source, + $description); + } else { + if (!empty($wts)) { + $wts_array = explode(",", $wts); + } else { + $wts_array = null; + } + $data = xhprof_aggregate_runs($xhprof_runs_impl, + $runs_array, $wts_array, $source, false); + $xhprof_data = $data['raw']; + $description = $data['description']; + } + + + profiler_single_run_report($url_params, + $xhprof_data, + $description, + $symbol, + $sort, + $run); + + } else if ($run1 && $run2) { // diff report for two runs + + $xhprof_data1 = $xhprof_runs_impl->get_run($run1, $source, $description1); + $xhprof_data2 = $xhprof_runs_impl->get_run($run2, $source, $description2); + + profiler_diff_report($url_params, + $xhprof_data1, + $description1, + $xhprof_data2, + $description2, + $symbol, + $sort, + $run1, + $run2); + + } else { + echo "No XHProf runs specified in the URL."; + if (method_exists($xhprof_runs_impl, 'list_runs')) { + $xhprof_runs_impl->list_runs(); + } + } +} diff --git a/yaf/Hood/Debug/XHProf/utils/callgraph_utils.php b/yaf/Hood/Debug/XHProf/utils/callgraph_utils.php new file mode 100644 index 0000000..90f4c31 --- /dev/null +++ b/yaf/Hood/Debug/XHProf/utils/callgraph_utils.php @@ -0,0 +1,486 @@ +<?php +// Copyright (c) 2009 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/* + * This file contains callgraph image generation related XHProf utility + * functions + * + */ + +// Supported ouput format +$xhprof_legal_image_types = array( + "jpg" => 1, + "gif" => 1, + "png" => 1, + "svg" => 1, // support scalable vector graphic + "ps" => 1, + ); + +/** + * Send an HTTP header with the response. You MUST use this function instead + * of header() so that we can debug header issues because they're virtually + * impossible to debug otherwise. If you try to commit header(), SVN will + * reject your commit. + * + * @param string HTTP header name, like 'Location' + * @param string HTTP header value, like 'http://www.example.com/' + * + */ +function xhprof_http_header($name, $value) { + + if (!$name) { + xhprof_error('http_header usage'); + return null; + } + + if (!is_string($value)) { + xhprof_error('http_header value not a string'); + } + + header($name.': '.$value, true); +} + +/** + * Genearte and send MIME header for the output image to client browser. + * + * @author cjiang + */ +function xhprof_generate_mime_header($type, $length) { + switch ($type) { + case 'jpg': + $mime = 'image/jpeg'; + break; + case 'gif': + $mime = 'image/gif'; + break; + case 'png': + $mime = 'image/png'; + break; + case 'svg': + $mime = 'image/svg+xml'; // content type for scalable vector graphic + break; + case 'ps': + $mime = 'application/postscript'; + default: + $mime = false; + } + + if ($mime) { + xhprof_http_header('Content-type', $mime); + xhprof_http_header('Content-length', (string)$length); + } +} + +/** + * Generate image according to DOT script. This function will spawn a process + * with "dot" command and pipe the "dot_script" to it and pipe out the + * generated image content. + * + * @param dot_script, string, the script for DOT to generate the image. + * @param type, one of the supported image types, see + * $xhprof_legal_image_types. + * @returns, binary content of the generated image on success. empty string on + * failure. + * + * @author cjiang + */ +function xhprof_generate_image_by_dot($dot_script, $type) { + $descriptorspec = array( + // stdin is a pipe that the child will read from + 0 => array("pipe", "r"), + // stdout is a pipe that the child will write to + 1 => array("pipe", "w"), + // stderr is a pipe that the child will write to + 2 => array("pipe", "w") + ); + + $cmd = " dot -T".$type; + + $process = proc_open( $cmd, $descriptorspec, $pipes, sys_get_temp_dir(), array( 'PATH' => getenv( 'PATH' ) ) ); + if (is_resource($process)) { + fwrite($pipes[0], $dot_script); + fclose($pipes[0]); + + $output = stream_get_contents($pipes[1]); + + $err = stream_get_contents($pipes[2]); + if (!empty($err)) { + print "failed to execute cmd: \"$cmd\". stderr: `$err'\n"; + exit; + } + + fclose($pipes[2]); + fclose($pipes[1]); + proc_close($process); + return $output; + } + print "failed to execute cmd \"$cmd\""; + exit(); +} + +/* + * Get the children list of all nodes. + */ +function xhprof_get_children_table($raw_data) { + $children_table = array(); + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if (!isset($children_table[$parent])) { + $children_table[$parent] = array($child); + } else { + $children_table[$parent][] = $child; + } + } + return $children_table; +} + +/** + * Generate DOT script from the given raw phprof data. + * + * @param raw_data, phprof profile data. + * @param threshold, float, the threshold value [0,1). The functions in the + * raw_data whose exclusive wall times ratio are below the + * threshold will be filtered out and won't apprear in the + * generated image. + * @param page, string(optional), the root node name. This can be used to + * replace the 'main()' as the root node. + * @param func, string, the focus function. + * @param critical_path, bool, whether or not to display critical path with + * bold lines. + * @returns, string, the DOT script to generate image. + * + * @author cjiang + */ +function xhprof_generate_dot_script($raw_data, $threshold, $source, $page, + $func, $critical_path, $right=null, + $left=null) { + + $max_width = 5; + $max_height = 3.5; + $max_fontsize = 35; + $max_sizing_ratio = 20; + + $totals; + + if ($left === null) { + // init_metrics($raw_data, null, null); + } + $sym_table = xhprof_compute_flat_info($raw_data, $totals); + + if ($critical_path) { + $children_table = xhprof_get_children_table($raw_data); + $node = "main()"; + $path = array(); + $path_edges = array(); + $visited = array(); + while ($node) { + $visited[$node] = true; + if (isset($children_table[$node])) { + $max_child = null; + foreach ($children_table[$node] as $child) { + + if (isset($visited[$child])) { + continue; + } + if ($max_child === null || + abs($raw_data[xhprof_build_parent_child_key($node, + $child)]["wt"]) > + abs($raw_data[xhprof_build_parent_child_key($node, + $max_child)]["wt"])) { + $max_child = $child; + } + } + if ($max_child !== null) { + $path[$max_child] = true; + $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true; + } + $node = $max_child; + } else { + $node = null; + } + } + } + + // if it is a benchmark callgraph, we make the benchmarked function the root. + if ($source == "bm" && array_key_exists("main()", $sym_table)) { + $total_times = $sym_table["main()"]["ct"]; + $remove_funcs = array("main()", + "hotprofiler_disable", + "call_user_func_array", + "xhprof_disable"); + + foreach ($remove_funcs as $cur_del_func) { + if (array_key_exists($cur_del_func, $sym_table) && + $sym_table[$cur_del_func]["ct"] == $total_times) { + unset($sym_table[$cur_del_func]); + } + } + } + + // use the function to filter out irrelevant functions. + if (!empty($func)) { + $interested_funcs = array(); + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if ($parent == $func || $child == $func) { + $interested_funcs[$parent] = 1; + $interested_funcs[$child] = 1; + } + } + foreach ($sym_table as $symbol => $info) { + if (!array_key_exists($symbol, $interested_funcs)) { + unset($sym_table[$symbol]); + } + } + } + + $result = "digraph call_graph {\n"; + + // Filter out functions whose exclusive time ratio is below threshold, and + // also assign a unique integer id for each function to be generated. In the + // meantime, find the function with the most exclusive time (potentially the + // performance bottleneck). + $cur_id = 0; $max_wt = 0; + foreach ($sym_table as $symbol => $info) { + if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) { + unset($sym_table[$symbol]); + continue; + } + if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) { + $max_wt = abs($info["excl_wt"]); + } + $sym_table[$symbol]["id"] = $cur_id; + $cur_id ++; + } + + // Generate all nodes' information. + foreach ($sym_table as $symbol => $info) { + if ($info["excl_wt"] == 0) { + $sizing_factor = $max_sizing_ratio; + } else { + $sizing_factor = $max_wt / abs($info["excl_wt"]) ; + if ($sizing_factor > $max_sizing_ratio) { + $sizing_factor = $max_sizing_ratio; + } + } + $fillcolor = (($sizing_factor < 1.5) ? + ", style=filled, fillcolor=red" : ""); + + if ($critical_path) { + // highlight nodes along critical path. + if (!$fillcolor && array_key_exists($symbol, $path)) { + $fillcolor = ", style=filled, fillcolor=yellow"; + } + } + + $fontsize = ", fontsize=" + .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1)); + + $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor); + $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor); + + if ($symbol == "main()") { + $shape = "octagon"; + $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n"; + $name .= addslashes(isset($page) ? $page : $symbol); + } else { + $shape = "box"; + $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) . + " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")"; + } + if ($left === null) { + $label = ", label=\"".$name."\\nExcl: " + .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms (" + .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"]) + . ")\\n".$info["ct"]." total calls\""; + } else { + if (isset($left[$symbol]) && isset($right[$symbol])) { + $label = ", label=\"".addslashes($symbol). + "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) + ." ms - " + .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = " + .(sprintf("%.3f",$info["wt"] / 1000.0))." ms". + "\\nExcl: " + .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) + ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) + ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". + "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - " + .(sprintf("%.3f",$right[$symbol]["ct"]))." = " + .(sprintf("%.3f",$info["ct"]))."\""; + } else if (isset($left[$symbol])) { + $label = ", label=\"".addslashes($symbol). + "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) + ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0)) + ." ms"."\\nExcl: " + .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) + ." ms - 0 ms = " + .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". + "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = " + .(sprintf("%.3f",$info["ct"]))."\""; + } else { + $label = ", label=\"".addslashes($symbol). + "\\nInc: 0 ms - " + .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0)) + ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms". + "\\nExcl: 0 ms - " + .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) + ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". + "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"])) + ." = ".(sprintf("%.3f",$info["ct"]))."\""; + } + } + $result .= "N" . $sym_table[$symbol]["id"]; + $result .= "[shape=$shape ".$label.$width + .$height.$fontsize.$fillcolor."];\n"; + } + + // Generate all the edges' information. + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if (isset($sym_table[$parent]) && isset($sym_table[$child]) && + (empty($func) || + (!empty($func) && ($parent == $func || $child == $func)))) { + + $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls"; + + $headlabel = $sym_table[$child]["wt"] > 0 ? + sprintf("%.1f%%", 100 * $info["wt"] + / $sym_table[$child]["wt"]) + : "0.0%"; + + $taillabel = ($sym_table[$parent]["wt"] > 0) ? + sprintf("%.1f%%", + 100 * $info["wt"] / + ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"])) + : "0.0%"; + + $linewidth = 1; + $arrow_size = 1; + + if ($critical_path && + isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) { + $linewidth = 10; $arrow_size = 2; + } + + $result .= "N" . $sym_table[$parent]["id"] . " -> N" + . $sym_table[$child]["id"]; + $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\"," + ." label=\"" + .$label."\", headlabel=\"".$headlabel + ."\", taillabel=\"".$taillabel."\" ]"; + $result .= ";\n"; + + } + } + $result = $result . "\n}"; + + return $result; +} + +function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, + $type, $threshold, $source) { + $total1; + $total2; + + $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); + $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); + + // init_metrics($raw_data1, null, null); + $children_table1 = xhprof_get_children_table($raw_data1); + $children_table2 = xhprof_get_children_table($raw_data2); + $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1); + $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2); + $run_delta = xhprof_compute_diff($raw_data1, $raw_data2); + $script = xhprof_generate_dot_script($run_delta, $threshold, $source, + null, null, true, + $symbol_tab1, $symbol_tab2); + $content = xhprof_generate_image_by_dot($script, $type); + + xhprof_generate_mime_header($type, strlen($content)); + echo $content; +} + +/** + * Generate image content from phprof run id. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + * @param run_id, integer, the unique id for the phprof run, this is the + * primary key for phprof database table. + * @param type, string, one of the supported image types. See also + * $xhprof_legal_image_types. + * @param threshold, float, the threshold value [0,1). The functions in the + * raw_data whose exclusive wall times ratio are below the + * threshold will be filtered out and won't apprear in the + * generated image. + * @param func, string, the focus function. + * @returns, string, the DOT script to generate image. + * + * @author cjiang + */ +function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, + $threshold, $func, $source, + $critical_path) { + if (!$run_id) + return ""; + + $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); + if (!$raw_data) { + xhprof_error("Raw data is empty"); + return ""; + } + + $script = xhprof_generate_dot_script($raw_data, $threshold, $source, + $description, $func, $critical_path); + + $content = xhprof_generate_image_by_dot($script, $type); + return $content; +} + +/** + * Generate image from phprof run id and send it to client. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + * @param run_id, integer, the unique id for the phprof run, this is the + * primary key for phprof database table. + * @param type, string, one of the supported image types. See also + * $xhprof_legal_image_types. + * @param threshold, float, the threshold value [0,1). The functions in the + * raw_data whose exclusive wall times ratio are below the + * threshold will be filtered out and won't apprear in the + * generated image. + * @param func, string, the focus function. + * @param bool, does this run correspond to a PHProfLive run or a dev run? + * @author cjiang + */ +function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold, + $func, $source, $critical_path) { + + $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, + $threshold, + $func, $source, $critical_path); + if (!$content) { + print "Error: either we can not find profile data for run_id ".$run_id + ." or the threshold ".$threshold." is too small or you do not" + ." have 'dot' image generation utility installed."; + exit(); + } + + xhprof_generate_mime_header($type, strlen($content)); + echo $content; +} diff --git a/yaf/Hood/Debug/XHProf/utils/xhprof_lib.php b/yaf/Hood/Debug/XHProf/utils/xhprof_lib.php new file mode 100644 index 0000000..4a07e90 --- /dev/null +++ b/yaf/Hood/Debug/XHProf/utils/xhprof_lib.php @@ -0,0 +1,945 @@ +<?php +// Copyright (c) 2009 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// +// This file contains various XHProf library (utility) functions. +// Do not add any display specific code here. +// + +function xhprof_error($message) { + error_log($message); +} + +/* + * The list of possible metrics collected as part of XHProf that + * require inclusive/exclusive handling while reporting. + * + * @author Kannan + */ +function xhprof_get_possible_metrics() { + static $possible_metrics = + array("wt" => array("Wall", "microsecs", "walltime"), + "ut" => array("User", "microsecs", "user cpu time"), + "st" => array("Sys", "microsecs", "system cpu time"), + "cpu" => array("Cpu", "microsecs", "cpu time"), + "mu" => array("MUse", "bytes", "memory usage"), + "pmu" => array("PMUse", "bytes", "peak memory usage"), + "samples" => array("Samples", "samples", "cpu time")); + return $possible_metrics; +} + +/** + * Initialize the metrics we'll display based on the information + * in the raw data. + * + * @author Kannan + */ +function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) { + global $stats; + global $pc_stats; + global $metrics; + global $diff_mode; + global $sortable_columns; + global $sort_col; + global $display_calls; + + $diff_mode = $diff_report; + + if (!empty($sort)) { + if (array_key_exists($sort, $sortable_columns)) { + $sort_col = $sort; + } else { + print("Invalid Sort Key $sort specified in URL"); + } + } + + // For C++ profiler runs, walltime attribute isn't present. + // In that case, use "samples" as the default sort column. + if (!isset($xhprof_data["main()"]["wt"])) { + + if ($sort_col == "wt") { + $sort_col = "samples"; + } + + // C++ profiler data doesn't have call counts. + // ideally we should check to see if "ct" metric + // is present for "main()". But currently "ct" + // metric is artificially set to 1. So, relying + // on absence of "wt" metric instead. + $display_calls = false; + } else { + $display_calls = true; + } + + // parent/child report doesn't support exclusive times yet. + // So, change sort hyperlinks to closest fit. + if (!empty($rep_symbol)) { + $sort_col = str_replace("excl_", "", $sort_col); + } + + if ($display_calls) { + $stats = array("fn", "ct", "Calls%"); + } else { + $stats = array("fn"); + } + + $pc_stats = $stats; + + $possible_metrics = xhprof_get_possible_metrics(); + foreach ($possible_metrics as $metric => $desc) { + if (isset($xhprof_data["main()"][$metric])) { + $metrics[] = $metric; + // flat (top-level reports): we can compute + // exclusive metrics reports as well. + $stats[] = $metric; + $stats[] = "I" . $desc[0] . "%"; + $stats[] = "excl_" . $metric; + $stats[] = "E" . $desc[0] . "%"; + + // parent/child report for a function: we can + // only breakdown inclusive times correctly. + $pc_stats[] = $metric; + $pc_stats[] = "I" . $desc[0] . "%"; + } + } +} + +/* + * Get the list of metrics present in $xhprof_data as an array. + * + * @author Kannan + */ +function xhprof_get_metrics($xhprof_data) { + + // get list of valid metrics + $possible_metrics = xhprof_get_possible_metrics(); + + // return those that are present in the raw data. + // We'll just look at the root of the subtree for this. + $metrics = array(); + foreach ($possible_metrics as $metric => $desc) { + if (isset($xhprof_data["main()"][$metric])) { + $metrics[] = $metric; + } + } + + return $metrics; +} + +/** + * Takes a parent/child function name encoded as + * "a==>b" and returns array("a", "b"). + * + * @author Kannan + */ +function xhprof_parse_parent_child($parent_child) { + $ret = explode("==>", $parent_child); + + // Return if both parent and child are set + if (isset($ret[1])) { + return $ret; + } + + return array(null, $ret[0]); +} + +/** + * Given parent & child function name, composes the key + * in the format present in the raw data. + * + * @author Kannan + */ +function xhprof_build_parent_child_key($parent, $child) { + if ($parent) { + return $parent . "==>" . $child; + } else { + return $child; + } +} + + +/** + * Checks if XHProf raw data appears to be valid and not corrupted. + * + * @param int $run_id Run id of run to be pruned. + * [Used only for reporting errors.] + * @param array $raw_data XHProf raw data to be pruned + * & validated. + * + * @return bool true on success, false on failure + * + * @author Kannan + */ +function xhprof_valid_run($run_id, $raw_data) { + + $main_info = $raw_data["main()"]; + if (empty($main_info)) { + xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id"); + return false; + } + + // raw data should contain either wall time or samples information... + if (isset($main_info["wt"])) { + $metric = "wt"; + } else if (isset($main_info["samples"])) { + $metric = "samples"; + } else { + xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id"); + return false; + } + + foreach ($raw_data as $info) { + $val = $info[$metric]; + + // basic sanity checks... + if ($val < 0) { + xhprof_error("XHProf: $metric should not be negative: Run ID $run_id" + . serialize($info)); + return false; + } + if ($val > (86400000000)) { + xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id " + . serialize($info)); + return false; + } + } + return true; +} + + +/** + * Return a trimmed version of the XHProf raw data. Note that the raw + * data contains one entry for each unique parent/child function + * combination.The trimmed version of raw data will only contain + * entries where either the parent or child function is in the list + * of $functions_to_keep. + * + * Note: Function main() is also always kept so that overall totals + * can still be obtained from the trimmed version. + * + * @param array XHProf raw data + * @param array array of function names + * + * @return array Trimmed XHProf Report + * + * @author Kannan + */ +function xhprof_trim_run($raw_data, $functions_to_keep) { + + // convert list of functions to a hash with function as the key + $function_map = array_fill_keys($functions_to_keep, 1); + + // always keep main() as well so that overall totals can still + // be computed if need be. + $function_map['main()'] = 1; + + $new_raw_data = array(); + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if (isset($function_map[$parent]) || isset($function_map[$child])) { + $new_raw_data[$parent_child] = $info; + } + } + + return $new_raw_data; +} + +/** + * Takes raw XHProf data that was aggregated over "$num_runs" number + * of runs averages/nomalizes the data. Essentially the various metrics + * collected are divided by $num_runs. + * + * @author Kannan + */ +function xhprof_normalize_metrics($raw_data, $num_runs) { + + if (empty($raw_data) || ($num_runs == 0)) { + return $raw_data; + } + + $raw_data_total = array(); + + if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) { + xhprof_error("XHProf Error: both ==>main() and main() set in raw data..."); + } + + foreach ($raw_data as $parent_child => $info) { + foreach ($info as $metric => $value) { + $raw_data_total[$parent_child][$metric] = ($value / $num_runs); + } + } + + return $raw_data_total; +} + + +/** + * Get raw data corresponding to specified array of runs + * aggregated by certain weightage. + * + * Suppose you have run:5 corresponding to page1.php, + * run:6 corresponding to page2.php, + * and run:7 corresponding to page3.php + * + * and you want to accumulate these runs in a 2:4:1 ratio. You + * can do so by calling: + * + * xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1)); + * + * The above will return raw data for the runs aggregated + * in 2:4:1 ratio. + * + * @param object $xhprof_runs_impl An object that implements + * the iXHProfRuns interface + * @param array $runs run ids of the XHProf runs.. + * @param array $wts integral (ideally) weights for $runs + * @param string $source source to fetch raw data for run from + * @param bool $use_script_name If true, a fake edge from main() to + * to __script::<scriptname> is introduced + * in the raw data so that after aggregations + * the script name is still preserved. + * + * @return array Return aggregated raw data + * + * @author Kannan + */ +function xhprof_aggregate_runs($xhprof_runs_impl, $runs, + $wts, $source="phprof", + $use_script_name=false) { + + $raw_data_total = null; + $raw_data = null; + $metrics = array(); + + $run_count = count($runs); + $wts_count = count($wts); + + if (($run_count == 0) || + (($wts_count > 0) && ($run_count != $wts_count))) { + return array('description' => 'Invalid input..', + 'raw' => null); + } + + $bad_runs = array(); + foreach ($runs as $idx => $run_id) { + + $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); + + // use the first run to derive what metrics to aggregate on. + if ($idx == 0) { + foreach ($raw_data["main()"] as $metric => $val) { + if ($metric != "pmu") { + // for now, just to keep data size small, skip "peak" memory usage + // data while aggregating. + // The "regular" memory usage data will still be tracked. + if (isset($val)) { + $metrics[] = $metric; + } + } + } + } + + if (!xhprof_valid_run($run_id, $raw_data)) { + $bad_runs[] = $run_id; + continue; + } + + if ($use_script_name) { + $page = $description; + + // create a fake function '__script::$page', and have and edge from + // main() to '__script::$page'. We will also need edges to transfer + // all edges originating from main() to now originate from + // '__script::$page' to all function called from main(). + // + // We also weight main() ever so slightly higher so that + // it shows up above the new entry in reports sorted by + // inclusive metrics or call counts. + if ($page) { + foreach ($raw_data["main()"] as $metric => $val) { + $fake_edge[$metric] = $val; + $new_main[$metric] = $val + 0.00001; + } + $raw_data["main()"] = $new_main; + $raw_data[xhprof_build_parent_child_key("main()", + "__script::$page")] + = $fake_edge; + } else { + $use_script_name = false; + } + } + + // if no weights specified, use 1 as the default weightage.. + $wt = ($wts_count == 0) ? 1 : $wts[$idx]; + + // aggregate $raw_data into $raw_data_total with appropriate weight ($wt) + foreach ($raw_data as $parent_child => $info) { + if ($use_script_name) { + // if this is an old edge originating from main(), it now + // needs to be from '__script::$page' + if (substr($parent_child, 0, 9) == "main()==>") { + $child = substr($parent_child, 9); + // ignore the newly added edge from main() + if (substr($child, 0, 10) != "__script::") { + $parent_child = xhprof_build_parent_child_key("__script::$page", + $child); + } + } + } + + if (!isset($raw_data_total[$parent_child])) { + foreach ($metrics as $metric) { + $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]); + } + } else { + foreach ($metrics as $metric) { + $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]); + } + } + } + } + + $runs_string = implode(",", $runs); + + if (isset($wts)) { + $wts_string = "in the ratio (" . implode(":", $wts) . ")"; + $normalization_count = array_sum($wts); + } else { + $wts_string = ""; + $normalization_count = $run_count; + } + + $run_count = $run_count - count($bad_runs); + + $data['description'] = "Aggregated Report for $run_count runs: ". + "$runs_string $wts_string\n"; + $data['raw'] = xhprof_normalize_metrics($raw_data_total, + $normalization_count); + $data['bad_runs'] = $bad_runs; + + return $data; +} + + +/** + * Analyze hierarchical raw data, and compute per-function (flat) + * inclusive and exclusive metrics. + * + * Also, store overall totals in the 2nd argument. + * + * @param array $raw_data XHProf format raw profiler data. + * @param array &$overall_totals OUT argument for returning + * overall totals for various + * metrics. + * @return array Returns a map from function name to its + * call count and inclusive & exclusive metrics + * (such as wall time, etc.). + * + * @author Kannan Muthukkaruppan + */ +function xhprof_compute_flat_info($raw_data, &$overall_totals) { + + global $display_calls; + + $metrics = xhprof_get_metrics($raw_data); + + $overall_totals = array("ct" => 0, + "wt" => 0, + "ut" => 0, + "st" => 0, + "cpu" => 0, + "mu" => 0, + "pmu" => 0, + "samples" => 0 + ); + + // compute inclusive times for each function + $symbol_tab = xhprof_compute_inclusive_times($raw_data); + + /* total metric value is the metric value for "main()" */ + foreach ($metrics as $metric) { + $overall_totals[$metric] = $symbol_tab["main()"][$metric]; + } + + /* + * initialize exclusive (self) metric value to inclusive metric value + * to start with. + * In the same pass, also add up the total number of function calls. + */ + foreach ($symbol_tab as $symbol => $info) { + foreach ($metrics as $metric) { + $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric]; + } + if ($display_calls) { + /* keep track of total number of calls */ + $overall_totals["ct"] += $info["ct"]; + } + } + + /* adjust exclusive times by deducting inclusive time of children */ + foreach ($raw_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if ($parent) { + foreach ($metrics as $metric) { + // make sure the parent exists hasn't been pruned. + if (isset($symbol_tab[$parent])) { + $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric]; + } + } + } + } + + return $symbol_tab; +} + +/** + * Hierarchical diff: + * Compute and return difference of two call graphs: Run2 - Run1. + * + * @author Kannan + */ +function xhprof_compute_diff($xhprof_data1, $xhprof_data2) { + global $display_calls; + + // use the second run to decide what metrics we will do the diff on + $metrics = xhprof_get_metrics($xhprof_data2); + + $xhprof_delta = $xhprof_data2; + + foreach ($xhprof_data1 as $parent_child => $info) { + + if (!isset($xhprof_delta[$parent_child])) { + + // this pc combination was not present in run1; + // initialize all values to zero. + if ($display_calls) { + $xhprof_delta[$parent_child] = array("ct" => 0); + } else { + $xhprof_delta[$parent_child] = array(); + } + foreach ($metrics as $metric) { + $xhprof_delta[$parent_child][$metric] = 0; + } + } + + if ($display_calls) { + $xhprof_delta[$parent_child]["ct"] -= $info["ct"]; + } + + foreach ($metrics as $metric) { + $xhprof_delta[$parent_child][$metric] -= $info[$metric]; + } + } + + return $xhprof_delta; +} + + +/** + * Compute inclusive metrics for function. This code was factored out + * of xhprof_compute_flat_info(). + * + * The raw data contains inclusive metrics of a function for each + * unique parent function it is called from. The total inclusive metrics + * for a function is therefore the sum of inclusive metrics for the + * function across all parents. + * + * @return array Returns a map of function name to total (across all parents) + * inclusive metrics for the function. + * + * @author Kannan + */ +function xhprof_compute_inclusive_times($raw_data) { + global $display_calls; + + $metrics = xhprof_get_metrics($raw_data); + + $symbol_tab = array(); + + /* + * First compute inclusive time for each function and total + * call count for each function across all parents the + * function is called from. + */ + foreach ($raw_data as $parent_child => $info) { + + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + if ($parent == $child) { + /* + * XHProf PHP extension should never trigger this situation any more. + * Recursion is handled in the XHProf PHP extension by giving nested + * calls a unique recursion-depth appended name (for example, foo@1). + */ + xhprof_error("Error in Raw Data: parent & child are both: $parent"); + return; + } + + if (!isset($symbol_tab[$child])) { + + if ($display_calls) { + $symbol_tab[$child] = array("ct" => $info["ct"]); + } else { + $symbol_tab[$child] = array(); + } + foreach ($metrics as $metric) { + $symbol_tab[$child][$metric] = $info[$metric]; + } + } else { + if ($display_calls) { + /* increment call count for this child */ + $symbol_tab[$child]["ct"] += $info["ct"]; + } + + /* update inclusive times/metric for this child */ + foreach ($metrics as $metric) { + $symbol_tab[$child][$metric] += $info[$metric]; + } + } + } + + return $symbol_tab; +} + + +/* + * Prunes XHProf raw data: + * + * Any node whose inclusive walltime accounts for less than $prune_percent + * of total walltime is pruned. [It is possible that a child function isn't + * pruned, but one or more of its parents get pruned. In such cases, when + * viewing the child function's hierarchical information, the cost due to + * the pruned parent(s) will be attributed to a special function/symbol + * "__pruned__()".] + * + * @param array $raw_data XHProf raw data to be pruned & validated. + * @param double $prune_percent Any edges that account for less than + * $prune_percent of time will be pruned + * from the raw data. + * + * @return array Returns the pruned raw data. + * + * @author Kannan + */ +function xhprof_prune_run($raw_data, $prune_percent) { + + $main_info = $raw_data["main()"]; + if (empty($main_info)) { + xhprof_error("XHProf: main() missing in raw data"); + return false; + } + + // raw data should contain either wall time or samples information... + if (isset($main_info["wt"])) { + $prune_metric = "wt"; + } else if (isset($main_info["samples"])) { + $prune_metric = "samples"; + } else { + xhprof_error("XHProf: for main() we must have either wt " + ."or samples attribute set"); + return false; + } + + // determine the metrics present in the raw data.. + $metrics = array(); + foreach ($main_info as $metric => $val) { + if (isset($val)) { + $metrics[] = $metric; + } + } + + $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0); + + init_metrics($raw_data, null, null, false); + $flat_info = xhprof_compute_inclusive_times($raw_data); + + foreach ($raw_data as $parent_child => $info) { + + list($parent, $child) = xhprof_parse_parent_child($parent_child); + + // is this child's overall total from all parents less than threshold? + if ($flat_info[$child][$prune_metric] < $prune_threshold) { + unset($raw_data[$parent_child]); // prune the edge + } else if ($parent && + ($parent != "__pruned__()") && + ($flat_info[$parent][$prune_metric] < $prune_threshold)) { + + // Parent's overall inclusive metric is less than a threshold. + // All edges to the parent node will get nuked, and this child will + // be a dangling child. + // So instead change its parent to be a special function __pruned__(). + $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child); + + if (isset($raw_data[$pruned_edge])) { + foreach ($metrics as $metric) { + $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric]; + } + } else { + $raw_data[$pruned_edge] = $raw_data[$parent_child]; + } + + unset($raw_data[$parent_child]); // prune the edge + } + } + + return $raw_data; +} + + +/** + * Set one key in an array and return the array + * + * @author Kannan + */ +function xhprof_array_set($arr, $k, $v) { + $arr[$k] = $v; + return $arr; +} + +/** + * Removes/unsets one key in an array and return the array + * + * @author Kannan + */ +function xhprof_array_unset($arr, $k) { + unset($arr[$k]); + return $arr; +} + +/** + * Type definitions for URL params + */ +define('XHPROF_STRING_PARAM', 1); +define('XHPROF_UINT_PARAM', 2); +define('XHPROF_FLOAT_PARAM', 3); +define('XHPROF_BOOL_PARAM', 4); + + +/** + * Internal helper function used by various + * xhprof_get_param* flavors for various + * types of parameters. + * + * @param string name of the URL query string param + * + * @author Kannan + */ +function xhprof_get_param_helper($param) { + $val = null; + if (isset($_GET[$param])) + $val = $_GET[$param]; + else if (isset($_POST[$param])) { + $val = $_POST[$param]; + } + return $val; +} + +/** + * Extracts value for string param $param from query + * string. If param is not specified, return the + * $default value. + * + * @author Kannan + */ +function xhprof_get_string_param($param, $default = '') { + $val = xhprof_get_param_helper($param); + + if ($val === null) + return $default; + + return $val; +} + +/** + * Extracts value for unsigned integer param $param from + * query string. If param is not specified, return the + * $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_uint_param($param, $default = 0) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + // if it only contains digits, then ok.. + if (ctype_digit($val)) { + return $val; + } + + xhprof_error("$param is $val. It must be an unsigned integer."); + return null; +} + + +/** + * Extracts value for a float param $param from + * query string. If param is not specified, return + * the $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_float_param($param, $default = 0) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + // TBD: confirm the value is indeed a float. + if (true) // for now.. + return (float)$val; + + xhprof_error("$param is $val. It must be a float."); + return null; +} + +/** + * Extracts value for a boolean param $param from + * query string. If param is not specified, return + * the $default value. + * + * If value is not a valid unsigned integer, logs error + * and returns null. + * + * @author Kannan + */ +function xhprof_get_bool_param($param, $default = false) { + $val = xhprof_get_param_helper($param); + + if ($val === null) + $val = $default; + + // trim leading/trailing whitespace + $val = trim($val); + + switch (strtolower($val)) { + case '0': + case '1': + $val = (bool)$val; + break; + case 'true': + case 'on': + case 'yes': + $val = true; + break; + case 'false': + case 'off': + case 'no': + $val = false; + break; + default: + xhprof_error("$param is $val. It must be a valid boolean string."); + return null; + } + + return $val; + +} + +/** + * Initialize params from URL query string. The function + * creates globals variables for each of the params + * and if the URL query string doesn't specify a particular + * param initializes them with the corresponding default + * value specified in the input. + * + * @params array $params An array whose keys are the names + * of URL params who value needs to + * be retrieved from the URL query + * string. PHP globals are created + * with these names. The value is + * itself an array with 2-elems (the + * param type, and its default value). + * If a param is not specified in the + * query string the default value is + * used. + * @author Kannan + */ +function xhprof_param_init($params) { + /* Create variables specified in $params keys, init defaults */ + foreach ($params as $k => $v) { + switch ($v[0]) { + case XHPROF_STRING_PARAM: + $p = xhprof_get_string_param($k, $v[1]); + break; + case XHPROF_UINT_PARAM: + $p = xhprof_get_uint_param($k, $v[1]); + break; + case XHPROF_FLOAT_PARAM: + $p = xhprof_get_float_param($k, $v[1]); + break; + case XHPROF_BOOL_PARAM: + $p = xhprof_get_bool_param($k, $v[1]); + break; + default: + xhprof_error("Invalid param type passed to xhprof_param_init: " + . $v[0]); + exit(); + } + + if ($k === 'run') { + $p = implode(',', array_filter(explode(',', $p), 'ctype_xdigit')); + } + + // create a global variable using the parameter name. + $GLOBALS[$k] = $p; + } +} + + +/** + * Given a partial query string $q return matching function names in + * specified XHProf run. This is used for the type ahead function + * selector. + * + * @author Kannan + */ +function xhprof_get_matching_functions($q, $xhprof_data) { + + $matches = array(); + + foreach ($xhprof_data as $parent_child => $info) { + list($parent, $child) = xhprof_parse_parent_child($parent_child); + if (stripos($parent, $q) !== false) { + $matches[$parent] = 1; + } + if (stripos($child, $q) !== false) { + $matches[$child] = 1; + } + } + + $res = array_keys($matches); + + // sort it so the answers are in some reliable order... + asort($res); + + return ($res); +} diff --git a/yaf/Hood/Debug/XHProf/utils/xhprof_runs.php b/yaf/Hood/Debug/XHProf/utils/xhprof_runs.php new file mode 100644 index 0000000..cde5ff5 --- /dev/null +++ b/yaf/Hood/Debug/XHProf/utils/xhprof_runs.php @@ -0,0 +1,165 @@ +<?php +// +// Copyright (c) 2009 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// +// This file defines the interface iXHProfRuns and also provides a default +// implementation of the interface (class XHProfRuns). +// + +/** + * iXHProfRuns interface for getting/saving a XHProf run. + * + * Clients can either use the default implementation, + * namely XHProfRuns_Default, of this interface or define + * their own implementation. + * + * @author Kannan + */ +interface iXHProfRuns { + + /** + * Returns XHProf data given a run id ($run) of a given + * type ($type). + * + * Also, a brief description of the run is returned via the + * $run_desc out parameter. + */ + public function get_run($run_id, $type, &$run_desc); + + /** + * Save XHProf data for a profiler run of specified type + * ($type). + * + * The caller may optionally pass in run_id (which they + * promise to be unique). If a run_id is not passed in, + * the implementation of this method must generated a + * unique run id for this saved XHProf run. + * + * Returns the run id for the saved XHProf run. + * + */ + public function save_run($xhprof_data, $type, $run_id = null); +} + + +/** + * XHProfRuns_Default is the default implementation of the + * iXHProfRuns interface for saving/fetching XHProf runs. + * + * It stores/retrieves runs to/from a filesystem directory + * specified by the "xhprof.output_dir" ini parameter. + * + * @author Kannan + */ +class XHProfRuns_Default implements iXHProfRuns { + + private $dir = ''; + private $suffix = 'xhprof'; + + private function gen_run_id($type) { + return uniqid(); + } + + private function file_name($run_id, $type) { + + $file = "$run_id.$type." . $this->suffix; + + if (!empty($this->dir)) { + $file = $this->dir . "/" . $file; + } + return $file; + } + + public function __construct($dir = null) { + + // if user hasn't passed a directory location, + // we use the xhprof.output_dir ini setting + // if specified, else we default to the directory + // in which the error_log file resides. + + if (empty($dir)) { + $dir = ini_get("xhprof.output_dir"); + if (empty($dir)) { + + // some default that at least works on unix... + $dir = "/tmp"; + + xhprof_error("Warning: Must specify directory location for XHProf runs. ". + "Trying {$dir} as default. You can either pass the " . + "directory location as an argument to the constructor ". + "for XHProfRuns_Default() or set xhprof.output_dir ". + "ini param."); + } + } + $this->dir = $dir; + } + + public function get_run($run_id, $type, &$run_desc) { + $file_name = $this->file_name($run_id, $type); + + if (!file_exists($file_name)) { + xhprof_error("Could not find file $file_name"); + $run_desc = "Invalid Run Id = $run_id"; + return null; + } + + $contents = file_get_contents($file_name); + $run_desc = "XHProf Run (Namespace=$type)"; + return unserialize($contents); + } + + public function save_run($xhprof_data, $type, $run_id = null) { + + // Use PHP serialize function to store the XHProf's + // raw profiler data. + $xhprof_data = serialize($xhprof_data); + + if ($run_id === null) { + $run_id = $this->gen_run_id($type); + } + + $file_name = $this->file_name($run_id, $type); + $file = fopen($file_name, 'w'); + + if ($file) { + fwrite($file, $xhprof_data); + fclose($file); + } else { + xhprof_error("Could not open $file_name\n"); + } + + // echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n"; + return $run_id; + } + + function list_runs() { + if (is_dir($this->dir)) { + echo "<hr/>Existing runs:\n<ul>\n"; + $files = glob("{$this->dir}/*.{$this->suffix}"); + usort($files, create_function('$a,$b', 'return filemtime($b) - filemtime($a);')); + foreach ($files as $file) { + list($run,$source) = explode('.', basename($file)); + echo '<li><a href="' . htmlentities($_SERVER['SCRIPT_NAME']) + . '?run=' . htmlentities($run) . '&source=' + . htmlentities($source) . '">' + . htmlentities(basename($file)) . "</a><small> " + . date("Y-m-d H:i:s", filemtime($file)) . "</small></li>\n"; + } + echo "</ul>\n"; + } + } +} diff --git a/yaf/Hood/Helper/Paging.php b/yaf/Hood/Helper/Paging.php new file mode 100644 index 0000000..cd4ab27 --- /dev/null +++ b/yaf/Hood/Helper/Paging.php @@ -0,0 +1,201 @@ +<?php +/** + * 分页基类 + * + * example: + * <pre> + * $paging = new Q_Paging(); + * $paging->setTotal(10)->setCursor(1); + * </pre> + * + * @name Q_Paging + * @version 2.0 (2009-5-11 下午05:47:31) + * @package Q.Paging + * @author peter.zyliu liuziyang@gmail.com + * @since 1.0 + */ +namespace Hood\Helper; +class Paging +{ + + /** + * 总分页数 + * + * @var Integer + */ + private $total; + + /** + * 每页记录数 + * + * @var Integer + */ + private $size; + + /** + * 当前页 + * @var Integer + */ + protected $currentPage = 1; + + /** + * 光标 + * + * @var Integer + */ + private $cursor = 0; + + /** + * 当前页 + * @return Integer + */ + public function getCurrent() + { + return $this->currentPage; + } + + /** + * 设置当前页 + * @param $pageNo + * @return $this + */ + public function setCurrent($pageNo) + { + $cur = (int)intval($pageNo); + if ($cur <= 0) { + $cur = 1; + } + $this->currentPage = $cur; + return $this; + } + + /** + * 下一页 + * @return int + */ + public function getNext() + { + $pageNum = $this->getPageNum(); + $current = $this->getCurrent(); + return $current < $pageNum ? ($current + 1) : $pageNum; + } + + /** + * 上一页 + * @return int + */ + public function getPrev() + { + $current = $this->getCurrent(); + return $current > 1 ? ($current - 1) : 1; + } + + /** + * 取得记录开始的偏移量 + * @return int + */ + public function getOffset() + { + $offset = $this->getSize() * abs($this->getCurrent() - 1); + if ($offset >= $this->getTotal()) { + $offset = 0; + if ($this->getTotal() > 0 && $this->getTotal() > $this->getSize()) { + $offset = $this->getSize() * abs($this->getPageNum() - 1); + } + } + return (int)abs($offset); + } + + + /** + * 设置总记录数 + * @param $total + * @return $this + */ + public function setTotal($total) + { + $this->total = (int)intval($total); + return $this; + } + + /** + * 获取总数 + * @return int + */ + public function getTotal() + { + return (int)intval($this->total); + } + + /** + * 获取每页显示数 + * @return int + */ + public function getSize() + { + return (int)intval($this->size); + } + + /** + * 设置每页记录数 + * @param $size + * @return $this + */ + public function setSize($size) + { + if ($size > 0) { + $this->size = (int)intval($size); + } + return $this; + } + + /** + * 获取起始数 + * @return int + */ + public function getStarting() + { + return $this->getOffset(); + } + + /** + * 获取终点数 + * @return int + */ + public function getEnding() + { + return $this->getOffset() + $this->getSize(); + } + + /** + * 光标 + * @return int + */ + public function getCursor() + { + return $this->cursor; + } + + /** + * 设置光标 + * @param $cursor + * @return $this + */ + public function setCursor($cursor) + { + $this->cursor = intval($cursor); + return $this; + } + + /** + * 取得总分页数 + * @return float|int + */ + public function getPageNum() + { + if ($this->getSize() == 0) { + return 0; + } + return ceil($this->getTotal() / $this->getSize()); + } +} \ No newline at end of file diff --git a/yaf/Hood/Helper/Validation/File.php b/yaf/Hood/Helper/Validation/File.php new file mode 100644 index 0000000..891a4db --- /dev/null +++ b/yaf/Hood/Helper/Validation/File.php @@ -0,0 +1,14 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/20 + * Time: 下午2:36 + */ + +namespace Hood\Helper\Validation; + + +class File { + +} \ No newline at end of file diff --git a/yaf/Hood/Helper/Validation/Language.php b/yaf/Hood/Helper/Validation/Language.php new file mode 100644 index 0000000..d71b296 --- /dev/null +++ b/yaf/Hood/Helper/Validation/Language.php @@ -0,0 +1,59 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/19 + * Time: 下午11:11 + */ + +namespace Hood\Helper\Validation; + + +class Language +{ + static public $messages = [ + "after" => "{attribute} 必须是一个在 {date} 之后的日期.", + "alpha" => "{attribute} 只能由字母组成.", + "alpha_dash" => "{attribute} 只能由字母、数字和斜杠组成.", + "alpha_num" => "{attribute} 只能由字母和数字组成.", + "array" => "{attribute} 必须是一个数组.", + "between" => [ + "numeric" => "{attribute} 必须介于 {min} - {max} 之间.", + "string" => "{attribute} 必须介于 {min} - {max} 个字符之间.", + "array" => "{attribute} 必须只有 {min} - {max} 个单元.", + ], + "boolean" => "{attribute} 必须为布尔值.", + "confirmed" => "{attribute} 两次输入不一致.", + "date" => "{attribute} 不是一个有效的日期.", + "digits" => "{attribute} 必须是 {digits} 位的数字.", + "digits_between" => "{attribute} 必须是介于 {min} 和 {max} 位的数字.", + "email" => "{attribute} 不是一个合法的邮箱.", + "in" => "已选的属性 {attribute} 非法,必须在 {data} 范围之内.", + "not_in" => "已选的属性 {attribute} 非法,不能在 {data} 范围之内.", + "integer" => " {attribute} 必须是整数.", + 'string' => '{attribute} 必须是字符.', + "ip" => "{attribute} 必须是有效的 IP 地址.", + "max" => [ + "numeric" => "{attribute} 不能大于 {max}.", + "string" => "{attribute} 不能大于 {max} 个字符.", + "array" => "{attribute} 最多只有 {max} 个单元.", + ], + "min" => [ + "numeric" => "{attribute} 必须大于等于 {min}.", + "string" => "{attribute} 至少为 {min} 个字符.", + "array" => "{attribute} 至少有 {min} 个单元.", + ], + "numeric" => "{attribute} 必须是一个数字.", + "regex" => "{attribute} 格式不正确.", + "required" => "{attribute} 不能为空.", + "same" => "{attribute} 和 {other} 必须相同.", + "size" => [ + "numeric" => "{attribute} 大小必须为 {size}.", + "string" => "{attribute} 必须是 {size} 个字符.", + "array" => "{attribute} 必须为 {size} 个单元.", + ], + "active_url" => "{attribute} 不是一个有效的网址.", + "url" => "{attribute} 格式不正确.", + 'mobile' => "手机号格式不正确." + ]; +} \ No newline at end of file diff --git a/yaf/Hood/Helper/Validation/Messages.php b/yaf/Hood/Helper/Validation/Messages.php new file mode 100644 index 0000000..460e0d7 --- /dev/null +++ b/yaf/Hood/Helper/Validation/Messages.php @@ -0,0 +1,145 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/19 + * Time: 下午7:29 + */ + +namespace Hood\Helper\Validation; + + +class Messages +{ + private $messages = array(); + + private $_customMessages = array(); + + private $defaultMessagesLanguage = array(); + + public function __construct(array $customMessages = array()) + { + $this->defaultMessagesLanguage = Language::$messages; + $this->customMessages($customMessages); + } + + /** + * TODO 自定义数据 + * xxx.required=>'xxx' + * xxx.min=>'xxx' + * + * 设置自定义消息 + * @param array $customMessages + */ + public function customMessages(array $customMessages = array()) + { + $_customMessages = array(); + foreach ($customMessages as $key => $custom) { + if (is_string($custom)) { + $_customMessages[$key] = $custom; + } elseif (is_array($custom)) { + foreach ($custom as $_key => $_val) { + $_customMessages[$key . '.' . $_key] = $_val; + } + } + } + unset($customMessages); + $this->_customMessages = $_customMessages; + } + + /** + * 设置一条自定义消息 + * @param $key + * @param $message + * @return $this + */ + public function add($key, $message) + { + $this->_customMessages[$key] = $message; + return $this; + } + + /** + * 获取所有错误 + * @return array + */ + public function all() + { + $all = array(); + foreach ($this->messages as $key => $messages) { + $all = array_merge($all, $messages); + } + return $all; + } + + /** + * 存储消息 + * @param $key + * @param $val + */ + public function setMessages($key, $val) + { + $this->messages[$key][] = $val; + } + + /** + * 查看所有字段的所有错误信息 + * @param $key + * @return array + */ + public function get($key) + { + if (isset($this->messages[$key])) { + return $this->messages[$key]; + } + return array(); + } + + /** + * 判断是否有错误信息 + * @return bool + */ + public function has($key = null) + { + if ($key == null) { + return count($this->messages) > 0; + } + return isset($this->messages[$key]); + } + + /** + * 第一个错误 + * @param $key + * @return array + */ + public function first($key = null) + { + if (is_null($key) && count($this->messages) > 0) { + $messages = array_slice($this->messages, 0, 1); + return $messages[key($messages)][0]; + } + $messages = $this->get($key); + return (count($messages) > 0) ? $messages[0] : ''; + } + + /** + * 获取消息 + * @param $key + * @param null $type + * @return string + */ + public function getMessages($attribute, $rule, $type = null) + { + if (isset($this->_customMessages[$attribute . '.' . $rule])) { + return $this->_customMessages[$attribute . '.' . $rule]; + } elseif (isset($this->_customMessages[$attribute])) { + return $this->_customMessages[$attribute]; + } elseif (array_key_exists($rule, $this->defaultMessagesLanguage)) { + if (!empty($type) && isset($this->defaultMessagesLanguage[$rule][$type])) { + return $this->defaultMessagesLanguage[$rule][$type]; + } + return $this->defaultMessagesLanguage[$rule]; + } + return ''; + } +} \ No newline at end of file diff --git a/yaf/Hood/Helper/Validation/README.md b/yaf/Hood/Helper/Validation/README.md new file mode 100644 index 0000000..675e72b --- /dev/null +++ b/yaf/Hood/Helper/Validation/README.md @@ -0,0 +1,383 @@ +#Validator +```php +$validator = Validator::make( + ['url_str' => 'http://www.baidu.com'], + ['url_str' => 'url'] +); + +判断错误 +$validator->fails(); + +判断正确 +$validator->passes(); + +获取所有验证的第一个错误,可以指定验证字段的第一个错误 +$validator->messages()->first([$key=null]); + +获取所有验证的错误 +$validator->messages()->all(); + +获取指定验证字段的所有错误 +$validator->messages()->get($key); + +判断是否有错误信息,可以指定验证字段 +$validator->messages()->has([$key]); + +设定自定义错误信息 +$validator->messages()->customMessages(['key'=>'mes']); + +设定指定验证字段错误消息 +$validator->messages()->add($key,$mes); + +TODO 待加入 +唯一(Unique)规则的基本用法,指定一个自定义的字段名称 +( + 唯一:数据库.表,字段,指定ID + unique:dbname.table,column,except + 唯一:表,字段,指定ID + unique:table,column,except + 唯一:表,字段 + unique:table,column + 唯一:表(使用验证字段作为数据库验证字段) + unique:table +) +'email' => 'unique:users,email_address' + +``` + +##active_url +```php + Validator::make( + ['url'=>'active_url'] + ) + + Validator::make( + ['url_str' => 'http://www.baidu.com'], + ['url_str' => 'url'] + ); + 字段值通过 PHP 函数 checkdnsrr 来验证是否为一个有效的网址。 +``` +##url +```php + Validator::make( + ['url'=>'active_url'] + ) + + Validator::make( + ['url_str' => 'http://www.baidu.com'], + ['url_str' => 'url'] + ); + 网址格式不正确。 +``` +##after +```php + Validator::make( + ['time'=>'after:date'] + ) + + Validator::make( + ['url_str' => '2015-04-01'], + ['url_str' => 'after:date'] + ); + 验证字段是否是在指定日期之后。这个日期将会使用 PHP strtotime 函数验证。 +``` +##alpha +```php + Validator::make( + ['name'=>'alpha'] + ) + + Validator::make( + ['url_str' => 'asd'], + ['url_str' => 'alpha'] + ); + 字段仅全数为字母字串时通过验证。 +``` +##alpha_dash +```php + Validator::make( + ['name'=>'alpha_dash'] + ) + Validator::make( + ['url_str' => '..'], + ['url_str' => 'alpha_dash'] + ); + 字段值仅允许字母、数字、破折号(-)以及底线(_) +``` +##alpha_num +```php + Validator::make( + ['name'=>'alpha_num'] + ) + Validator::make( + ['url_str' => '120l'], + ['url_str' => 'alpha_num'] + ); + 字段值仅允许字母、数字 +``` +##array +```php + Validator::make( + ['list'=>'array'] + ) + Validator::make( + ['url_str' => array()], + ['url_str' => 'array'] + ); + 字段值仅允许为数组 +``` +##between:min,max +```php + Validator::make( + ['number'=>'between:min,max'] + ) + + Validator::make( + ['url_str' => 170000], + ['url_str' => 'between:1,5'] + ); + 字段值需介于指定的 min 和 max 值之间。字串、数值或是文件都是用同样的方式来进行验证。 +``` +##confirmed +```php + Validator::make( + ['password_confirmation'=>'confirmed:password'] + ) + + Validator::make( + [ + 'password' => 170000, + 'password_confirmation' => 1700001 + ], + ['password_confirmation' => 'confirmed:password'] + ); + 字段值需与对应的字段值 foo_confirmation 相同。例如,如果验证的字段是 password ,那对应的字段 password_confirmation 就必须存在且与 password 字段相符。 +``` +##date +```php + Validator::make( + ['time'=>'date'] + ) + Validator::make( + [ + 'date_time' => '2015-05' + ], + ['date_time' => 'date'] + ); + 字段值通过 PHP strtotime 函数验证是否为一个合法的日期 +``` +##digits:value +```php + Validator::make( + ['time'=>'digits:value'] + ) + Validator::make( + [ + 'date_time' => 20 + ], + ['date_time' => 'digits:2'] + ); + 字段值需为数字且长度需为 value +``` +##digits_between:min,max +```php + Validator::make( + ['number'=>'digits_between:min,max'] + ) + Validator::make( + [ + 'date_time' => 200 + ], + ['date_time' => 'digits_between:2,3'] + ); + 字段值需为数字,且长度需介于 min 与 max 之间 +``` +##boolean +```php + Validator::make( + ['is_open'=>'boolean'] + ) + Validator::make( + [ + 'date_time' => false + ], + ['date_time' => 'boolean'] + ); + 字段必须可以转换成布尔值,可接受的值为 true, false, 1, 0, "1", "0" +``` +##email +```php + Validator::make( + ['email'=>'email'] + ) + Validator::make( + [ + 'date_time' => 'peter@gmail.com' + ], + ['date_time' => 'email'] + ); + 字段值需符合 email 格式 +``` +##in +```php + Validator::make( + ['code'=>'in:1,2,3,4,5'] + ) + Validator::make( + [ + 'date_time' => 1 + ], + ['date_time' => 'in:1,2,3,4,5'] + ); + 字段值需符合事先给予的清单的其中一个值 +``` +##not_in +```php + Validator::make( + ['code'=>'in:1,2,3,4,5'] + ) + Validator::make( + [ + 'date_time' => 10 + ], + ['date_time' => 'not_in:1,2,3,4,5'] + ); + 字段值不得为给定清单中其一 +``` +##integer +```php + Validator::make( + ['number'=>'integer'] + ) + + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'integer'] + ); + 字段值需为一个整数值 +``` +##ip +```php + Validator::make( + ['ip'=>'ip'] + ) + + Validator::make( + [ + 'date_time' => '127.0.0.1' + ], + ['date_time' => 'ip'] + ); + 字段值需符合 IP 位址格式 +``` +##max +```php + Validator::make( + ['number'=>'max:value'] + ) + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'max:1'] + ); + 字段值需小于等于 value。字串、数字和文件则是判断 size 大小 +``` +##min +```php + Validator::make( + ['number'=>'min:value'] + ) + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'min:1'] + ); + 字段值需大于等于 value。字串、数字和文件则是判断 size 大小 +``` +##numeric +```php + Validator::make( + ['number'=>'numeric'] + ) + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'numeric'] + ); + 字段值需为数字 +``` +##regex:pattern +```php + Validator::make( + ['number'=>'regex:pattern'] + ) + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'regex:[\w]'] + ); + 字段值需符合给定的正规表示式。 +``` +##required +```php + Validator::make( + ['number'=>'required'] + ) + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'required'] + ); + 字段值为必填 +``` +##same:field +```php + Validator::make( + ['number'=>'same:field'] + ) + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'same:1'] + ); + 字段值需与指定字段 field 等值 +``` +##size:value +```php + Validator::make( + ['number'=>'size:value'] + ) + Validator::make( + [ + 'date_time' => '1' + ], + ['date_time' => 'size:1'] + ); + 字段值的尺寸需符合给定 value 值。对于字串来说,value 为需符合的字串长度。对于数字来说,value 为需符合的整数值 +``` +##code +```php + 判断是否有错误 + if ($validator->fails() == true) { + + } + 判断是否通过 + if ($validator->passes() == true) { + + } + + 获取所有错误 + foreach ($validator->messages()->all() as $key => $val) { + + } + 获取指定错误 + $validator->messages()->get('email'); +``` \ No newline at end of file diff --git a/yaf/Hood/Helper/Validation/Validator.php b/yaf/Hood/Helper/Validation/Validator.php new file mode 100644 index 0000000..d05373e --- /dev/null +++ b/yaf/Hood/Helper/Validation/Validator.php @@ -0,0 +1,603 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/19 + * Time: 下午5:58 + */ +namespace Hood\Helper\Validation; + +use Hood\Core\Support\Str; +use Hood\Core\Support\Arr; + +class Validator +{ + private $files = array(); + + private $data; + + protected $failedRules = array(); + + private $rules; + + protected $numericRules = array('Numeric', 'Integer'); + + protected $implicitRules = array( + 'Required' + ); + + protected $messagesRules = array( + 'alphadash' => 'alpha_dash', + 'alphanum' => 'alpha_num', + 'digitsbetween' => 'digits_between', + 'notin' => 'not_in', + 'activeurl' => 'active_url' + ); + + /** + * @var Messages + */ + private $messages; + + public function __construct(array $data, array $rules, array $customMessages = array()) + { + $this->messages = new Messages($customMessages); + $this->data = $this->parseData($data); + $this->rules = $this->explodeRules($rules); + } + + /** + * 自定义错误信息 + * @param array $customMessages + * @return $this + */ + public function customMessages(array $customMessages = array()) + { + $this->messages->customMessages($customMessages); + return $this; + } + + /** + * 解析数据 + * @param array $data + */ + protected function parseData(array $data) + { + $this->files = array(); + foreach ($data as $key => $value) { + if ($value instanceof File) { + $this->files[$key] = $value; + unset($data[$key]); + } + } + return $data; + } + + /** + * 拆分规则 + * @param $rules + * @return mixed + */ + protected function explodeRules($rules) + { + $_rules = array(); + foreach ($rules as $key => &$rule) { + $rule = (is_string($rule)) ? explode('|', $rule) : $rule; + $_ruleKey = (is_string($key)) ? explode('|', $key) : array($key); + foreach ($_ruleKey as $k) { + $_rules[$k] = $rule; + } + } + unset($rules); + return $_rules; + } + + /** + * 通过 + * @return bool + */ + public function passes() + { + foreach ($this->rules as $attribute => $rules) { + foreach ($rules as $rule) { + $this->validate($attribute, $rule); + } + } + return count($this->messages->all()) === 0; + } + + protected function doReplacements($message, $attribute, $rule, $parameters) + { + if (method_exists($this, $replacer = "replace{$rule}")) { + return $this->$replacer($message, $attribute, $rule, $parameters); + } + return ''; + } + + protected function validate($attribute, $rule) + { + list($rule, $parameters) = $this->parseRule($rule); + if ($rule == '') return; + $value = $this->getValue($attribute); + $validatable = $this->isValidatable($rule, $attribute, $value); + $method = "validate{$rule}"; + if ($validatable && !$this->$method($attribute, $value, $parameters, $this)) { + $this->addError($attribute, $rule, $parameters, $value); + $this->failedRules[$attribute][$rule] = $parameters; + } + } + + protected function addError($attribute, $rule, $parameters, $value) + { + $_rule = strtolower($rule); + if (isset($this->messagesRules[$_rule])) { + $_rule = $this->messagesRules[$_rule]; + } + $message = $this->messages->getMessages($attribute, $_rule, $this->getValueType($value)); + $message = $this->doReplacements($message, $attribute, $rule, $parameters); + $this->messages->setMessages((strtolower($attribute) . '.' . strtolower($rule)), $message); + } + + protected function getValueType($value) + { + if (is_numeric($value)) { + return 'numeric'; + } elseif (is_array($value)) { + return 'array'; + } elseif (is_string($value)) { + return 'string'; + } + return null; + } + + protected function getAttributeType($attribute) + { + if ($this->hasRule($attribute, $this->numericRules)) { + return 'numeric'; + } elseif ($this->hasRule($attribute, array('Array'))) { + return 'array'; + } + return 'string'; + } + + protected function getAttribute($attribute) + { + return str_replace('_', ' ', Str::snake($attribute)); + } + + protected function getSize($attribute, $value) + { + $hasNumeric = $this->hasRule($attribute, $this->numericRules); + if (is_numeric($value) && $hasNumeric) { + return $this->data[$attribute]; + } elseif (is_array($value)) { + return count($value); + } + return $this->getStringSize($value); + } + + protected function getStringSize($value) + { + if (function_exists('mb_strlen')) return mb_strlen($value); + return strlen($value); + } + + protected function requireParameterCount($count, $parameters, $rule) + { + if (count($parameters) < $count) { + throw new \Exception("Validation rule $rule requires at least $count parameters."); + } + } + + protected function isValidatable($rule, $attribute, $value) + { + return $this->presentOrRuleIsImplicit($rule, $attribute, $value) && $this->passesOptionalCheck($attribute); + } + + protected function hasRule($attribute, $rules) + { + return !is_null($this->getRule($attribute, $rules)); + } + + protected function passesOptionalCheck($attribute) + { + if ($this->hasRule($attribute, array('Sometimes'))) { + return array_key_exists($attribute, $this->arrayDot($this->data)) + || array_key_exists($attribute, $this->files); + } + return true; + } + + protected function arrayDot($array, $prepend = '') + { + $results = array(); + foreach ($array as $key => $value) { + if (is_array($value)) { + $results = array_merge($results, $this->arrayDot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + return $results; + } + + protected function getRule($attribute, $rules) + { + if (!array_key_exists($attribute, $this->rules)) { + return; + } + $rules = (array)$rules; + foreach ($this->rules[$attribute] as $rule) { + list($rule, $parameters) = $this->parseRule($rule); + if (in_array($rule, $rules)) return [$rule, $parameters]; + } + } + + protected function presentOrRuleIsImplicit($rule, $attribute, $value) + { + return $this->validateRequired($attribute, $value) || $this->isImplicit($rule); + } + + protected function isImplicit($rule) + { + return in_array($rule, $this->implicitRules); + } + + protected function getValue($attribute) + { + if (isset($this->data[$attribute])) { + return $this->data[$attribute]; + } elseif (isset($this->files[$attribute])) { + return $this->files[$attribute]; + } + } + + protected function parseRule($rules) + { + if (is_array($rules)) { + return $this->parseArrayRule($rules); + } + return $this->parseStringRule($rules); + } + + protected function parseArrayRule(array $rules) + { + return array(Str::studly(trim(array_get($rules, 0))), array_slice($rules, 1)); + } + + protected function parseStringRule($rules) + { + $parameters = []; + if (strpos($rules, ':') !== false) { + list($rules, $parameter) = explode(':', $rules, 2); + $parameters = $this->parseParameters($rules, $parameter); + } + return array(Str::studly(trim($rules)), $parameters); + } + + protected function parseParameters($rule, $parameter) + { + if (strtolower($rule) == 'regex') return array($parameter); + return str_getcsv($parameter); + } + + public function fails() + { + return !$this->passes(); + } + + protected function replaceString($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}'), $attribute, $message); + } + + protected function validateString($attribute, $value) + { + return is_string($value); + } + + protected function replaceRequired($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateRequired($attribute, $value) + { + if (is_null($value)) { + return false; + } elseif (is_string($value) && trim($value) === '') { + return false; + } elseif (is_array($value) && count($value) < 1) { + return false; + } + return true; + } + + + protected function replaceActiveUrl($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateActiveUrl($attribute, $value) + { + $url = str_replace(array('http://', 'https://', 'ftp://'), '', strtolower($value)); + return checkdnsrr($url); + } + + protected function validateUrl($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_URL) !== false; + } + + protected function replaceUrl($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateAfter($attribute, $value) + { + return strtotime($value) ? true : false; + } + + protected function replaceAfter($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{date}'), array($attribute, $parameters[0]), $message); + } + + protected function validateAlpha($attribute, $value) + { + return preg_match('/^[\pL\pM]+$/u', $value); + } + + protected function replaceAlpha($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateAlphaDash($attribute, $value) + { + return preg_match('/^[\pL\pM\pN_-]+$/u', $value); + } + + protected function replaceAlphaDash($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateAlphaNum($attribute, $value) + { + return preg_match('/^[\pL\pM\pN]+$/u', $value); + } + + protected function replaceAlphaNum($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateArray($attribute, $value) + { + return is_array($value); + } + + protected function replaceArray($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateBetween($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'between'); + $size = $this->getSize($attribute, $value); + return $size >= $parameters[0] && $size <= $parameters[1]; + } + + protected function replaceBetween($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{min}', '{max}'), array_merge((array)$attribute, $parameters), $message); + } + + protected function validateConfirmed($attribute, $value, $parameters) + { + return $this->validateSame($attribute, $value, $parameters); + } + + protected function replaceConfirmed($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}'), $attribute, $message); + } + + protected function validateSame($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'same'); + $other = Arr::get($this->data, $parameters[0]); + return (isset($other) && $value == $other); + } + + protected function replaceSame($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{other}'), array($attribute, $this->getAttribute($parameters[0])), $message); + } + + protected function validateDate($attribute, $value) + { + if ($value instanceof DateTime) return true; + if (strtotime($value) === false) return false; + $date = date_parse($value); + return checkdate($date['month'], $date['day'], $date['year']); + } + + protected function replaceDate($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateDigits($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'digits'); + return $this->validateNumeric($attribute, $value) && strlen((string)$value) == $parameters[0]; + } + + protected function replaceDigits($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{digits}'), array($attribute, $parameters[0]), $message); + } + + protected function validateDigitsBetween($attribute, $value, $parameters) + { + $this->requireParameterCount(2, $parameters, 'digits_between'); + $length = strlen((string)$value); + return $length >= $parameters[0] && $length <= $parameters[1]; + } + + protected function replaceDigitsBetween($message, $attribute, $rule, $parameters) + { + return $this->replaceBetween($message, $attribute, $rule, $parameters); + } + + protected function validateBoolean($attribute, $value) + { + $acceptable = array(true, false, 0, 1, '0', '1'); + return in_array($value, $acceptable, true); + } + + protected function replaceBoolean($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateEmail($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; + } + + protected function replaceEmail($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateIn($attribute, $value, $parameters) + { + return in_array((string)$value, $parameters); + } + + protected function replaceIn($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{data}'), array($attribute, implode(',', $parameters)), $message); + } + + protected function validateNotIn($attribute, $value, $parameters) + { + return !$this->validateIn($attribute, $value, $parameters); + } + + protected function replaceNotIn($message, $attribute, $rule, $parameters) + { + return $this->replaceIn($message, $attribute, $rule, $parameters); + } + + protected function validateInteger($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_INT) !== false; + } + + protected function replaceInteger($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateIp($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_IP) !== false; + } + + protected function replaceIp($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function replaceMax($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{max}'), array($attribute, $parameters[0]), $message); + } + + protected function validateMax($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'max'); + return $this->getSize($attribute, $value) <= $parameters[0]; + } + + protected function validateMin($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'min'); + return $this->getSize($attribute, $value) >= $parameters[0]; + } + + protected function replaceMin($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{min}'), array($attribute, $parameters[0]), $message); + } + + protected function validateNumeric($attribute, $value) + { + return is_numeric($value); + } + + protected function replaceNumeric($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateRegex($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'regex'); + return preg_match($parameters[0], $value); + } + + protected function replaceRegex($message, $attribute, $rule, $parameters) + { + return str_replace('{attribute}', $attribute, $message); + } + + protected function validateSize($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'size'); + return $this->getSize($attribute, $value) == $parameters[0]; + } + + protected function replaceSize($message, $attribute, $rule, $parameters) + { + return str_replace(array('{attribute}', '{size}'), array($attribute, $parameters[0]), $message); + } + + /** + * 验证手机号 + * @param $attribute + * @param $value + * @param $parameters + * @return bool + * @throws \Exception + */ + protected function validateMobile($attribute, $value, $parameters) + { + if (!preg_match('/^1[3|4|5|8|7][0-9]\\d{8}$/si', $value)) { + return false; + } + return true; + } + + protected function replaceMobile($message, $attribute, $rule, $parameters) + { + return $message; + } + + public function messages() + { + return $this->messages; + } + + public function errors() + { + return $this->messages; + } +} \ No newline at end of file diff --git a/yaf/Hood/Helper/View/Link.php b/yaf/Hood/Helper/View/Link.php new file mode 100644 index 0000000..513f217 --- /dev/null +++ b/yaf/Hood/Helper/View/Link.php @@ -0,0 +1,76 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/16 + * Time: 下午12:58 + */ + +namespace Hood\Helper\View; + + +class Link extends ViewAbstract +{ + protected $_mediaTypes = array( + 'charset', 'href', 'hreflang', 'id', 'media', 'rel', 'rev', 'type', 'title', 'extras' + ); + + protected $_mediaList = array( + 'rel="stylesheet"', + 'type="text/css"', + 'media="screen"' + ); + + public function offsetSetFile($index, $src, $conditional = '') + { + $this->_item[$index] = $src; + if (!empty($conditional)) { + $this->_target_conditional[$index] = $conditional; + } + return $this; + } + + public function appendFile($src) + { + $this->_item[] = $src; + } + + public function setMedia(array $media) + { + foreach ($media as $_media => $val) { + if (isset($this->_mediaTypes[$_media]) && !empty($val) && is_string($val)) { + $this->_mediaList[] = sprintf(' %s="%s"', $_media, htmlspecialchars($val, ENT_COMPAT, $this->enc)); + } + } + return $this; + } + + public function conditional($conditional = 'lt IE 9') + { + assert(is_string($conditional)); + $this->conditional = $conditional; + return $this; + } + + public function __toString() + { + $html = ''; + $_mediaString = implode(' ', $this->_mediaList); + ksort($this->_item); + foreach ($this->_item as $key => $val) { + if (empty($val)) { + continue; + } + $linkString = '<link href="' . $val . '" ' . $_mediaString . ' />' . PHP_EOL; + if (isset($this->_target_conditional[$key])) { + $html .= '<!--[if ' . $this->_target_conditional[$key] . ']> ' . PHP_EOL . $linkString . '<![endif]-->' . PHP_EOL; + } else { + $html .= $linkString; + } + } + if (!empty($this->conditional) && empty($this->_target_conditional)) { + $html = '<!--[if ' . $this->conditional . ']>' . PHP_EOL . $html . ' <![endif]-->' . PHP_EOL; + } + return $html; + } +} \ No newline at end of file diff --git a/yaf/Hood/Helper/View/Meta.php b/yaf/Hood/Helper/View/Meta.php new file mode 100644 index 0000000..c293c9e --- /dev/null +++ b/yaf/Hood/Helper/View/Meta.php @@ -0,0 +1,66 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/16 + * Time: 下午12:59 + */ + +namespace Hood\Helper\View; + + +class Meta extends ViewAbstract +{ + private $_requiredKeys = array(); + + protected $_modifierKey = array('content', 'name', 'http-equiv', 'charset', 'property', 'lang', 'scheme'); + + public function appendMeta($content, $metaKey, $metaVal, $modifiers = array()) + { + assert(is_scalar($content)); + assert(is_scalar($metaKey)); + assert(is_scalar($metaVal)); + $this->_requiredKeys[] = array( + 'required' => array( + 'content' => $content, + 'type_name' => $metaKey, + 'type_val' => $metaVal + ), + 'modifiers' => $this->_checking($modifiers) + ); + return $this; + } + + private function _checking(array $modifiers) + { + $_modifiers = array(); + foreach ($modifiers as $key => $val) { + if (isset($this->_modifierKey[$key])) { + $_modifiers[] = array( + $key => $val + ); + } + } + return $_modifiers; + } + + public function __toString() + { + $html = ''; + foreach ($this->_requiredKeys as $_key => $_val) { + $modifiersString = ''; + foreach ($_val['modifiers'] as $m_key => $m_val) { + $modifiersString .= $m_key . '="' . $this->_escape($m_val) . '" '; + } + $tpl = '<meta %s="%s" content="%s" %s/>' . PHP_EOL; + $html .= sprintf( + $tpl, + $_val['required']['type_name'], + $this->_escape($_val['required']['type_val']), + $this->_escape($_val['required']['content']), + $modifiersString + ); + } + return $html; + } +} \ No newline at end of file diff --git a/yaf/Hood/Helper/View/Script.php b/yaf/Hood/Helper/View/Script.php new file mode 100644 index 0000000..bad992a --- /dev/null +++ b/yaf/Hood/Helper/View/Script.php @@ -0,0 +1,89 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/16 + * Time: 下午12:59 + */ + +namespace Hood\Helper\View; + + +class Script extends ViewAbstract +{ + protected $_attributes = array( + 'charset', 'defer', 'language', 'src' + ); + + protected $_attrList = array( + 'charset="UTF-8"', + 'type="text/javascript"', + ); + + protected $_fieldsFile = array(); + + public function offsetSetFile($index, $src, $conditional = '') + { + $this->_item[$index] = $src; + if (!empty($conditional)) { + $this->_target_conditional[$index] = $conditional; + } + return $this; + } + + /** + * 保留输出文件 + * @param array $fieldsIDS + * @return $this + */ + public function fieldsFile(array $fieldsIDS) + { + $this->_fieldsFile = $fieldsIDS; + return $this; + } + + public function appendFile($src) + { + $this->_item[] = $src; + } + + public function setAttr(array $attr) + { + foreach ($attr as $_attr => $val) { + if (isset($this->_attrList[$_attr]) && !empty($val) && is_string($val)) { + $this->_attrList[] = sprintf(' %s="%s"', $_attr, htmlspecialchars($val, ENT_COMPAT, $this->enc)); + } + } + return $this; + } + + public function conditional($conditional = 'lt IE 9') + { + assert(is_string($conditional)); + $this->conditional = $conditional; + return $this; + } + + public function __toString() + { + + $html = ''; + $_attrString = implode(' ', $this->_attrList); + ksort($this->_item); + foreach ($this->_item as $key => $val) { + if (empty($val)) { + continue; + } + $_script = '<script src="' . $val . '" ' . $_attrString . '></script>' . PHP_EOL; + if (isset($this->_target_conditional[$key])) { + $html .= '<!--[if ' . $this->_target_conditional[$key] . ']> ' . PHP_EOL . $_script . '<![endif]-->' . PHP_EOL; + } else { + $html .= $_script; + } + } + if (!empty($this->conditional) && empty($this->_target_conditional)) { + $html = '<!--[if ' . $this->conditional . ']> ' . PHP_EOL . $html . '<![endif]-->' . PHP_EOL; + } + return $html; + } +} \ No newline at end of file diff --git a/yaf/Hood/Helper/View/Title.php b/yaf/Hood/Helper/View/Title.php new file mode 100644 index 0000000..4f9cc5a --- /dev/null +++ b/yaf/Hood/Helper/View/Title.php @@ -0,0 +1,26 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/16 + * Time: 下午1:00 + */ + +namespace Hood\Helper\View; + + +class Title extends ViewAbstract +{ + private $_title = ''; + + public function headTitle($title) + { + $this->_title = $title; + return $this; + } + + public function __toString() + { + return '<title>' . $this->_escape($this->_title) . '</title>' . PHP_EOL; + } +} \ No newline at end of file diff --git a/yaf/Hood/Helper/View/ViewAbstract.php b/yaf/Hood/Helper/View/ViewAbstract.php new file mode 100644 index 0000000..f8dbf0a --- /dev/null +++ b/yaf/Hood/Helper/View/ViewAbstract.php @@ -0,0 +1,26 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/16 + * Time: 下午12:57 + */ + +namespace Hood\Helper\View; + + +class ViewAbstract +{ + protected $_item = array(); + + protected $enc = 'UTF-8'; + + protected $conditional = ''; + + protected $_target_conditional = array(); + + protected function _escape($string) + { + return htmlspecialchars((string)$string, ENT_COMPAT, $this->enc); + } +} \ No newline at end of file diff --git a/yaf/Hood/Ose/README.md b/yaf/Hood/Ose/README.md new file mode 100644 index 0000000..3d047a1 --- /dev/null +++ b/yaf/Hood/Ose/README.md @@ -0,0 +1 @@ +Object Service Engine \ No newline at end of file diff --git a/yaf/Hood/Paging.php b/yaf/Hood/Paging.php new file mode 100644 index 0000000..89c8b30 --- /dev/null +++ b/yaf/Hood/Paging.php @@ -0,0 +1,189 @@ +<?php +/** + * 分页基类 + * + * example: + * <pre> + * use Hood\Paging; + * $paging = new Paging(); + * $paging->setTotal(10)->setCursor(1); + * </pre> + * + * @name Paging + * @version 2.0 (2009-5-11 下午05:47:31) + * @author ZIP + * @since 1.0 + */ +namespace Hood; +class Paging { + + /** + * 总分页数 + * + * @var Integer + */ + private $total; + + /** + * 每页记录数 + * + * @var Integer + */ + private $size; + + /** + * 当前页 + * @var Integer + */ + protected $currentPage = 1; + + /** + * 光标 + * + * @var Integer + */ + private $cursor = 0; + + /** + * 当前页 + * @return Integer + */ + public function getCurrent() { + return $this->currentPage; + } + + /** + * 设置当前页 + * @param Integer $pageNo + * @return Paging + */ + public function setCurrent( $pageNo ) { + $cur = (int) intval($pageNo); + if ($cur <= 0) { + $cur = 1; + } + $this->currentPage = $cur; + return $this; + } + + /** + * 下一页 + * @return Integer + */ + public function getNext() { + $pageNum = $this->getPageNum(); + $current = $this->getCurrent(); + return $current < $pageNum ? ($current + 1) : $pageNum; + } + + /** + * 上一页 + * @return Integer + */ + public function getPrev() { + $current = $this->getCurrent(); + return $current > 1 ? ($current - 1) : 1; + } + + /** + * 取得记录开始的偏移量 + * @return Integer + */ + public function getOffset() { + $offset = $this->getSize() * abs($this->getCurrent() - 1); + if ($offset >= $this->getTotal()) { + $offset = 0; + if ($this->getTotal() > 0 && $this->getTotal() > $this->getSize()) { + $offset = $this->getSize() * abs($this->getPageNum() - 1); + } + } + return (int) abs($offset); + } + + /** + * 设置总记录数 + * + * @param Integer $total + * @return Paging + */ + public function setTotal( $total ) { + $this->total = (int) intval($total); + return $this; + } + /** + * 获取总数 + * @return Integer + */ + public function getTotal() { + return (int) intval($this->total); + } + + /** + * 获取每页显示数 + * @return Integer + */ + public function getSize() { + return (int) intval($this->size); + } + + /** + * 设置每页记录数 + * + * @param Integer $size + * @return Paging + */ + public function setSize( $size ) { + if ($size > 0) { + $this->size = (int) intval($size); + } + return $this; + } + + /** + * 获取起始数 + * @return Integer + **/ + public function getStarting() { + return $this->getOffset(); + } + + /** + * 获取终点数 + * @return Integer + **/ + public function getEnding() { + return $this->getOffset() + $this->getSize(); + } + + /** + * 光标 + * + * @return Integer + */ + public function getCursor() { + return $this->cursor; + } + + /** + * 设置光标 + * + * @param Integer $cursor + * @return Paging + */ + public function setCursor( $cursor ) { + $this->cursor = intval($cursor); + return $this; + } + + /** + * 取得总分页数 + * + * @return Integer + */ + public function getPageNum() { + if ($this->getSize() == 0) { + return 0; + } + return ceil($this->getTotal() / $this->getSize()); + } +} \ No newline at end of file diff --git a/yaf/Hood/Session.php b/yaf/Hood/Session.php new file mode 100644 index 0000000..ef31b6b --- /dev/null +++ b/yaf/Hood/Session.php @@ -0,0 +1,64 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 14/12/26 + * Time: 上午12:39 + */ + +namespace Hood; + +use Hood\Core\Session\SessionNamespace; +use Hood\Core\Session\CacheSession; +use Hood\Core\Session\FileSession; + +class Session +{ + + private static $_session = null; + + /** + * 开启Session并设置NameSpace + * @param $namespace + * @return SessionNamespace + */ + static public function start($namespace = 'session_default', $sessionName = null, $domain = null) + { + if (self::isSessionStarted() == false || self::$_session == null) { + self::$_session = new CacheSession($sessionName, $domain); + session_start(); + } + return new SessionNamespace ($namespace); + } + + /** + * 开启Session并设置NameSpace,使用File + * @param $namespace + * @return SessionNamespace + */ + static public function fileStart($namespace = 'session_default') + { + if (self::isSessionStarted() == false || self::$_session == null) { + self::$_session = new FileSession(); + session_start(); + } + return new SessionNamespace ($namespace); + } + + + /** + * 检查Session是否开启 + * @return bool + */ + static private function isSessionStarted() + { + if (php_sapi_name() !== 'cli') { + if (version_compare(phpversion(), '5.4.0', '>=')) { + return session_status() === PHP_SESSION_ACTIVE ? TRUE : FALSE; + } else { + return session_id() === '' ? FALSE : TRUE; + } + } + return FALSE; + } +} \ No newline at end of file diff --git a/yaf/Hood/Template/Mustache/Lightncandy.php b/yaf/Hood/Template/Mustache/Lightncandy.php new file mode 100644 index 0000000..9ce3cd0 --- /dev/null +++ b/yaf/Hood/Template/Mustache/Lightncandy.php @@ -0,0 +1,2759 @@ +<?php +/* + +Copyrights for code authored by Yahoo! Inc. is licensed under the following terms: +MIT License +Copyright (c) 2013-2015 Yahoo! Inc. All Rights Reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Origin: https://github.com/zordius/lightncandy +*/ + +/** + * This is abstract engine which defines must-have methods. + * + * @package LightnCandy + * @author Zordius <zordius@yahoo-inc.com> + */ + +/** + * LightnCandy static core class. + */ +namespace Hood\Template; +class LightnCandy +{ + // Compile time error handling flags + const FLAG_ERROR_LOG = 1; + const FLAG_ERROR_EXCEPTION = 2; + const FLAG_ERROR_SKIPPARTIAL = 4194304; + + // Compile the template as standalone PHP code which can execute without including LightnCandy + const FLAG_STANDALONE = 4; + const FLAG_BARE = 33554432; + const FLAG_NOESCAPE = 67108864; + + // JavaScript compatibility + const FLAG_JSTRUE = 8; + const FLAG_JSOBJECT = 16; + + // Handlebars.js compatibility + const FLAG_THIS = 32; + const FLAG_WITH = 64; + const FLAG_PARENT = 128; + const FLAG_JSQUOTE = 256; + const FLAG_ADVARNAME = 512; + const FLAG_SPACECTL = 1024; + const FLAG_NAMEDARG = 2048; + const FLAG_SPVARS = 4096; + const FLAG_SLASH = 8388608; + const FLAG_ELSE = 16777216; + + // PHP behavior flags + const FLAG_EXTHELPER = 8192; + const FLAG_ECHO = 16384; + const FLAG_PROPERTY = 32768; + const FLAG_METHOD = 65536; + const FLAG_RUNTIMEPARTIAL = 1048576; + + // Mustache compatibility + const FLAG_MUSTACHESP = 131072; + const FLAG_MUSTACHELOOKUP = 262144; + const FLAG_MUSTACHEPAIN = 2097152; + + // Template rendering time debug flags + const FLAG_RENDER_DEBUG = 524288; + + // alias flags + const FLAG_BESTPERFORMANCE = 16384; // FLAG_ECHO + const FLAG_JS = 24; // FLAG_JSTRUE + FLAG_JSOBJECT + const FLAG_MUSTACHE = 6684672; // FLAG_ERROR_SKIPPARTIAL + FLAG_MUSTACHESP + FLAG_MUSTACHELOOKUP + FLAG_MUSTACHEPAIN + const FLAG_HANDLEBARS = 27402208; // FLAG_THIS + FLAG_WITH + FLAG_PARENT + FLAG_JSQUOTE + FLAG_ADVARNAME + FLAG_SPACECTL + FLAG_NAMEDARG + FLAG_SPVARS + FLAG_SLASH + FLAG_ELSE + FLAG_MUSTACHESP + FLAG_MUSTACHEPAIN + const FLAG_HANDLEBARSJS = 27402232; // FLAG_JS + FLAG_HANDLEBARS + const FLAG_INSTANCE = 98304; // FLAG_PROPERTY + FLAG_METHOD + + // RegExps + const VARNAME_SEARCH = '/(\\[[^\\]]+\\]|[^\\[\\]\\.]+)/'; + const EXTENDED_COMMENT_SEARCH = '/{{!--.*?--}}/s'; + const IS_SUBEXP_SEARCH = '/^\(.+\)$/s'; + + // Positions of matched token + const POS_LOTHER = 1; + const POS_LSPACE = 2; + const POS_BEGINTAG = 3; + const POS_LSPACECTL = 4; + const POS_OP = 5; + const POS_INNERTAG = 6; + const POS_RSPACECTL = 7; + const POS_ENDTAG = 8; + const POS_RSPACE = 9; + const POS_ROTHER = 10; + + protected static $lastContext; + + /** + * Compile handlebars template into PHP code. + * + * @param string $template handlebars template string + * @param array <string,array|string|integer> $options LightnCandy compile time and run time options, default is array('flags' => LightnCandy::FLAG_BESTPERFORMANCE) + * + * @return string|false Compiled PHP code when successed. If error happened and compile failed, return false. + */ + public static function compile($template, $options = array('flags' => self::FLAG_BESTPERFORMANCE)) + { + $context = static::buildContext($options); + + if (static::handleError($context)) { + return false; + } + + // Strip extended comments + $template = preg_replace(static::EXTENDED_COMMENT_SEARCH, '{{! }}', $template); + + // Do first time scan to find out used feature, detect template error. + static::setupToken($context); + static::verifyTemplate($context, $template); + + if (static::handleError($context)) { + return false; + } + + $context['scan'] = false; + + // Do PHP code generation. + static::setupToken($context); + + // Handle dynamic partials + static::handleDynamicPartial($context); + + $code = static::compileTemplate($context, static::escapeTemplate($template)); + + // return false when fatal error + if (static::handleError($context)) { + return false; + } + + // Or, return full PHP render codes as string + return static::composePHPRender($context, $code); + } + + /** + * Include all partials when using dynamic partials + */ + protected static function handleDynamicPartial(&$context) + { + if ($context['usedFeature']['dynpartial'] == 0) { + return; + } + + foreach ($context['partials'] as $name => $code) { + static::readPartial($name, $context); + } + } + + /** + * Escape template + * + * @param string $template handlebars template string + * + * @return string Escaped template + * + * @expect 'abc' when input 'abc' + * @expect 'a\\\\bc' when input 'a\bc' + * @expect 'a\\\'bc' when input 'a\'bc' + */ + protected static function escapeTemplate($template) + { + return addcslashes(addcslashes($template, '\\'), "'"); + } + + /** + * Setup token delimiter by default or provided string + * + * @param array <string,array|string|integer> $context Current context + * @param string $left left string of a token + * @param string $right right string of a token + */ + protected static function setupToken(&$context, $left = '{{', $right = '}}') + { + if (preg_match('/=/', "$left$right")) { + $context['error'][] = "Can not set delimiter contains '=' , you try to set delimiter as '$left' and '$right'."; + return; + } + + $context['tokens']['startchar'] = substr($left, 0, 1); + $context['tokens']['left'] = $left; + $context['tokens']['right'] = $right; + + if (($left === '{{') && ($right === '}}')) { + $left = '\\{{2,3}'; + $right = '\\}{2,3}'; + } else { + $left = preg_quote($left); + $right = preg_quote($right); + } + + $context['tokens']['search'] = "/^(.*?)(\\s*)($left)(~?)([\\^#\\/!&>]?)(.*?)(~?)($right)(\\s*)(.*)\$/s"; + } + + /** + * Verify template and scan for used features + * + * @param array <string,array|string|integer> $context Current context + * @param string $template handlebars template + */ + protected static function verifyTemplate(&$context, $template) + { + while (preg_match($context['tokens']['search'], $template, $matches)) { + $context['tokens']['count']++; + static::scanFeatures($matches, $context); + $template = $matches[self::POS_ROTHER]; + } + } + + /** + * Compile template into PHP code (internal method) + * + * @param array <string,array|string|integer> $context Current context + * @param string $template handlebars template + * @param string $partial partial name when $template is come from the template + * + * @return string generated PHP code + */ + protected static function compileTemplate(&$context, $template, $partial = '') + { + // Check for recursive partial + if ($partial && !$context['flags']['runpart']) { + $context['partialStack'][] = $partial; + $diff = count($context['partialStack']) - count(array_unique($context['partialStack'])); + if ($diff > 1) { + $context['error'][] = "Skip rendering partial '$partial' again due to recursive detected"; + return ''; + } + if ($diff) { + $context['error'][] = 'I found recursive partial includes as the path: ' . implode(' -> ', $context['partialStack']) . '! You should fix your template or compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag.'; + } + } + + $code = ''; + while (preg_match($context['tokens']['search'], $template, $matches)) { + // Skip a token when it is slash escaped + if ($context['flags']['slash'] && ($matches[self::POS_LSPACE] === '') && preg_match('/^(.*?)(\\\\+)$/s', $matches[self::POS_LOTHER], $escmatch)) { + if (strlen($escmatch[2]) % 4) { + $code .= substr($matches[self::POS_LOTHER], 0, -2) . $context['tokens']['startchar']; + $matches[self::POS_BEGINTAG] = substr($matches[self::POS_BEGINTAG], 1); + $template = implode('', array_slice($matches, self::POS_BEGINTAG)); + continue; + } else { + $matches[self::POS_LOTHER] = $escmatch[1] . str_repeat('\\', strlen($escmatch[2]) / 2); + } + } + + $context['tokens']['current']++; + $tmpl = static::compileToken($matches, $context); + if ($tmpl == $context['ops']['seperator']) { + $tmpl = ''; + } else { + $tmpl = "'$tmpl'"; + } + $code .= "{$matches[self::POS_LOTHER]}{$matches[self::POS_LSPACE]}$tmpl"; + $template = "{$matches[self::POS_RSPACE]}{$matches[self::POS_ROTHER]}"; + } + + if ($partial && !$context['flags']['runpart']) { + array_pop($context['partialStack']); + } + + return "$code$template"; + } + + /** + * Compose LightnCandy render codes for include() + * + * @param array <string,array|string|integer> $context Current context + * @param string $code generated PHP code + * + * @return string Composed PHP code + */ + protected static function composePHPRender($context, $code) + { + $flagJStrue = static::getBoolStr($context['flags']['jstrue']); + $flagJSObj = static::getBoolStr($context['flags']['jsobj']); + $flagSPVar = static::getBoolStr($context['flags']['spvar']); + $flagProp = static::getBoolStr($context['flags']['prop']); + $flagMethod = static::getBoolStr($context['flags']['method']); + $flagMustlok = static::getBoolStr($context['flags']['mustlok']); + $flagEcho = static::getBoolStr($context['flags']['echo']); + $libstr = static::exportLCRun($context); + $constants = static::exportLCRunConstant($context); + $helpers = static::exportHelper($context); + $bhelpers = static::exportHelper($context, 'blockhelpers'); + $hbhelpers = static::exportHelper($context, 'hbhelpers'); + $debug = LCRun3::DEBUG_ERROR_LOG; + $phpstart = $context['flags']['bare'] ? '' : '<?php '; + $phpend = $context['flags']['bare'] ? '' : "\n?>"; + + // Return generated PHP code string. + return "{$phpstart}return function (\$in, \$debugopt = $debug) { + \$cx = array( + 'flags' => array( + 'jstrue' => $flagJStrue, + 'jsobj' => $flagJSObj, + 'spvar' => $flagSPVar, + 'prop' => $flagProp, + 'method' => $flagMethod, + 'mustlok' => $flagMustlok, + 'echo' => $flagEcho, + 'debug' => \$debugopt, + ), + 'constants' => $constants, + 'helpers' => $helpers, + 'blockhelpers' => $bhelpers, + 'hbhelpers' => $hbhelpers, + 'partials' => array({$context['partialCode']}), + 'scopes' => array(), + 'sp_vars' => array('root' => \$in), + 'lcrun' => '{$context['lcrun']}', +$libstr + ); + {$context['renderex']} + {$context['ops']['op_start']}'$code'{$context['ops']['op_end']} +}$phpend"; + } + + /** + * Build context from options + * + * @param array <string,array|string|integer> $options input options + * + * @return array<string,array|string|integer> Context from options + */ + protected static function buildContext($options) + { + if (!is_array($options)) { + $options = array(); + } + + $flags = isset($options['flags']) ? $options['flags'] : self::FLAG_BESTPERFORMANCE; + + $context = array( + 'flags' => array( + 'errorlog' => $flags & self::FLAG_ERROR_LOG, + 'exception' => $flags & self::FLAG_ERROR_EXCEPTION, + 'skippartial' => $flags & self::FLAG_ERROR_SKIPPARTIAL, + 'standalone' => $flags & self::FLAG_STANDALONE, + 'bare' => $flags & self::FLAG_BARE, + 'noesc' => $flags & self::FLAG_NOESCAPE, + 'jstrue' => $flags & self::FLAG_JSTRUE, + 'jsobj' => $flags & self::FLAG_JSOBJECT, + 'jsquote' => $flags & self::FLAG_JSQUOTE, + 'this' => $flags & self::FLAG_THIS, + 'with' => $flags & self::FLAG_WITH, + 'parent' => $flags & self::FLAG_PARENT, + 'echo' => $flags & self::FLAG_ECHO, + 'advar' => $flags & self::FLAG_ADVARNAME, + 'namev' => $flags & self::FLAG_NAMEDARG, + 'spvar' => $flags & self::FLAG_SPVARS, + 'slash' => $flags & self::FLAG_SLASH, + 'else' => $flags & self::FLAG_ELSE, + 'exhlp' => $flags & self::FLAG_EXTHELPER, + 'mustsp' => $flags & self::FLAG_MUSTACHESP, + 'mustlok' => $flags & self::FLAG_MUSTACHELOOKUP, + 'mustpi' => $flags & self::FLAG_MUSTACHEPAIN, + 'debug' => $flags & self::FLAG_RENDER_DEBUG, + 'prop' => $flags & self::FLAG_PROPERTY, + 'method' => $flags & self::FLAG_METHOD, + 'runpart' => $flags & self::FLAG_RUNTIMEPARTIAL, + ), + 'level' => 0, + 'scan' => true, + 'stack' => array(), + 'error' => array(), + 'basedir' => static::buildCXBasedir($options), + 'fileext' => static::buildCXFileext($options), + 'tokens' => array( + 'standalone' => true, + 'ahead' => false, + 'current' => 0, + 'count' => 0, + 'partialind' => '', + ), + 'usedPartial' => array(), + 'partialStack' => array(), + 'partialCode' => '', + 'usedFeature' => array( + 'rootthis' => 0, + 'enc' => 0, + 'raw' => 0, + 'sec' => 0, + 'isec' => 0, + 'if' => 0, + 'else' => 0, + 'unless' => 0, + 'each' => 0, + 'this' => 0, + 'parent' => 0, + 'with' => 0, + 'comment' => 0, + 'partial' => 0, + 'dynpartial' => 0, + 'helper' => 0, + 'bhelper' => 0, + 'hbhelper' => 0, + 'delimiter' => 0, + 'subexp' => 0, + ), + 'usedCount' => array( + 'var' => array(), + 'helpers' => array(), + 'blockhelpers' => array(), + 'hbhelpers' => array(), + 'lcrun' => array(), + ), + 'partials' => (isset($options['partials']) && is_array($options['partials'])) ? $options['partials'] : array(), + 'helpers' => array(), + 'blockhelpers' => array(), + 'hbhelpers' => array(), + 'renderex' => isset($options['renderex']) ? $options['renderex'] : '', + 'lcrun' => isset($options['lcrun']) ? $options['lcrun'] : 'LCRun3', + ); + + $context['ops'] = $context['flags']['echo'] ? array( + 'seperator' => ',', + 'f_start' => 'echo ', + 'f_end' => ';', + 'op_start' => 'ob_start();echo ', + 'op_end' => ';return ob_get_clean();', + 'cnd_start' => ';if ', + 'cnd_then' => '{echo ', + 'cnd_else' => ';}else{echo ', + 'cnd_end' => ';}echo ', + ) : array( + 'seperator' => '.', + 'f_start' => 'return ', + 'f_end' => ';', + 'op_start' => 'return ', + 'op_end' => ';', + 'cnd_start' => '.(', + 'cnd_then' => ' ? ', + 'cnd_else' => ' : ', + 'cnd_end' => ').', + ); + + $context['ops']['enc'] = $context['flags']['jsquote'] ? 'encq' : 'enc'; + $context = static::buildHelperTable($context, $options); + $context = static::buildHelperTable($context, $options, 'blockhelpers'); + $context = static::buildHelperTable($context, $options, 'hbhelpers'); + + return $context; + } + + /** + * Build custom helper table + * + * @param array <string,array|string|integer> $context prepared context + * @param array <string,array|string|integer> $options input options + * @param string $tname helper table name + * + * @return array<string,array|string|integer> context with generated helper table + * + * @expect array() when input array(), array() + * @expect array('flags' => array('exhlp' => 1)) when input array('flags' => array('exhlp' => 1)), array('helpers' => array('abc')) + * @expect array('error' => array('Can not find custom helper function defination abc() !'), 'flags' => array('exhlp' => 0)) when input array('error' => array(), 'flags' => array('exhlp' => 0)), array('helpers' => array('abc')) + * @expect array('flags' => array('exhlp' => 1), 'helpers' => array('LCRun3::raw' => 'LCRun3::raw')) when input array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('LCRun3::raw')) + * @expect array('flags' => array('exhlp' => 1), 'helpers' => array('test' => 'LCRun3::raw')) when input array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('test' => 'LCRun3::raw')) + */ + protected static function buildHelperTable($context, $options, $tname = 'helpers') + { + if (isset($options[$tname]) && is_array($options[$tname])) { + foreach ($options[$tname] as $name => $func) { + if (is_callable($func)) { + $context[$tname][is_int($name) ? $func : $name] = $func; + } else { + if (is_array($func)) { + $context['error'][] = "I found an array in $tname with key as $name, please fix it."; + } else { + if (!$context['flags']['exhlp']) { + $context['error'][] = "Can not find custom helper function defination $func() !"; + } + } + } + } + } + return $context; + } + + /** + * Read partial file content as string and store in context + * + * @param string $name partial name + * @param array <string,array|string|integer> $context Current context of compiler progress. + */ + protected static function readPartial($name, &$context) + { + $context['usedFeature']['partial']++; + + if (isset($context['usedPartial'][$name])) { + return; + } + + $cnt = static::resolvePartial($name, $context); + + if ($cnt !== null) { + return static::compilePartial($name, $context, $cnt); + } + + if (preg_match(static::IS_SUBEXP_SEARCH, $name)) { + if ($context['flags']['runpart']) { + $context['usedFeature']['dynpartial']++; + return; + } else { + $context['error'][] = "You use dynamic partial name as '$name', this only works with option FLAG_RUNTIMEPARTIAL enabled"; + return; + } + } + + if (!$context['flags']['skippartial']) { + $context['error'][] = "Can not find partial file for '$name', you should set correct basedir and fileext in options"; + } + } + + /** + * locate partial file, return the file name + * + * @param string $name partial name + * @param array <string,array|string|integer> $context Current context of compiler progress. + * + * @return string|null $content partial content + */ + protected static function resolvePartial(&$name, &$context) + { + if (isset($context['partials'][$name])) { + return $context['partials'][$name]; + } + + foreach ($context['basedir'] as $dir) { + foreach ($context['fileext'] as $ext) { + $fn = "$dir/$name$ext"; + if (file_exists($fn)) { + return file_get_contents($fn); + } + } + } + return null; + } + + /** + * compile partial file, stored in context + * + * @param string $name partial name + * @param array <string,array|string|integer> $context Current context of compiler progress. + * @param string $content partial content + */ + protected static function compilePartial(&$name, &$context, $content) + { + $context['usedPartial'][$name] = static::escapeTemplate($content); + + $originalAhead = $context['tokens']['ahead']; + $tmpContext = $context; + $tmpContext['level'] = 0; + static::setupToken($tmpContext); + + static::verifyTemplate($tmpContext, $content); + $originalToken = $context['tokens']; + $context = $tmpContext; + $context['tokens'] = $originalToken; + $context['tokens']['ahead'] = $originalAhead; + + if ($context['flags']['runpart']) { + $code = static::compileTemplate($context, $context['usedPartial'][$name], $name); + if ($context['flags']['mustpi']) { + $sp = ', $sp'; + $code = preg_replace('/^/m', "'{$context['ops']['seperator']}\$sp{$context['ops']['seperator']}'", $code); + // callbacks inside partial should be aware of $sp + $code = preg_replace('/\bfunction\s*\((.*?)\)\s*{/', 'function(\\1)use($sp){', $code); + } else { + $sp = ''; + } + $context['partialCode'] .= "'$name' => function (\$cx, \$in{$sp}) {{$context['ops']['op_start']}'$code'{$context['ops']['op_end']}},"; + } + } + + /** + * Internal method used by compile(). Check options and handle fileext. + * + * @param array <string,array|string|integer> $options current compile option + * + * @return array<string> file extensions + * + * @expect array('.tmpl') when input array() + * @expect array('test') when input array('fileext' => 'test') + * @expect array('test1') when input array('fileext' => array('test1')) + * @expect array('test2', 'test3') when input array('fileext' => array('test2', 'test3')) + */ + protected static function buildCXFileext($options) + { + $exts = isset($options['fileext']) ? $options['fileext'] : '.tmpl'; + return is_array($exts) ? $exts : array($exts); + } + + /** + * Internal method used by compile(). Check options and handle basedir. + * + * @param array <string,array|string|integer> $options current compile option + * + * @return array<string> base directories + * + * @expect array() when input array() + * @expect array() when input array('basedir' => array()) + * @expect array('src') when input array('basedir' => array('src')) + * @expect array('src') when input array('basedir' => array('src', 'dir_not_found')) + * @expect array('src', 'tests') when input array('basedir' => array('src', 'tests')) + */ + protected static function buildCXBasedir($options) + { + $dirs = isset($options['basedir']) ? $options['basedir'] : 0; + $dirs = is_array($dirs) ? $dirs : array($dirs); + $ret = array(); + + foreach ($dirs as $dir) { + if (is_string($dir) && is_dir($dir)) { + $ret[] = $dir; + } + } + + return $ret; + } + + /** + * Internal method used by compile(). Get PHP code from a closure of function as string. + * + * @param object $closure Closure object + * + * @return string + * + * @expect 'function($a) {return;}' when input function ($a) {return;} + * @expect 'function($a) {return;}' when input function ($a) {return;} + */ + protected static function getPHPCode($closure) + { + if (is_string($closure) && preg_match('/(.+)::(.+)/', $closure, $matched)) { + $ref = new ReflectionMethod($matched[1], $matched[2]); + } else { + $ref = new ReflectionFunction($closure); + } + $fname = $ref->getFileName(); + + $lines = file_get_contents($fname); + $file = new SplFileObject($fname); + $file->seek($ref->getStartLine() - 2); + $spos = $file->ftell(); + $file->seek($ref->getEndLine() - 1); + $epos = $file->ftell(); + + return preg_replace('/^.*?function(\s+[^\s\\(]+?)?\s*?\\((.+?)\\}[,\\s]*;?$/s', 'function($2}', substr($lines, $spos, $epos - $spos)); + } + + /** + * Internal method used by compile(). Export required custom helper functions. + * + * @param string $tname helper table name + * @param array <string,array|string|integer> $context current compile context + * + * @return string + */ + protected static function exportHelper($context, $tname = 'helpers') + { + $ret = ''; + foreach ($context[$tname] as $name => $func) { + if (!isset($context['usedCount'][$tname][$name])) { + continue; + } + if ((is_object($func) && ($func instanceof Closure)) || ($context['flags']['exhlp'] == 0)) { + $ret .= (" '$name' => " . static::getPHPCode($func) . ",\n"); + continue; + } + $ret .= " '$name' => '$func',\n"; + } + + return "array($ret)"; + } + + /** + * Internal method used by compile(). Export required standalone functions. + * + * @param array <string,array|string|integer> $context current compile context + * + * @return string + */ + protected static function exportLCRun($context) + { + if ($context['flags']['standalone'] == 0) { + return ''; + } + + $class = new ReflectionClass($context['lcrun']); + $fname = $class->getFileName(); + $lines = file_get_contents($fname); + $file = new SplFileObject($fname); + $methods = array(); + $ret = "'funcs' => array(\n"; + + foreach ($class->getMethods() as $method) { + $name = $method->getName(); + $file->seek($method->getStartLine() - 2); + $spos = $file->ftell(); + $file->seek($method->getEndLine() - 2); + $epos = $file->ftell(); + $methods[$name] = static::scanLCRunDependency($context, preg_replace('/public static function (.+)\\(/', '\'$1\' => function (', substr($lines, $spos, $epos - $spos))); + } + unset($file); + + $exports = array_keys($context['usedCount']['lcrun']); + + while (true) { + if (array_sum(array_map(function ($name) use (&$exports, $methods) { + $n = 0; + foreach ($methods[$name][1] as $child => $count) { + if (!in_array($child, $exports)) { + $exports[] = $child; + $n++; + } + } + return $n; + }, $exports)) == 0 + ) { + break; + } + } + + foreach ($exports as $export) { + $ret .= ($methods[$export][0] . " },\n"); + } + + return "$ret)\n"; + } + + /** + * Internal method used by compile(). Export standalone constants. + * + * @param array <string,array|string|integer> $context current compile context + * + * @return string + */ + protected static function exportLCRunConstant($context) + { + if ($context['flags']['standalone'] == 0) { + return 'array()'; + } + + $class = new ReflectionClass($context['lcrun']); + $constants = $class->getConstants(); + $ret = " array(\n"; + foreach ($constants as $name => $value) { + $ret .= " '$name' => " . (is_string($value) ? "'$value'" : $value) . ",\n"; + } + $ret .= " )"; + return $ret; + } + + /** + * Internal method used by compile(). Export required standalone functions. + * + * @param array <string,array|string|integer> $context current compile context + * @param string $code PHP code string of the method + * + * @return array<string|array> list of converted code and children array + */ + protected static function scanLCRunDependency($context, $code) + { + $child = array(); + + $code = preg_replace_callback('/self::(\w+?)\s*\(/', function ($matches) use ($context, &$child) { + if (!isset($child[$matches[1]])) { + $child[$matches[1]] = 0; + } + $child[$matches[1]]++; + + return "\$cx['funcs']['{$matches[1]}']("; + }, $code); + + // replace the constants + $code = preg_replace('/self::([A-Z0-9_]+)/', "\$cx['constants']['$1']", $code); + return array($code, $child); + } + + /** + * Internal method used by compile(). Handle exists error and return error status. + * + * @param array <string,array|string|integer> $context Current context of compiler progress. + * + * @throws Exception + * @return boolean True when error detected + * + * @expect true when input array('level' => 1, 'stack' => array('X'), 'flags' => array('errorlog' => 0, 'exception' => 0), 'error' => array()) + * @expect false when input array('level' => 0, 'error' => array()) + * @expect true when input array('level' => 0, 'error' => array('some error'), 'flags' => array('errorlog' => 0, 'exception' => 0)) + */ + protected static function handleError(&$context) + { + if ($context['level'] > 0) { + $token = array_pop($context['stack']); + $context['error'][] = "Unclosed token {{{#$token}}} !!"; + } + + static::$lastContext = $context; + + if (count($context['error'])) { + if ($context['flags']['errorlog']) { + error_log(implode("\n", $context['error'])); + } + if ($context['flags']['exception']) { + throw new Exception(implode("\n", $context['error'])); + } + return true; + } + return false; + } + + /** + * Internal method used by compile(). Return 'true' or 'false' string. + * + * @param integer $v value + * + * @return string 'true' when the value larger then 0 + * + * @expect 'true' when input 1 + * @expect 'true' when input 999 + * @expect 'false' when input 0 + * @expect 'false' when input -1 + */ + protected static function getBoolStr($v) + { + return ($v > 0) ? 'true' : 'false'; + } + + /** + * Get last compiler context. + * + * @return array<string,array|string|integer> Context data + */ + public static function getContext() + { + return static::$lastContext; + } + + /** + * Get a working render function by a string of PHP code. This method may requires php setting allow_url_include=1 and allow_url_fopen=1 , or access right to tmp file system. + * + * @param string $php PHP code + * @param string|null $tmpDir Optional, change temp directory for php include file saved by prepare() when cannot include PHP code with data:// format. + * @param boolean $delete Optional, delete temp php file when set to tru. Default is true, set it to false for debug propose + * + * @return Closure|false result of include() + * + * @deprecated + */ + public static function prepare($php, $tmpDir = null, $delete = true) + { + if (!ini_get('allow_url_include') || !ini_get('allow_url_fopen')) { + if (!is_string($tmpDir) || !is_dir($tmpDir)) { + $tmpDir = sys_get_temp_dir(); + } + } + + if (is_dir($tmpDir)) { + $fn = tempnam($tmpDir, 'lci_'); + if (!$fn) { + error_log("Can not generate tmp file under $tmpDir!!\n"); + return false; + } + if (!file_put_contents($fn, $php)) { + error_log("Can not include saved temp php code from $fn, you should add $tmpDir into open_basedir!!\n"); + return false; + } + + $phpfunc = include($fn); + + if ($delete) { + unlink($fn); + } + + return $phpfunc; + } + + return include('data://text/plain,' . urlencode($php)); + } + + /** + * Internal method used by compile(). Get function name for standalone or none standalone template. + * + * @param array <string,array|string|integer> $context Current context of compiler progress. + * @param string $name base function name + * @param string $tag original handlabars tag for debug + * + * @return string compiled Function name + * + * @expect 'LCRun3::test(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'lcrun' => 'LCRun3'), 'test', '' + * @expect 'LCRun3::test2(' when input array('flags' => array('standalone' => 0, 'debug' => 0), 'lcrun' => 'LCRun3'), 'test2', '' + * @expect "\$cx['funcs']['test3'](" when input array('flags' => array('standalone' => 1, 'debug' => 0), 'lcrun' => 'LCRun3'), 'test3', '' + * @expect 'LCRun3::debug(\'abc\', \'test\', ' when input array('flags' => array('standalone' => 0, 'debug' => 1), 'lcrun' => 'LCRun3'), 'test', 'abc' + */ + protected static function getFuncName(&$context, $name, $tag) + { + static::addUsageCount($context, 'lcrun', $name); + + if ($context['flags']['debug'] && ($name != 'miss')) { + $dbg = "'$tag', '$name', "; + $name = 'debug'; + static::addUsageCount($context, 'lcrun', 'debug'); + } else { + $dbg = ''; + } + + return $context['flags']['standalone'] ? "\$cx['funcs']['$name']($dbg" : "{$context['lcrun']}::$name($dbg"; + } + + /** + * Internal method used by getArrayCode(). Get variable names translated string. + * + * @param array <string> $scopes an array of variable names with single quote + * + * @return string PHP array names string + * + * @expect '' when input array() + * @expect '[a]' when input array('a') + * @expect '[a][b][c]' when input array('a', 'b', 'c') + */ + protected static function getArrayStr($scopes) + { + return count($scopes) ? '[' . implode('][', $scopes) . ']' : ''; + } + + /** + * Internal method used by getVariableName(). Get variable names translated string. + * + * @param array <string> $list an array of variable names. + * + * @return string PHP array names string + * + * @expect '' when input array() + * @expect "['a']" when input array('a') + * @expect "['a']['b']['c']" when input array('a', 'b', 'c') + */ + protected static function getArrayCode($list) + { + return static::getArrayStr(array_map(function ($v) { + return "'$v'"; + }, $list)); + } + + /** + * Internal method used by compile(). + * + * @param array <array> $vn variable name array. + * @param array <string,array|string|integer> $context current compile context + * + * @return array<string|array> variable names + * + * @expect array('array(array($in),array())', array('this')) when input array(null), array('flags'=>array('spvar'=>true)) + * @expect array('array(array($in,$in),array())', array('this', 'this')) when input array(null, null), array('flags'=>array('spvar'=>true)) + * @expect array('array(array(),array(\'a\'=>$in))', array('this')) when input array('a' => null), array('flags'=>array('spvar'=>true)) + */ + protected static function getVariableNames($vn, &$context) + { + $vars = array(array(), array()); + $exps = array(); + foreach ($vn as $i => $v) { + $V = static::getVariableNameOrSubExpression($v, $context); + if (is_string($i)) { + $vars[1][] = "'$i'=>{$V[0]}"; + } else { + $vars[0][] = $V[0]; + } + $exps[] = $V[1]; + } + return array('array(array(' . implode(',', $vars[0]) . '),array(' . implode(',', $vars[1]) . '))', $exps); + } + + /** + * Internal method used by compile(). + * + * @param string $subExpression subExpression to compile + * @param array <string,array|string|integer> $context current compile context + * @param boolean $keepCount keep original usage count + * + * @return array<string> code representing passed expression + */ + protected static function compileSubExpression($subExpression, &$context, $keepCount = false) + { + // mock up a token for this expression + $token = array_fill(self::POS_LOTHER, self::POS_ROTHER, ''); + + // strip outer ( ) from subexpression + $token[self::POS_INNERTAG] = substr($subExpression, 1, -1); + $oldCount = $context['usedFeature']; + list(, $vars) = static::parseTokenArgs($token, $context); + + // no separator is needed, this code will be used as a function argument + $origSeperator = $context['ops']['seperator']; + $context['ops']['seperator'] = ''; + // override $raw, subexpressions are never escaped + $ret = static::compileCustomHelper($context, $vars, true, true); + $context['ops']['seperator'] = $origSeperator; + + if ($keepCount) { + $context['usedFeature'] = $oldCount; + } else { + $context['usedFeature']['subexp']++; + // detect handlebars custom helpers. + if (isset($context['hbhelpers'][$vars[0][0]])) { + $context['usedFeature']['hbhelper']++; + } else { + // detect custom helpers. + if (isset($context['helpers'][$vars[0][0]])) { + $context['usedFeature']['helper']++; + } + } + } + + return array($ret ? $ret : '', $subExpression); + } + + /** + * Internal method used by compile(). + * + * @param array <array|string|integer> $var variable parsed path + * @param array <array|string|integer> $context current compile context + * + * @return array<string> variable names + */ + protected static function getVariableNameOrSubExpression($var, &$context) + { + if (isset($var[0]) && preg_match(static::IS_SUBEXP_SEARCH, $var[0])) { + return static::compileSubExpression($var[0], $context, true); + } + return static::getVariableName($var, $context); + } + + /** + * Internal method used by compile(). + * + * @param array <array|string|integer> $var variable parsed path + * @param array <array|string|integer> $context current compile context + * + * @return array<string> variable names + * + * @expect array('$in', 'this') when input array(null), array('flags'=>array('spvar'=>true,'debug'=>0)) + * @expect array('((isset($in[\'true\']) && is_array($in)) ? $in[\'true\'] : null)', '[true]') when input array('true'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('((isset($in[\'false\']) && is_array($in)) ? $in[\'false\'] : null)', '[false]') when input array('false'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('true', 'true') when input array(0, 'true'), array('flags'=>array('spvar'=>true,'debug'=>0)) + * @expect array('false', 'false') when input array(0, 'false'), array('flags'=>array('spvar'=>true,'debug'=>0)) + * @expect array('((isset($in[\'2\']) && is_array($in)) ? $in[\'2\'] : null)', '[2]') when input array('2'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('2', '2') when input array(0, '2'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0)) + * @expect array('((isset($in[\'@index\']) && is_array($in)) ? $in[\'@index\'] : null)', '[@index]') when input array('@index'), array('flags'=>array('spvar'=>false,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array("((isset(\$cx['sp_vars']['index']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['index'] : null)", '@[index]') when input array('@index'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array("((isset(\$cx['sp_vars']['key']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['key'] : null)", '@[key]') when input array('@key'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array("((isset(\$cx['sp_vars']['first']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['first'] : null)", '@[first]') when input array('@first'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array("((isset(\$cx['sp_vars']['last']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['last'] : null)", '@[last]') when input array('@last'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('((isset($in[\'"a"\']) && is_array($in)) ? $in[\'"a"\'] : null)', '["a"]') when input array('"a"'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('"a"', '"a"') when input array(0, '"a"'), array('flags'=>array('spvar'=>true,'debug'=>0)) + * @expect array('((isset($in[\'a\']) && is_array($in)) ? $in[\'a\'] : null)', '[a]') when input array('a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-1])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'] : null)', '../[a]') when input array(1,'a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-3])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'] : null)', '../../../[a]') when input array(3,'a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('((isset($in[\'id\']) && is_array($in)) ? $in[\'id\'] : null)', 'this.[id]') when input array(null, 'id'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0)) + * @expect array('LCRun3::v($cx, $in, array(\'id\'))', 'this.[id]') when input array(null, 'id'), array('flags'=>array('prop'=>true,'spvar'=>true,'debug'=>0,'method'=>0,'mustlok'=>0,'standalone'=>0), 'lcrun' => 'LCRun3') + */ + protected static function getVariableName($var, &$context) + { + if (isset($var[0]) && ($var[0] === 0)) { + return array($var[1], preg_replace('/\'(.*)\'/', '$1', $var[1])); + } + + $levels = 0; + $base = '$in'; + $spvar = false; + + if (isset($var[0])) { + // trace to parent + if (!is_string($var[0]) && is_int($var[0])) { + $levels = array_shift($var); + } + } + + if (isset($var[0])) { + // handle @root, @index, @key, @last, etc + if ($context['flags']['spvar']) { + if (substr($var[0], 0, 1) === '@') { + $spvar = true; + $base = "\$cx['sp_vars']"; + $var[0] = substr($var[0], 1); + } + } + } + + // change base when trace to parent + if ($levels > 0) { + if ($spvar) { + $base .= str_repeat("['_parent']", $levels); + } else { + $base = "\$cx['scopes'][count(\$cx['scopes'])-$levels]"; + } + } + + // Generate normalized expression for debug + $exp = static::getExpression($levels, $spvar, $var); + + if ((count($var) == 0) || (is_null($var[0]) && (count($var) == 1))) { + return array($base, $exp); + } + + if (is_null($var[0])) { + array_shift($var); + } + + // 1. To support recursive context lookup... + // 2. To support instance properties or methods... + // the only way is using slower rendering time variable resolver. + if ($context['flags']['prop'] || $context['flags']['method'] || $context['flags']['mustlok']) { + return array(static::getFuncName($context, 'v', $exp) . "\$cx, $base, array(" . implode(',', array_map(function ($V) { + return "'$V'"; + }, $var)) . '))', $exp); + } + + $n = static::getArrayCode($var); + array_pop($var); + $p = count($var) ? static::getArrayCode($var) : ''; + + return array("((isset($base$n) && is_array($base$p)) ? $base$n : " . ($context['flags']['debug'] ? (static::getFuncName($context, 'miss', '') . "\$cx, '$exp')") : 'null') . ')', $exp); + } + + /** + * Internal method used by compile(). + * + * @param integer $levels trace N levels top parent scope + * @param boolean $spvar is the path start with @ or not + * @param array <string|integer> $var variable parsed path + * + * @return string normalized expression for debug display + * + * @expect '[a].[b]' when input 0, false, array('a', 'b') + * @expect '@[root]' when input 0, true, array('root') + * @expect 'this' when input 0, false, null + * @expect 'this.[id]' when input 0, false, array(null, 'id') + * @expect '@[root].[a].[b]' when input 0, true, array('root', 'a', 'b') + * @expect '../../[a].[b]' when input 2, false, array('a', 'b') + * @expect '../[a\'b]' when input 1, false, array('a\'b') + */ + protected static function getExpression($levels, $spvar, $var) + { + return ($spvar ? '@' : '') . str_repeat('../', $levels) . ((is_array($var) && count($var)) ? implode('.', array_map(function ($v) { + return is_null($v) ? 'this' : "[$v]"; + }, $var)) : 'this'); + } + + /** + * Internal method used by compile(). Return array presentation for a variable name + * + * @param string $v variable name to be fixed. + * @param array <string,array|string|integer> $context Current compile content. + * + * @return array<integer,string> Return variable name array + * + * @expect array('this') when input 'this', array('flags' => array('advar' => 0, 'this' => 0)) + * @expect array() when input 'this', array('flags' => array('advar' => 0, 'this' => 1)) + * @expect array(1) when input '../', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0), 'scan' => true) + * @expect array(1) when input '../.', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0), 'scan' => true) + * @expect array(1) when input '../this', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0), 'scan' => true) + * @expect array(1, 'a') when input '../a', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0), 'scan' => true) + * @expect array(2, 'a', 'b') when input '../../a.b', array('flags' => array('advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0), 'scan' => true) + * @expect array(2, '[a]', 'b') when input '../../[a].b', array('flags' => array('advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0), 'scan' => true) + * @expect array(2, 'a', 'b') when input '../../[a].b', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0), 'scan' => true) + * @expect array('id') when input 'this.id', array('flags' => array('advar' => 1, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0)) + * @expect array(0, '\'a.b\'') when input '"a.b"', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)) + * @expect array(0, '123') when input '123', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)) + * @expect array(0, 'null') when input 'null', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0)) + */ + protected static function fixVariable($v, &$context) + { + // handle number + if (is_numeric($v)) { + // convert 0x00 or 0b00 numbers to decimal + return array(0, (string)1 * $v); + } + + // handle double quoted string + if (preg_match('/^"(.*)"$/', $v, $matched)) { + return array(0, "'" . preg_replace('/([^\\\\])\\\\\\\\"/', '$1"', preg_replace('/^\\\\\\\\"/', '"', $matched[1])) . "'"); + } + + // handle single quoted string + if (preg_match('/^\\\\\'(.*)\\\\\'$/', $v, $matched)) { + return array(0, "'$matched[1]'"); + } + + // handle boolean, null and undefined + if (preg_match('/^(true|false|null|undefined)$/', $v)) { + return array(0, ($v === 'undefined') ? 'null' : $v); + } + + $ret = array(); + $levels = 0; + + // handle .. + if ($v === '..') { + $v = '../'; + } + + // Trace to parent for ../ N times + $v = preg_replace_callback('/\\.\\.\\//', function () use (&$levels) { + $levels++; + return ''; + }, trim($v)); + + if ($levels) { + $ret[] = $levels; + if (!$context['flags']['parent']) { + $context['error'][] = 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag'; + } + $context['usedFeature']['parent'] += ($context['scan'] ? 1 : 0); + } + + if ($context['flags']['advar'] && preg_match('/\\]/', $v)) { + preg_match_all(self::VARNAME_SEARCH, $v, $matchedall); + } else { + preg_match_all('/([^\\.\\/]+)/', $v, $matchedall); + } + + foreach ($matchedall[1] as $m) { + if ($context['flags']['advar'] && substr($m, 0, 1) === '[') { + $ret[] = substr($m, 1, -1); + } else if ((!$context['flags']['this'] || ($m !== 'this')) && ($m !== '.')) { + $ret[] = $m; + } + } + + return $ret; + } + + /** + * Internal method used by scanFeatures() and compile(). Parse the token and return parsed result. + * + * @param array <string> $token preg_match results + * @param array <string,array|string|integer> $context current compile context + * + * @return array<boolean|array> Return parsed result + * + * @expect array(false, array(array())) when input array(0,0,0,0,0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(true, array(array())) when input array(0,0,0,'{{{',0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(true, array(array())) when input array(0,0,0,0,0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 1), 'scan' => false) + * @expect array(false, array(array('a'))) when input array(0,0,0,0,0,0,'a'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array('b'))) when input array(0,0,0,0,0,0,'a b'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array('"b'), array('c"'))) when input array(0,0,0,0,0,0,'a "b c"'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array(0, '\'b c\''))) when input array(0,0,0,0,0,0,'a "b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), 'q' => array('b c'))) when input array(0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array('q=[b c'))) when input array(0,0,0,0,0,0,'a [q=[b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), 'q' => array('[b'), array('c]'))) when input array(0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), 'q' => array('b'), array('c'))) when input array(0,0,0,0,0,0,'a [q]=b c'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), 'q' => array(0, '\'b c\''))) when input array(0,0,0,0,0,0,'a q="b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('(foo bar)'))) when input array(0,0,0,0,0,0,'(foo bar)'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 1), 'ops' => array('seperator' => ''), 'usedFeature' => array('subexp' => 0), 'scan' => false) + * @expect array(false, array(array('foo'), array("'=='"), array('bar'))) when input array(0,0,0,0,0,0,"foo '==' bar"), array('flags' => array('advar' => 1, 'namev' => 1, 'noesc' => 0, 'this' => 0), 'scan' => false) + * @expect array(false, array(array('( foo bar)'))) when input array(0,0,0,0,0,0,'( foo bar)'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0, 'exhlp' => 1), 'ops' => array('seperator' => ''), 'usedFeature' => array('subexp' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array(0, '\' b c\''))) when input array(0,0,0,0,0,0,'a " b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), 'q' => array(0, '\' b c\''))) when input array(0,0,0,0,0,0,'a q=" b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('foo'), array(0, "' =='"), array('bar'))) when input array(0,0,0,0,0,0,"foo \' ==\' bar"), array('flags' => array('advar' => 1, 'namev' => 1, 'noesc' => 0, 'this' => 0), 'scan' => false) + * @expect array(false, array(array('a'), array(' b c'))) when input array(0,0,0,0,0,0,'a [ b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array(array('a'), 'q' => array(0, "' d e'"))) when input array(0,0,0,0,0,0,"a q=\' d e\'"), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + * @expect array(false, array('q' => array('( foo bar)'))) when input array(0,0,0,0,0,0,'q=( foo bar)'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0), 'scan' => false) + */ + protected static function parseTokenArgs(&$token, &$context) + { + trim($token[self::POS_INNERTAG]); + + // Handle delimiter change + if (preg_match('/^=\s*([^ ]+)\s+([^ ]+)\s*=$/', $token[self::POS_INNERTAG], $matched)) { + static::setupToken($context, $matched[1], $matched[2]); + $token[self::POS_OP] = ' '; + return array(false, array()); + } + + // Skip validation on comments + if ($token[self::POS_OP] === '!') { + return array(false, array()); + } + + $vars = array(); + $count = preg_match_all('/(\s*)([^\s]+)/', $token[self::POS_INNERTAG], $matchedall); + + // Parse arguments and deal with "..." or [...] or (...) + if (($count > 0) && $context['flags']['advar']) { + $prev = ''; + $expect = 0; + foreach ($matchedall[2] as $index => $t) { + // continue from previous match when expect something + if ($expect) { + $prev .= "{$matchedall[1][$index]}$t"; + // end an argument when end with expected charactor + if (substr($t, -1, 1) === $expect) { + $vars[] = $prev; + $prev = ''; + $expect = 0; + } + continue; + } + + // continue to next match when begin with '(' without ending ')' + if (preg_match('/^\([^\)]*$/', $t)) { + $prev = $t; + $expect = ')'; + continue; + } + + // continue to next match when begin with '"' without ending '"' + if (preg_match('/^"[^"]*$/', $t)) { + $prev = $t; + $expect = '"'; + continue; + } + + // continue to next match when begin with \' without ending ' + if (preg_match('/^\\\\\'[^\']*$/', $t)) { + $prev = $t; + $expect = '\''; + continue; + } + + // continue to next match when '="' exists without ending '"' + if (preg_match('/^[^"]*="[^"]*$/', $t)) { + $prev = $t; + $expect = '"'; + continue; + } + + // continue to next match when '[' exists without ending ']' + if (preg_match('/\\[[^\\]]*$/', $t)) { + $prev = $t; + $expect = ']'; + continue; + } + + // continue to next match when =\' exists without ending ' + if (preg_match('/^[^\']*=\\\\\'[^\']*$/', $t)) { + $prev = $t; + $expect = '\''; + continue; + } + + // continue to next match when =( exists without ending ) + if (preg_match('/.+\([^\)]*$/', $t)) { + $prev = $t; + $expect = ')'; + continue; + } + + $vars[] = $t; + } + } else { + $vars = ($count > 0) ? $matchedall[2] : explode(' ', $token[self::POS_INNERTAG]); + } + + // Check for advanced variable. + $ret = array(); + $i = 0; + foreach ($vars as $idx => $var) { + // Skip advanced processing for subexpressions + if (preg_match(static::IS_SUBEXP_SEARCH, $var)) { + static::compileSubExpression($var, $context, !$context['scan']); + $ret[$i] = array($var); + $i++; + continue; + } + + if ($context['flags']['namev']) { + if (preg_match('/^((\\[([^\\]]+)\\])|([^=^["\']+))=(.+)$/', $var, $m)) { + if (!$context['flags']['advar'] && $m[3]) { + $context['error'][] = "Wrong argument name as '[$m[3]]' in " . static::tokenString($token) . ' ! You should fix your template or compile with LightnCandy::FLAG_ADVARNAME flag.'; + } + $idx = $m[3] ? $m[3] : $m[4]; + $var = $m[5]; + } + } + + $esc = $context['scan'] ? '' : '\\\\'; + if ($context['flags']['advar'] && !preg_match("/^(\"|$esc')(.*)(\"|$esc')$/", $var)) { + // foo] Rule 1: no starting [ or [ not start from head + if (preg_match('/^[^\\[\\.]+[\\]\\[]/', $var) + // [bar Rule 2: no ending ] or ] not in the end + || preg_match('/[\\[\\]][^\\]\\.]+$/', $var) + // ]bar. Rule 3: middle ] not before . + || preg_match('/\\][^\\]\\[\\.]+\\./', $var) + // .foo[ Rule 4: middle [ not after . + || preg_match('/\\.[^\\]\\[\\.]+\\[/', preg_replace('/^(..\\/)+/', '', preg_replace('/\\[[^\\]]+\\]/', '[XXX]', $var))) + ) { + $context['error'][] = "Wrong variable naming as '$var' in " . static::tokenString($token) . ' !'; + } else { + if (!$context['scan']) { + $name = preg_replace('/(\\[.+?\\])/', '', $var); + // Scan for invalid charactors which not be protected by [ ] + // now make ( and ) pass, later fix + if (preg_match('/[!"#%\'*+,;<=>{|}~]/', $name)) { + $context['error'][] = "Wrong variable naming as '$var' in " . static::tokenString($token) . ' ! You should wrap ! " # % & \' * + , ; < = > { | } ~ into [ ]'; + } + } + } + } + + if (($idx === 0) && ($token[self::POS_OP] === '>')) { + $var = array(preg_replace('/^("(.+)")|(\\[(.+)\\])$/', '$2$4', $var)); + } else if (preg_match('/^\(.+\)$/', $var)) { + $var = array($var); + } else { + $var = static::fixVariable($var, $context); + } + + if (is_string($idx)) { + $ret[$idx] = $var; + } else { + $ret[$i] = $var; + $i++; + } + } + + return array(($token[self::POS_BEGINTAG] === '{{{') || ($token[self::POS_OP] === '&') || $context['flags']['noesc'], $ret); + } + + /** + * Internal method used by scanFeatures(). return token string + * + * @param string[] $token detected handlebars {{ }} token + * @param integer $remove remove how many heading and ending token + * + * @return string Return whole token + * + * @expect 'b' when input array(0, 'a', 'b', 'c'), 1 + * @expect 'c' when input array(0, 'a', 'b', 'c', 'd', 'e') + */ + protected static function tokenString($token, $remove = 2) + { + return implode('', array_slice($token, 1 + $remove, -$remove)); + } + + /** + * Internal method used by scanFeatures(). Validate start and and. + * + * @param string[] $token detected handlebars {{ }} token + * @param array <string,array|string|integer> $context current compile context + * + * @return boolean|null Return true when invalid + * + * @expect null when input array_fill(0, 9, ''), array() + * @expect null when input array_fill(0, 9, '}}'), array() + * @expect true when input array_fill(0, 9, '{{{'), array() + */ + protected static function validateStartEnd($token, &$context) + { + // {{ }}} or {{{ }} are invalid + if (strlen($token[self::POS_BEGINTAG]) !== strlen($token[self::POS_ENDTAG])) { + $context['error'][] = 'Bad token ' . static::tokenString($token) . ' ! Do you mean {{' . static::tokenString($token, 4) . '}} or {{{' . static::tokenString($token, 4) . '}}}?'; + return true; + } + // {{{# }}} or {{{! }}} or {{{/ }}} or {{{^ }}} are invalid. + if ((strlen($token[self::POS_BEGINTAG]) === 3) && $token[self::POS_OP] && ($token[self::POS_OP] !== '&')) { + $context['error'][] = 'Bad token ' . static::tokenString($token) . ' ! Do you mean {{' . static::tokenString($token, 4) . '}} ?'; + return true; + } + } + + /** + * Internal method used by compile(). Collect handlebars usage information, detect template error. + * + * @param string[] $token detected handlebars {{ }} token + * @param array <string,array|string|integer> $context current compile context + * @param array <array> $vars parsed arguments list + * + * @return boolean|integer|null Return true when invalid or detected + * + * @expect null when input array(0, 0, 0, 0, 0, ''), array(), array() + * @expect 2 when input array(0, 0, 0, 0, 0, '^', '...'), array('usedFeature' => array('isec' => 1), 'level' => 0), array(array('foo')) + * @expect 3 when input array(0, 0, 0, 0, 0, '!', '...'), array('usedFeature' => array('comment' => 2)), array() + * @expect true when input array(0, 0, 0, 0, 0, '/'), array('stack' => array(1), 'level' => 1), array() + * @expect 4 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('sec' => 3), 'level' => 0), array(array('x')) + * @expect 5 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('if' => 4), 'level' => 0), array(array('if')) + * @expect 6 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('with' => 5), 'level' => 0, 'flags' => array('with' => 1)), array(array('with')) + * @expect 7 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('each' => 6), 'level' => 0), array(array('each')) + * @expect 8 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('unless' => 7), 'level' => 0), array(array('unless')) + * @expect 9 when input array(0, 0, 0, 0, 0, '#', '...'), array('blockhelpers' => array('abc' => ''), 'usedFeature' => array('bhelper' => 8), 'level' => 0), array(array('abc')) + * @expect 10 when input array(0, 0, 0, 0, 0, ' ', '...'), array('usedFeature' => array('delimiter' => 9), 'level' => 0), array() + * @expect 11 when input array(0, 0, 0, 0, 0, '#', '...'), array('hbhelpers' => array('abc' => ''), 'usedFeature' => array('hbhelper' => 10), 'level' => 0), array(array('abc')) + * @expect true when input array(0, 0, 0, 0, 0, '>', '...'), array('basedir' => array('.'), 'fileext' => array('.tmpl'), 'usedFeature' => array('unless' => 7, 'partial' => 7), 'level' => 0, 'flags' => array('skippartial' => 0)), array('test') + */ + protected static function validateOperations($token, &$context, $vars) + { + switch ($token[self::POS_OP]) { + case '>': + static::readPartial($vars[0][0], $context); + return true; + + case ' ': + return ++$context['usedFeature']['delimiter']; + + case '^': + if (isset($vars[0][0])) { + $context['stack'][] = $token[self::POS_INNERTAG]; + $context['level']++; + return ++$context['usedFeature']['isec']; + } + + if (!$context['flags']['else']) { + $context['error'][] = 'Do not support {{^}}, you should do compile with LightnCandy::FLAG_ELSE flag'; + } + return; + + case '/': + array_pop($context['stack']); + $context['level']--; + return true; + + case '!': + return ++$context['usedFeature']['comment']; + + case '#': + $context['stack'][] = $token[self::POS_INNERTAG]; + $context['level']++; + + if (!isset($vars[0][0])) { + return; + } + + if (is_string($vars[0][0])) { + // detect handlebars custom helpers. + if (isset($context['hbhelpers'][$vars[0][0]])) { + return ++$context['usedFeature']['hbhelper']; + } + + // detect block custom helpers. + if (isset($context['blockhelpers'][$vars[0][0]])) { + return ++$context['usedFeature']['bhelper']; + } + } + + switch ($vars[0][0]) { + case 'with': + if ($context['flags']['with']) { + if (count($vars) < 2) { + $context['error'][] = 'No argument after {{#with}} !'; + } + } else { + if (isset($vars[1][0])) { + $context['error'][] = 'Do not support {{#with var}}, you should do compile with LightnCandy::FLAG_WITH flag'; + } + } + // Continue to add usage... + case 'each': + case 'unless': + case 'if': + return ++$context['usedFeature'][$vars[0][0]]; + + default: + return ++$context['usedFeature']['sec']; + } + } + } + + /** + * Internal method used by compile(). Collect handlebars usage information, detect template error. + * + * @param string[] $token detected handlebars {{ }} token + * @param array <string,array|string|integer> $context current compile context + */ + protected static function scanFeatures($token, &$context) + { + list($raw, $vars) = static::parseTokenArgs($token, $context); + + if (static::validateStartEnd($token, $context)) { + return; + } + + if (static::validateOperations($token, $context, $vars)) { + return; + } + + if (($token[self::POS_OP] === '^') && ($context['flags']['else'])) { + return $context['usedFeature']['else']++; + } + + if (count($vars) == 0) { + return $context['error'][] = 'Wrong variable naming in ' . static::tokenString($token); + } + + if (!isset($vars[0])) { + return static::noNamedArguments($token, $context, true, ', you should use it after a custom helper.'); + } + + if ($vars[0] !== 'else') { + $context['usedFeature'][$raw ? 'raw' : 'enc']++; + } + + foreach ($vars as $var) { + if (!isset($var[0])) { + if ($context['level'] == 0) { + $context['usedFeature']['rootthis']++; + } + $context['usedFeature']['this']++; + } + } + + if (!isset($vars[0][0])) { + return; + } + + if ($vars[0][0] === 'else') { + if ($context['flags']['else']) { + return $context['usedFeature']['else']++; + } + } + + // detect handlebars custom helpers. + if (isset($context['hbhelpers'][$vars[0][0]])) { + return $context['usedFeature']['hbhelper']++; + } + + // detect custom helpers. + if (isset($context['helpers'][$vars[0][0]])) { + return $context['usedFeature']['helper']++; + } + } + + /** + * Internal method used by compile(). Show error message when named arguments appear without custom helper. + * + * @param array <string> $token detected handlebars {{ }} token + * @param array <string,array|string|integer> $context current compile context + * @param boolean $named is named arguments + * @param string $suggest extended hint for this no named argument error + */ + public static function noNamedArguments($token, &$context, $named, $suggest = '!') + { + if ($named) { + $context['error'][] = 'Do not support name=value in ' . static::tokenString($token) . $suggest; + } + } + + /** + * Internal method used by compileToken(). Modify $token when spacing rules matched. + * + * @param array <string> $token detected handlebars {{ }} token + * @param array <array|string|integer> $vars parsed arguments list + * @param array <string,array|string|integer> $context current compile context + * + * @return string|null Return compiled code segment for the token + */ + public static function handleMustacheSpacing(&$token, $vars, &$context) + { + if (!$context['flags']['mustsp'] && !$context['flags']['mustpi']) { + return; + } + + // left line change detection + $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[self::POS_LSPACE], $lmatch); + $ind = $lsp ? $lmatch[3] : $token[self::POS_LSPACE]; + + // right line change detection + $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[self::POS_RSPACE], $rmatch); + $st = true; + + // setup ahead flag + $ahead = $context['tokens']['ahead']; + $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[self::POS_RSPACE] . $token[self::POS_ROTHER]); + + // reset partial indent + $context['tokens']['partialind'] = ''; + + // same tags in the same line , not standalone + if (!$lsp && $ahead) { + $st = false; + } + + // Do not need standalone detection for these tags + if (!$token[self::POS_OP] || ($token[self::POS_OP] === '&')) { + if (!$context['flags']['else'] || (isset($vars[0][0]) && ($vars[0][0] !== 'else'))) { + $st = false; + } + } + + // not standalone because other things in the same line ahead + if ($token[self::POS_LOTHER] && !$token[self::POS_LSPACE]) { + $st = false; + } + + // not standalone because other things in the same line behind + if ($token[self::POS_ROTHER] && !$token[self::POS_RSPACE]) { + $st = false; + } + + if ($st && (($lsp && $rsp) // both side cr + || ($rsp && !$token[self::POS_LOTHER]) // first line without left + || ($lsp && ($context['tokens']['current'] == $context['tokens']['count']) && !$token[self::POS_ROTHER]) // final line + ) + ) { + // handle partial + if ($context['flags']['mustpi'] && ($token[self::POS_OP] === '>')) { + $context['tokens']['partialind'] = $ind; + } + if ($context['flags']['mustsp']) { + $token[self::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : ''); + $token[self::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : ''; + } + } + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars token. + * + * @param array <string> $token detected handlebars {{ }} token + * @param array <string,array|string|integer> $context current compile context + * + * @return string Return compiled code segment for the token + */ + public static function compileToken(&$token, &$context) + { + list($raw, $vars) = static::parseTokenArgs($token, $context); + $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0; + + // Handle spacing (standalone tags, partial indent) + static::handleMustacheSpacing($token, $vars, $context); + + // Handle space control. + if ($token[self::POS_LSPACECTL]) { + $token[self::POS_LSPACE] = ''; + } + + if ($token[self::POS_RSPACECTL]) { + $token[self::POS_RSPACE] = ''; + } + + if ($ret = static::compileSection($token, $context, $vars, $named)) { + return $ret; + } + + if (isset($vars[0][0])) { + if ($ret = static::compileCustomHelper($context, $vars, $raw)) { + return $ret; + } + if ($ret = static::compileElse($context, $vars)) { + return $ret; + } + } + + static::noNamedArguments($token, $context, $named, ', maybe you missing the custom helper?'); + + return static::compileVariable($context, $vars, $raw); + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars section token. + * + * @param array <string> $token detected handlebars {{ }} token + * @param array <string,array|string|integer> $context current compile context + * @param array <array|string|integer> $vars parsed arguments list + * @param boolean $named is named arguments or not + * + * @return string|null Return compiled code segment for the token when the token is section + */ + protected static function compileSection(&$token, &$context, &$vars, $named) + { + switch ($token[self::POS_OP]) { + case '>': + // mustache spec: ignore missing partial + if (($context['usedFeature']['dynpartial'] === 0) && !isset($context['usedPartial'][$vars[0][0]])) { + return $context['ops']['seperator']; + } + $p = array_shift($vars); + if (!isset($vars[0])) { + $vars[0] = array(); + } + $v = static::getVariableNames($vars, $context); + $tag = ">$p[0] " . implode(' ', $v[1]); + if ($context['flags']['runpart']) { + if (preg_match(static::IS_SUBEXP_SEARCH, $p[0])) { + list($p) = static::compileSubExpression($p[0], $context); + } else { + $p = "'$p[0]'"; + } + $sp = $context['tokens']['partialind'] ? ", '{$context['tokens']['partialind']}'" : ''; + return $context['ops']['seperator'] . static::getFuncName($context, 'p', $tag) . "\$cx, $p, $v[0]$sp){$context['ops']['seperator']}"; + } + if ($named || $v[0] !== 'array(array($in),array())') { + $context['error'][] = "Do not support {{{$tag}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag"; + } + return "{$context['ops']['seperator']}'" . static::compileTemplate($context, preg_replace('/^/m', $context['tokens']['partialind'], $context['usedPartial'][$p[0]]), $p[0]) . "'{$context['ops']['seperator']}"; + case '^': + // {{^}} means {{else}} + if (!isset($vars[0][0])) { + $vars[0][0] = 'else'; + $token[self::POS_OP] = ''; + return; + } + + // Try to compile as custom helper {{^myHelper}} + $r = static::compileBlockCustomHelper($context, $vars, true); + if ($r !== null) { + return $r; + } + + $v = static::getVariableName($vars[0], $context); + $context['stack'][] = $v[1]; + $context['stack'][] = '^'; + static::noNamedArguments($token, $context, $named); + // Compile to inverted section {{^myVar}} + return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'isec', '^' . $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}"; + case '/': + return static::compileBlockEnd($token, $context, $vars); + case '!': + case ' ': + return $context['ops']['seperator']; + case '#': + // Try to compile as custom helper {{#myHelper}} + $r = static::compileBlockCustomHelper($context, $vars); + if ($r !== null) { + return $r; + } + static::noNamedArguments($token, $context, $named, ', maybe you missing the block custom helper?'); + // Compile to section {{#myVar}} + return static::compileBlockBegin($context, $vars); + } + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars block custom helper begin token. + * + * @param array <string,array|string|integer> $context current compile context + * @param array <array|string|integer> $vars parsed arguments list + * @param boolean $inverted the logic will be inverted + * + * @return string|null Return compiled code segment for the token + */ + protected static function compileBlockCustomHelper(&$context, $vars, $inverted = false) + { + if (!isset($vars[0][0])) { + return; + } + $notHBCH = !isset($context['hbhelpers'][$vars[0][0]]); + + if (!isset($context['blockhelpers'][$vars[0][0]]) && $notHBCH) { + return; + } + + $v = static::getVariableName($vars[0], $context); + $context['stack'][] = $v[1]; + $context['stack'][] = '#'; + $ch = array_shift($vars); + $inverted = $inverted ? 'true' : 'false'; + + static::addUsageCount($context, $notHBCH ? 'blockhelpers' : 'hbhelpers', $ch[0]); + $v = static::getVariableNames($vars, $context); + return $context['ops']['seperator'] . static::getFuncName($context, $notHBCH ? 'bch' : 'hbch', ($inverted ? '^' : '#') . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, \$in, $inverted, function(\$cx, \$in) {{$context['ops']['f_start']}"; + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars block end token. + * + * @param array <string> $token detected handlebars {{ }} token + * @param array <string,array|string|integer> $context current compile context + * @param array <array|string|integer> $vars parsed arguments list + * + * @return string Return compiled code segment for the token + */ + protected static function compileBlockEnd(&$token, &$context, $vars) + { + $each = false; + $pop = array_pop($context['stack']); + switch ($token[self::POS_INNERTAG]) { + case 'if': + case 'unless': + if ($pop == ':') { + array_pop($context['stack']); + return $context['usedFeature']['parent'] ? "{$context['ops']['f_end']}}){$context['ops']['seperator']}" : "{$context['ops']['cnd_end']}"; + } + return $context['usedFeature']['parent'] ? "{$context['ops']['f_end']}}){$context['ops']['seperator']}" : "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}"; + case 'with': + if ($context['flags']['with']) { + if ($pop !== 'with') { + $context['error'][] = 'Unexpect token: {{/with}} !'; + return; + } + return "{$context['ops']['f_end']}}){$context['ops']['seperator']}"; + } + break; + case 'each': + $each = true; + } + + switch ($pop) { + case '#': + case '^': + $pop2 = array_pop($context['stack']); + $v = static::getVariableName($vars[0], $context); + if (!$each && ($pop2 !== $v[1])) { + $context['error'][] = 'Unexpect token ' . static::tokenString($token) . " ! Previous token {{{$pop}$pop2}} is not closed"; + return; + } + if ($pop == '^') { + return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}"; + } + return "{$context['ops']['f_end']}}){$context['ops']['seperator']}"; + default: + $context['error'][] = 'Unexpect token: ' . static::tokenString($token) . ' !'; + return; + } + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars block begin token. + * + * @param array <string,array|string|integer> $context current compile context + * @param array <array|string|integer> $vars parsed arguments list + * + * @return string Return compiled code segment for the token + */ + protected static function compileBlockBegin(&$context, $vars) + { + $each = 'false'; + $v = isset($vars[1]) ? static::getVariableNameOrSubExpression($vars[1], $context) : array(null, array()); + switch (isset($vars[0][0]) ? $vars[0][0] : null) { + case 'if': + $context['stack'][] = 'if'; + return $context['usedFeature']['parent'] + ? $context['ops']['seperator'] . static::getFuncName($context, 'ifv', 'if ' . $v[1]) . "\$cx, {$v[0]}, \$in, function(\$cx, \$in) {{$context['ops']['f_start']}" + : "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}"; + case 'unless': + $context['stack'][] = 'unless'; + return $context['usedFeature']['parent'] + ? $context['ops']['seperator'] . static::getFuncName($context, 'unl', 'unless ' . $v[1]) . "\$cx, {$v[0]}, \$in, function(\$cx, \$in) {{$context['ops']['f_start']}" + : "{$context['ops']['cnd_start']}(!" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}"; + case 'each': + $each = 'true'; + array_shift($vars); + if (!isset($vars[0])) { + $vars[0] = array(null); + } + break; + case 'with': + if ($context['flags']['with']) { + $context['stack'][] = 'with'; + return $context['ops']['seperator'] . static::getFuncName($context, 'wi', 'with ' . $v[1]) . "\$cx, {$v[0]}, \$in, function(\$cx, \$in) {{$context['ops']['f_start']}"; + } + } + + $v = static::getVariableNameOrSubExpression($vars[0], $context); + $context['stack'][] = $v[1]; + $context['stack'][] = '#'; + return $context['ops']['seperator'] . static::getFuncName($context, 'sec', (($each == 'true') ? 'each ' : '') . $v[1]) . "\$cx, {$v[0]}, \$in, $each, function(\$cx, \$in) {{$context['ops']['f_start']}"; + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars custom helper token. + * + * @param array <string,array|string|integer> $context current compile context + * @param array <array|string|integer> $vars parsed arguments list + * @param boolean $raw is this {{{ token or not + * @param boolean $err should cause error when missing helper or not + * + * @return string|null Return compiled code segment for the token when the token is custom helper + */ + protected static function compileCustomHelper(&$context, $vars, $raw, $err = false) + { + $notHH = !isset($context['hbhelpers'][$vars[0][0]]); + if (!isset($context['helpers'][$vars[0][0]]) && $notHH) { + if ($err) { + if (!$context['flags']['exhlp']) { + $context['error'][] = "Can not find custom helper function defination {$vars[0][0]}() !"; + } + } + return; + } + + $fn = $raw ? 'raw' : $context['ops']['enc']; + $ch = array_shift($vars); + $v = static::getVariableNames($vars, $context); + static::addUsageCount($context, $notHH ? 'helpers' : 'hbhelpers', $ch[0]); + return $context['ops']['seperator'] . static::getFuncName($context, $notHH ? 'ch' : 'hbch', "$ch[0] " . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, '$fn'" . ($notHH ? '' : ', $in') . "){$context['ops']['seperator']}"; + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars else token. + * + * @param array <string,array|string|integer> $context current compile context + * @param array <array|string|integer> $vars parsed arguments list + * + * @return string|null Return compiled code segment for the token when the token is else + */ + protected static function compileElse(&$context, &$vars) + { + if ($vars[0][0] === 'else') { + $c = count($context['stack']) - 1; + if ($c >= 0) { + switch ($context['stack'][count($context['stack']) - 1]) { + case 'if': + case 'unless': + $context['stack'][] = ':'; + return $context['usedFeature']['parent'] ? "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['f_start']}" : "{$context['ops']['cnd_else']}"; + case 'with': + case 'each': + case '#': + return "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['f_start']}"; + default: + } + } + $context['error'][] = '{{else}} only valid in if, unless, each, and #section context'; + } + } + + /** + * Internal method used by compile(). Return compiled PHP code partial for a handlebars variable token. + * + * @param array <string,array|string|integer> $context current compile context + * @param array <array|string|integer> $vars parsed arguments list + * @param boolean $raw is this {{{ token or not + * + * @return string Return compiled code segment for the token + */ + protected static function compileVariable(&$context, &$vars, $raw) + { + $v = static::getVariableName($vars[0], $context); + if ($context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug']) { + return $context['ops']['seperator'] . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $v[1]) . "\$cx, {$v[0]}){$context['ops']['seperator']}"; + } else { + return $raw ? "{$context['ops']['seperator']}$v[0]{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlentities((string){$v[0]}, ENT_QUOTES, 'UTF-8'){$context['ops']['seperator']}"; + } + } + + /** + * Internal method used by compile(). Add usage count to context + * + * @param array <string,array|string|integer> $context current context + * @param string $category ctegory name, can be one of: 'var', 'helpers', 'blockhelpers' + * @param string $name used name + * @param integer $count increment + * + * @expect 1 when input array('usedCount' => array('test' => array())), 'test', 'testname' + * @expect 3 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname' + * @expect 5 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname', 3 + */ + protected static function addUsageCount(&$context, $category, $name, $count = 1) + { + if (!isset($context['usedCount'][$category][$name])) { + $context['usedCount'][$category][$name] = 0; + } + return ($context['usedCount'][$category][$name] += $count); + } +} + +/** + * LightnCandy static class for compiled template runtime methods. + */ +class LCRun3 +{ + const DEBUG_ERROR_LOG = 1; + const DEBUG_ERROR_EXCEPTION = 2; + const DEBUG_TAGS = 4; + const DEBUG_TAGS_ANSI = 12; + const DEBUG_TAGS_HTML = 20; + + /** + * LightnCandy runtime method for output debug info. + * + * @param string $v expression + * @param string $f runtime function name + * @param array <string,array|string|integer> $cx render time context + * + * @expect '{{123}}' when input '123', 'miss', array('flags' => array('debug' => LCRun3::DEBUG_TAGS), 'lcrun' => 'LCRun3'), '' + * @expect '<!--MISSED((-->{{#123}}<!--))--><!--SKIPPED--><!--MISSED((-->{{/123}}<!--))-->' when input '123', 'wi', array('flags' => array('debug' => LCRun3::DEBUG_TAGS_HTML), 'lcrun' => 'LCRun3'), false, false, function () {return 'A';} + */ + public static function debug($v, $f, $cx) + { + $params = array_slice(func_get_args(), 2); + $r = call_user_func_array((isset($cx['funcs'][$f]) ? $cx['funcs'][$f] : "{$cx['lcrun']}::$f"), $params); + + if ($cx['flags']['debug'] & self::DEBUG_TAGS) { + $ansi = $cx['flags']['debug'] & (self::DEBUG_TAGS_ANSI - self::DEBUG_TAGS); + $html = $cx['flags']['debug'] & (self::DEBUG_TAGS_HTML - self::DEBUG_TAGS); + $cs = ($html ? (($r !== '') ? '<!!--OK((-->' : '<!--MISSED((-->') : '') + . ($ansi ? (($r !== '') ? "\033[0;32m" : "\033[0;31m") : ''); + $ce = ($html ? '<!--))-->' : '') + . ($ansi ? "\033[0m" : ''); + switch ($f) { + case 'sec': + case 'ifv': + case 'unl': + case 'wi': + if ($r == '') { + if ($ansi) { + $r = "\033[0;33mSKIPPED\033[0m"; + } + if ($html) { + $r = '<!--SKIPPED-->'; + } + } + return "$cs{{#{$v}}}$ce{$r}$cs{{/{$v}}}$ce"; + default: + return "$cs{{{$v}}}$ce"; + } + } else { + return $r; + } + } + + /** + * LightnCandy runtime method for missing data error. + * + * @param array <string,array|string|integer> $cx render time context + * @param string $v expression + */ + public static function miss($cx, $v) + { + $e = "LCRun3: $v is not exist"; + if ($cx['flags']['debug'] & self::DEBUG_ERROR_LOG) { + error_log($e); + return; + } + if ($cx['flags']['debug'] & self::DEBUG_ERROR_EXCEPTION) { + throw new Exception($e); + } + } + + /** + * LightnCandy runtime method for variable lookup. It is slower and only be used for instance property or method detection. + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer> $base current variable context + * @param array <string|integer> $path array of names for path + * + * @return null|string Return the value or null when not found + * + * @expect null when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), 0, array('a', 'b') + * @expect 3 when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0), 'mustlok' => 0), array('a' => array('b' => 3)), array('a', 'b') + * @expect null when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), (Object) array('a' => array('b' => 3)), array('a', 'b') + * @expect 3 when input array('scopes' => array(), 'flags' => array('prop' => 1, 'method' => 0, 'mustlok' => 0)), (Object) array('a' => array('b' => 3)), array('a', 'b') + */ + public static function v($cx, $base, $path) + { + $count = count($cx['scopes']); + while ($base) { + $v = $base; + foreach ($path as $name) { + if (is_array($v) && isset($v[$name])) { + $v = $v[$name]; + continue; + } + if (is_object($v)) { + if ($cx['flags']['prop'] && isset($v->$name)) { + $v = $v->$name; + continue; + } + if ($cx['flags']['method'] && is_callable(array($v, $name))) { + $v = $v->$name(); + continue; + } + } + if ($cx['flags']['mustlok']) { + unset($v); + break; + } + return null; + } + if (isset($v)) { + return $v; + } + $count--; + switch ($count) { + case -1: + $base = $cx['sp_vars']['root']; + break; + case -2: + return null; + default: + $base = $cx['scopes'][$count]; + } + } + } + + /** + * LightnCandy runtime method for {{#if var}}. + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $v value to be tested + * + * @return boolean Return true when the value is not null nor false. + * + * @expect false when input array(), null + * @expect false when input array(), 0 + * @expect false when input array(), false + * @expect true when input array(), true + * @expect true when input array(), 1 + * @expect false when input array(), '' + * @expect false when input array(), array() + * @expect true when input array(), array('') + * @expect true when input array(), array(0) + */ + public static function ifvar($cx, $v) + { + return !is_null($v) && ($v !== false) && ($v !== 0) && ($v !== 0.0) && ($v !== '') && (is_array($v) ? (count($v) > 0) : true); + } + + /** + * LightnCandy runtime method for {{#if var}} when {{../var}} used. + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $v value to be tested + * @param array <array|string|integer> $in input data with current scope + * @param Closure|null $truecb callback function when test result is true + * @param Closure|null $falsecb callback function when test result is false + * + * @return string The rendered string of the section + * + * @expect '' when input array('scopes' => array()), null, array(), null + * @expect '' when input array('scopes' => array()), null, array(), function () {return 'Y';} + * @expect 'Y' when input array('scopes' => array()), 1, array(), function () {return 'Y';} + * @expect 'N' when input array('scopes' => array()), null, array(), function () {return 'Y';}, function () {return 'N';} + */ + public static function ifv($cx, $v, $in, $truecb, $falsecb = null) + { + $ret = ''; + if (self::ifvar($cx, $v)) { + if ($truecb) { + $cx['scopes'][] = $in; + $ret = $truecb($cx, $in); + array_pop($cx['scopes']); + } + } else { + if ($falsecb) { + $cx['scopes'][] = $in; + $ret = $falsecb($cx, $in); + array_pop($cx['scopes']); + } + } + return $ret; + } + + /** + * LightnCandy runtime method for {{#unless var}} when {{../var}} used. + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $var value be tested + * @param array <array|string|integer>|string|integer|null $in input data with current scope + * @param Closure $truecb callback function when test result is true + * @param Closure|null $falsecb callback function when test result is false + * + * @return string Return rendered string when the value is not null nor false. + * + * @expect '' when input array('scopes' => array()), null, array(), null + * @expect 'Y' when input array('scopes' => array()), null, array(), function () {return 'Y';} + * @expect '' when input array('scopes' => array()), 1, array(), function () {return 'Y';} + * @expect 'Y' when input array('scopes' => array()), null, array(), function () {return 'Y';}, function () {return 'N';} + * @expect 'N' when input array('scopes' => array()), true, array(), function () {return 'Y';}, function () {return 'N';} + */ + public static function unl($cx, $var, $in, $truecb, $falsecb = null) + { + return self::ifv($cx, $var, $in, $falsecb, $truecb); + } + + /** + * LightnCandy runtime method for {{^var}} inverted section. + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $v value to be tested + * + * @return boolean Return true when the value is not null nor false. + * + * @expect true when input array(), null + * @expect false when input array(), 0 + * @expect true when input array(), false + * @expect false when input array(), 'false' + * @expect true when input array(), array() + * @expect false when input array(), array('1') + */ + public static function isec($cx, $v) + { + return is_null($v) || ($v === false) || (is_array($v) && (count($v) === 0)); + } + + /** + * LightnCandy runtime method for {{{var}}} . + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $v value to be output + * + * @return string The raw value of the specified variable + * + * @expect true when input array('flags' => array('jstrue' => 0)), true + * @expect 'true' when input array('flags' => array('jstrue' => 1)), true + * @expect '' when input array('flags' => array('jstrue' => 0)), false + * @expect 'false' when input array('flags' => array('jstrue' => 1)), false + * @expect 'false' when input array('flags' => array('jstrue' => 1)), false, true + * @expect 'Array' when input array('flags' => array('jstrue' => 1, 'jsobj' => 0)), array('a', 'b') + * @expect 'a,b' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', 'b') + * @expect '[object Object]' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', 'c' => 'b') + * @expect '[object Object]' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('c' => 'b') + * @expect 'a,true' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', true) + * @expect 'a,1' when input array('flags' => array('jstrue' => 0, 'jsobj' => 1)), array('a',true) + * @expect 'a,' when input array('flags' => array('jstrue' => 0, 'jsobj' => 1)), array('a',false) + * @expect 'a,false' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a',false) + */ + public static function raw($cx, $v) + { + if ($v === true) { + if ($cx['flags']['jstrue']) { + return 'true'; + } + } + + if (($v === false)) { + if ($cx['flags']['jstrue']) { + return 'false'; + } + } + + if (is_array($v)) { + if ($cx['flags']['jsobj']) { + if (count(array_diff_key($v, array_keys(array_keys($v)))) > 0) { + return '[object Object]'; + } else { + $ret = array(); + foreach ($v as $k => $vv) { + $ret[] = self::raw($cx, $vv); + } + return join(',', $ret); + } + } else { + return 'Array'; + } + } + + return "$v"; + } + + /** + * LightnCandy runtime method for {{var}} . + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $var value to be htmlencoded + * + * @return string The htmlencoded value of the specified variable + * + * @expect 'a' when input array(), 'a' + * @expect 'a&b' when input array(), 'a&b' + * @expect 'a'b' when input array(), 'a\'b' + */ + public static function enc($cx, $var) + { + return htmlentities(self::raw($cx, $var), ENT_QUOTES, 'UTF-8'); + } + + /** + * LightnCandy runtime method for {{var}} , and deal with single quote to same as handlebars.js . + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $var value to be htmlencoded + * + * @return string The htmlencoded value of the specified variable + * + * @expect 'a' when input array(), 'a' + * @expect 'a&b' when input array(), 'a&b' + * @expect 'a'b' when input array(), 'a\'b' + * @expect '`a'b' when input array(), '`a\'b' + */ + public static function encq($cx, $var) + { + return preg_replace('/`/', '`', preg_replace('/'/', ''', htmlentities(self::raw($cx, $var), ENT_QUOTES, 'UTF-8'))); + } + + /** + * LightnCandy runtime method for {{#var}} section. + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $v value for the section + * @param array <array|string|integer>|string|integer|null $in input data with current scope + * @param boolean $each true when rendering #each + * @param Closure $cb callback function to render child context + * @param Closure|null $else callback function to render child context when {{else}} + * + * @return string The rendered string of the section + * + * @expect '' when input array('flags' => array('spvar' => 0)), false, false, false, function () {return 'A';} + * @expect '' when input array('flags' => array('spvar' => 0)), null, null, false, function () {return 'A';} + * @expect 'A' when input array('flags' => array('spvar' => 0)), true, true, false, function () {return 'A';} + * @expect 'A' when input array('flags' => array('spvar' => 0)), 0, 0, false, function () {return 'A';} + * @expect '-a=' when input array('flags' => array('spvar' => 0)), array('a'), array('a'), false, function ($c, $i) {return "-$i=";} + * @expect '-a=-b=' when input array('flags' => array('spvar' => 0)), array('a','b'), array('a','b'), false, function ($c, $i) {return "-$i=";} + * @expect '' when input array('flags' => array('spvar' => 0)), 'abc', 'abc', true, function ($c, $i) {return "-$i=";} + * @expect '-b=' when input array('flags' => array('spvar' => 0)), array('a' => 'b'), array('a' => 'b'), true, function ($c, $i) {return "-$i=";} + * @expect '1' when input array('flags' => array('spvar' => 0)), 'b', 'b', false, function ($c, $i) {return count($i);} + * @expect '1' when input array('flags' => array('spvar' => 0)), 1, 1, false, function ($c, $i) {return print_r($i, true);} + * @expect '0' when input array('flags' => array('spvar' => 0)), 0, 0, false, function ($c, $i) {return print_r($i, true);} + * @expect '{"b":"c"}' when input array('flags' => array('spvar' => 0)), array('b' => 'c'), array('b' => 'c'), false, function ($c, $i) {return json_encode($i);} + * @expect 'inv' when input array('flags' => array('spvar' => 0)), array(), 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'inv' when input array('flags' => array('spvar' => 0)), array(), 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'inv' when input array('flags' => array('spvar' => 0)), false, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'inv' when input array('flags' => array('spvar' => 0)), false, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'inv' when input array('flags' => array('spvar' => 0)), '', 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'cb' when input array('flags' => array('spvar' => 0)), '', 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'inv' when input array('flags' => array('spvar' => 0)), 0, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'cb' when input array('flags' => array('spvar' => 0)), 0, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'inv' when input array('flags' => array('spvar' => 0)), new stdClass, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect 'cb' when input array('flags' => array('spvar' => 0)), new stdClass, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';} + * @expect '268' when input array('flags' => array('spvar' => 1), 'sp_vars'=>array('root' => 0)), array(1,3,4), 0, false, function ($c, $i) {return $i * 2;} + * @expect '038' when input array('flags' => array('spvar' => 1), 'sp_vars'=>array('root' => 0)), array(1,3,'a'=>4), 0, true, function ($c, $i) {return $i * $c['sp_vars']['index'];} + */ + public static function sec($cx, $v, $in, $each, $cb, $else = null) + { + $isAry = is_array($v); + $isTrav = $v instanceof Traversable; + $loop = $each; + $keys = null; + $last = null; + $isObj = false; + + if ($isAry && $else !== null && count($v) === 0) { + $cx['scopes'][] = $in; + $ret = $else($cx, $in); + array_pop($cx['scopes']); + return $ret; + } + + // #var, detect input type is object or not + if (!$loop && $isAry) { + $keys = array_keys($v); + $loop = (count(array_diff_key($v, array_keys($keys))) == 0); + $isObj = !$loop; + } + + if (($loop && $isAry) || $isTrav) { + if ($each && !$isTrav) { + // Detect input type is object or not when never done once + if ($keys == null) { + $keys = array_keys($v); + $isObj = (count(array_diff_key($v, array_keys($keys))) > 0); + } + } + $ret = array(); + $cx['scopes'][] = $in; + $i = 0; + if ($cx['flags']['spvar']) { + $old_spvar = $cx['sp_vars']; + $cx['sp_vars'] = array( + '_parent' => $old_spvar, + 'root' => $old_spvar['root'], + ); + if (!$isTrav) { + $last = count($keys) - 1; + } + } + foreach ($v as $index => $raw) { + if ($cx['flags']['spvar']) { + $cx['sp_vars']['first'] = ($i === 0); + $cx['sp_vars']['last'] = ($i == $last); + $cx['sp_vars']['key'] = $index; + $cx['sp_vars']['index'] = $i; + $i++; + } + $ret[] = $cb($cx, $raw); + } + if ($cx['flags']['spvar']) { + if ($isObj) { + unset($cx['sp_vars']['key']); + } else { + unset($cx['sp_vars']['last']); + } + unset($cx['sp_vars']['index']); + unset($cx['sp_vars']['first']); + $cx['sp_vars'] = $old_spvar; + } + array_pop($cx['scopes']); + return join('', $ret); + } + if ($each) { + if ($else !== null) { + $cx['scopes'][] = $in; + $ret = $else($cx, $v); + array_pop($cx['scopes']); + return $ret; + } + return ''; + } + if ($isAry) { + $cx['scopes'][] = $in; + $ret = $cb($cx, $v); + array_pop($cx['scopes']); + return $ret; + } + + if ($v === true) { + return $cb($cx, $in); + } + + if (!is_null($v) && ($v !== false)) { + return $cb($cx, $v); + } + + if ($else !== null) { + $cx['scopes'][] = $in; + $ret = $else($cx, $in); + array_pop($cx['scopes']); + return $ret; + } + + return ''; + } + + /** + * LightnCandy runtime method for {{#with var}} . + * + * @param array <string,array|string|integer> $cx render time context + * @param array <array|string|integer>|string|integer|null $v value to be the new context + * @param array <array|string|integer>|string|integer|null $in input data with current scope + * @param Closure $cb callback function to render child context + * @param Closure|null $else callback function to render child context when {{else}} + * + * @return string The rendered string of the token + * + * @expect '' when input array(), false, false, function () {return 'A';} + * @expect '' when input array(), null, null, function () {return 'A';} + * @expect '{"a":"b"}' when input array(), array('a'=>'b'), array('a'=>'c'), function ($c, $i) {return json_encode($i);} + * @expect '-b=' when input array(), 'b', array('a'=>'b'), function ($c, $i) {return "-$i=";} + */ + public static function wi($cx, $v, $in, $cb, $else = null) + { + if (($v === false) || ($v === null)) { + return $else ? $else($cx, $in) : ''; + } + $cx['scopes'][] = $in; + $ret = $cb($cx, $v); + array_pop($cx['scopes']); + return $ret; + } + + /** + * LightnCandy runtime method for {{> partial}} . + * + * @param array <string,array|string|integer> $cx render time context + * @param string $p partial name + * @param array <array|string|integer>|string|integer|null $v value to be the new context + * + * @return string The rendered string of the partial + * + */ + public static function p($cx, $p, $v, $sp = '') + { + $param = $v[0][0]; + + if (is_array($v[1])) { + if (is_array($v[0][0])) { + $param = array_merge($v[0][0], $v[1]); + } else if (($cx['flags']['method'] || $cx['flags']['prop']) && is_object($v[0][0])) { + foreach ($v[1] as $i => $v) { + $param->$i = $v; + } + } + } + + return call_user_func($cx['partials'][$p], $cx, $param, $sp); + } + + /** + * LightnCandy runtime method for custom helpers. + * + * @param array <string,array|string|integer> $cx render time context + * @param string $ch the name of custom helper to be executed + * @param array <array> $vars variables for the helper + * @param string $op the name of variable resolver. should be one of: 'raw', 'enc', or 'encq'. + * + * @return string The rendered string of the token + * + * @expect '=-=' when input array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('-'),array()), 'raw' + * @expect '=&=' when input array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('&'),array()), 'enc' + * @expect '='=' when input array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('\''),array()), 'encq' + * @expect '=b=' when input array('helpers' => array('a' => function ($i,$j) {return "={$j['a']}=";})), 'a', array(array(),array('a' => 'b')), 'raw' + */ + public static function ch($cx, $ch, $vars, $op) + { + return self::chret(call_user_func_array($cx['helpers'][$ch], $vars), $op); + } + + /** + * LightnCandy runtime method to handle response of custom helpers. + * + * @param string|array<string,array|string|integer> $ret return value from custom helper + * @param string $op the name of variable resolver. should be one of: 'raw', 'enc', or 'encq'. + * + * @return string The rendered string of the token + * + * @expect '=&=' when input '=&=', 'raw' + * @expect '=&'=' when input '=&\'=', 'enc' + * @expect '=&'=' when input '=&\'=', 'encq' + * @expect '=&'=' when input array('=&\'='), 'enc' + * @expect '=&'=' when input array('=&\'='), 'encq' + * @expect '=&=' when input array('=&=', false), 'enc' + * @expect '=&=' when input array('=&=', false), 'raw' + * @expect '=&=' when input array('=&=', 'raw'), 'enc' + * @expect '=&'=' when input array('=&\'=', 'encq'), 'raw' + */ + public static function chret($ret, $op) + { + if (is_array($ret)) { + if (isset($ret[1]) && $ret[1]) { + $op = $ret[1]; + } + $ret = $ret[0]; + } + + switch ($op) { + case 'enc': + return htmlentities($ret, ENT_QUOTES, 'UTF-8'); + case 'encq': + return preg_replace('/'/', ''', htmlentities($ret, ENT_QUOTES, 'UTF-8')); + } + return $ret; + } + + /** + * LightnCandy runtime method for Handlebars.js style custom helpers. + * + * @param array <string,array|string|integer> $cx render time context + * @param string $ch the name of custom helper to be executed + * @param array <array|string|integer>|string|integer|null $vars variables for the helper + * @param string $op the name of variable resolver. should be one of: 'raw', 'enc', or 'encq'. + * @param boolean $inverted the logic will be inverted + * @param Closure|null $cb callback function to render child context + * @param Closure|null $else callback function to render child context when {{else}} + * + * @return string The rendered string of the token + */ + public static function hbch($cx, $ch, $vars, $op, $inverted, $cb = null, $else = null) + { + $isBlock = (is_object($cb) && ($cb instanceof Closure)); + $args = $vars[0]; + $options = array( + 'name' => $ch, + 'hash' => $vars[1], + '_this' => $isBlock ? $op : $inverted, + ); + + // $invert the logic + if ($inverted) { + $tmp = $else; + $else = $cb; + $cb = $tmp; + } + + if ($isBlock) { + $options['fn'] = function ($context = '_NO_INPUT_HERE_') use ($cx, $op, $cb) { + if ($cx['flags']['echo']) { + ob_start(); + } + if ($context === '_NO_INPUT_HERE_') { + $cx['scopes'][] = $op; + $ret = $cb($cx, $op); + } else { + $cx['scopes'][] = $op; + $ret = $cb($cx, $context); + } + array_pop($cx['scopes']); + return $cx['flags']['echo'] ? ob_get_clean() : $ret; + }; + } + + if ($else) { + $options['inverse'] = function ($context = '_NO_INPUT_HERE_') use ($cx, $op, $else) { + if ($cx['flags']['echo']) { + ob_start(); + } + if ($context === '_NO_INPUT_HERE_') { + $ret = $else($cx, $op); + } else { + $cx['scopes'][] = $op; + $ret = $else($cx, $context); + array_pop($cx['scopes']); + } + return $cx['flags']['echo'] ? ob_get_clean() : $ret; + }; + } + + // prepare $options['data'] + if ($cx['flags']['spvar']) { + $options['data'] = $cx['sp_vars']; + } + + $args[] = $options; + $e = null; + $r = true; + + try { + $r = call_user_func_array($cx['hbhelpers'][$ch], $args); + } catch (Exception $E) { + $e = "LCRun3: call custom helper '$ch' error: " . $E->getMessage(); + } + + if ($e !== null) { + if ($cx['flags']['debug'] & self::DEBUG_ERROR_LOG) { + error_log($e); + } + if ($cx['flags']['debug'] & self::DEBUG_ERROR_EXCEPTION) { + throw new Exception($e); + } + } + + return self::chret($r, $isBlock ? 'raw' : $op); + } + + /** + * LightnCandy runtime method for block custom helpers. + * + * @param array <string,array|string|integer> $cx render time context + * @param string $ch the name of custom helper to be executed + * @param array <array|string|integer>|string|integer|null $vars variables for the helper + * @param array <array|string|integer>|string|integer|null $in input data with current scope + * @param boolean $inverted the logic will be inverted + * @param Closure $cb callback function to render child context + * @param Closure|null $else callback function to render child context when {{else}} + * + * @return string The rendered string of the token + * + * @expect '4.2.3' when input array('blockhelpers' => array('a' => function ($cx) {return array($cx,2,3);})), 'a', array(0, 0), 4, false, function($cx, $i) {return implode('.', $i);} + * @expect '2.6.5' when input array('blockhelpers' => array('a' => function ($cx,$in) {return array($cx,$in[0],5);})), 'a', array('6', 0), 2, false, function($cx, $i) {return implode('.', $i);} + * @expect '' when input array('blockhelpers' => array('a' => function ($cx,$in) {})), 'a', array('6', 0), 2, false, function($cx, $i) {return implode('.', $i);} + */ + public static function bch($cx, $ch, $vars, $in, $inverted, $cb, $else = null) + { + $r = call_user_func($cx['blockhelpers'][$ch], $in, $vars[0], $vars[1]); + + // $invert the logic + if ($inverted) { + $tmp = $else; + $else = $cb; + $cb = $tmp; + } + + $ret = ''; + if (is_null($r)) { + if ($else) { + $cx['scopes'][] = $in; + $ret = $else($cx, $r); + array_pop($cx['scopes']); + } + } else { + if ($cb) { + $cx['scopes'][] = $in; + $ret = $cb($cx, $r); + array_pop($cx['scopes']); + } + } + + return $ret; + } +} diff --git a/yaf/Hood/Validator.php b/yaf/Hood/Validator.php new file mode 100644 index 0000000..2f5fd60 --- /dev/null +++ b/yaf/Hood/Validator.php @@ -0,0 +1,20 @@ +<?php +/** + * Created by PhpStorm. + * User: Zip + * Date: 15/4/19 + * Time: 下午7:32 + */ + +namespace Hood; + +use Hood\Helper\Validation; + +class Validator +{ + static public function make(array $data, array $rules, array $messagesAttribute = array()) + { + $validator = new Validation\Validator($data, $rules, $messagesAttribute); + return $validator; + } +} \ No newline at end of file diff --git a/yohobuy/huodong.m.yohobuy.com/configs/core/cache.production.config.ini b/yohobuy/huodong.m.yohobuy.com/configs/core/cache.production.config.ini index b38c84f..e271a5c 100644 --- a/yohobuy/huodong.m.yohobuy.com/configs/core/cache.production.config.ini +++ b/yohobuy/huodong.m.yohobuy.com/configs/core/cache.production.config.ini @@ -1,7 +1,7 @@ [memcached] -master.hosts=10.170.182.9:12111,10.172.169.31:12111,10.173.8.214:12111 -slave.hosts=10.170.182.9:12112,10.172.169.31:12112,10.173.8.214:12112 -session.hosts=10.170.182.9:12111,10.172.169.31:12111,10.173.8.214:12111 +master.hosts=192.168.166.16:12111,192.168.166.17:12111,192.168.166.18:12111 +slave.hosts=192.168.166.16:12112,192.168.166.17:12112,192.168.166.18:12112 +session.hosts=192.168.166.16:12111,192.168.166.17:12111,192.168.166.18:12111 [redis] servers.hosts=127.0.0.1:6379 \ No newline at end of file diff --git a/yohobuy/m.yohobuy.com/configs/core/cache.production.config.ini b/yohobuy/m.yohobuy.com/configs/core/cache.production.config.ini index b38c84f..e271a5c 100644 --- a/yohobuy/m.yohobuy.com/configs/core/cache.production.config.ini +++ b/yohobuy/m.yohobuy.com/configs/core/cache.production.config.ini @@ -1,7 +1,7 @@ [memcached] -master.hosts=10.170.182.9:12111,10.172.169.31:12111,10.173.8.214:12111 -slave.hosts=10.170.182.9:12112,10.172.169.31:12112,10.173.8.214:12112 -session.hosts=10.170.182.9:12111,10.172.169.31:12111,10.173.8.214:12111 +master.hosts=192.168.166.16:12111,192.168.166.17:12111,192.168.166.18:12111 +slave.hosts=192.168.166.16:12112,192.168.166.17:12112,192.168.166.18:12112 +session.hosts=192.168.166.16:12111,192.168.166.17:12111,192.168.166.18:12111 [redis] servers.hosts=127.0.0.1:6379 \ No newline at end of file