├── public ├── robots.txt ├── favicon.ico ├── .htaccess └── index.php ├── .htaccess ├── library ├── Service │ ├── README.MD │ ├── Ucpaas.php │ ├── Api.php │ └── Qiniu.php ├── JsonSerializable.php ├── Bootstrap │ ├── product.php │ └── dev.php ├── Test │ ├── functions.php │ └── YafCase.php ├── Parse │ ├── Filter.php │ └── Xml.php ├── Safe.php ├── Random.php ├── Config.php ├── Debug │ ├── LogListener.php │ ├── SqlListener.php │ ├── Assertion.php │ ├── Tracer.php │ └── Header.php ├── Session.php ├── Rsa.php ├── Aes.php ├── Validate.php ├── Mail.php ├── Model.php ├── Debug.php ├── Kv.php ├── README.md ├── Cookie.php ├── Storage │ └── File.php ├── Input.php ├── Rest.php ├── Db.php ├── Cache.php └── Logger.php ├── app ├── controllers │ ├── README.MD │ ├── Error.php │ └── Index.php ├── models │ └── README.md ├── views │ └── index │ │ └── index.phtml └── email │ └── verify.tpl ├── .gitignore ├── tests ├── library │ ├── RsaTest.php │ ├── CipherTest.php │ ├── AesTest.php │ ├── ConfigTest.php │ ├── Storage │ │ └── FileTest.php │ ├── KvTest.php │ ├── CacheTest.php │ ├── DbTest.php │ ├── LoggerTest.php │ ├── Service │ │ └── DatabaseTest.php │ └── OrmTest.php ├── phpunit.xml └── yyf.sql ├── README.MD ├── .travis.yml ├── conf ├── app.ini └── secret.common.ini └── .php_cs.dist /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /* 3 | Allow: /index.php -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YunYinORG/YYF/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteRule ^(.*)$ public/$1 [QSA,PT,L] 4 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Options +FollowSymlinks 3 | RewriteEngine On 4 | RewriteCond %{REQUEST_FILENAME} !-d 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] 7 | -------------------------------------------------------------------------------- /library/Service/README.MD: -------------------------------------------------------------------------------- 1 | # 第三服务API接口 2 | 3 | ### 文件上传 4 | Flie.php 七牛文件管理接口 5 | ```php 6 | Qiniu::set($file,$new_file) #文件重命名 7 | Qiniu::get($name,$param='') #获取下载链接 8 | Qiniu::del($file) #删除文件 9 | Qiniu::getToken($name) #获取上传token 10 | ``` 11 | 12 | ### 数据库 13 | Db.php 数据库PDO封装的对象 14 | 15 | ### 邮件 16 | Smtp.php 邮件链接类 17 | Message.php 邮件消息 18 | -------------------------------------------------------------------------------- /library/JsonSerializable.php: -------------------------------------------------------------------------------- 1 | getConfig()->get('application.bootstrap') && $app->bootstrap(); 18 | 19 | // run the app;运行app 20 | $app->run(); 21 | -------------------------------------------------------------------------------- /library/Bootstrap/product.php: -------------------------------------------------------------------------------- 1 | getRouter()->addConfig($routes); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/controllers/README.MD: -------------------------------------------------------------------------------- 1 | controller目录 2 | ======== 3 | 4 | ## REST操作映射 5 | 6 | * GET操作 ==> GET_Action 7 | * POST操作 ==> POST_Action 8 | * PUT操作 ==> PUT_Action 9 | * DELETE操作 ==> DELETE_Action 10 | * 如果没有定义对应前缀的Action回调用默认的不带前缀Action 11 | * 对于数字(id)请求,会映射到一个`info`的Action,并绑定参数id 12 | 13 | 14 | ## `Test.php`示例代码 15 | 16 | ```php 17 | response=['method'=>'get']; 30 | } 31 | 32 | /** 33 | * 处理POST /Test/ 34 | */ 35 | function POST_indexAction() 36 | { 37 | $this->response['data']=$this->_request->getPost(); 38 | } 39 | 40 | 41 | /** 42 | * 处理 /Test/123 43 | */ 44 | function GET_infoAction($id) 45 | { 46 | $this->response(1,$id); 47 | } 48 | } 49 | ``` -------------------------------------------------------------------------------- /library/Test/functions.php: -------------------------------------------------------------------------------- 1 | &/\\%|{} ,; ,;、') === false) ? $str : false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #tempFile 2 | #临时文件 3 | temp/ 4 | runtime/ 5 | 6 | #vagrant 7 | .vagrant/ 8 | Vagrantfile 9 | start.cmd 10 | stop.cmd 11 | 12 | # 启动脚本 13 | # script 14 | server.cmd 15 | 16 | #editor 17 | .vscode/ 18 | .atom/ 19 | sftp-config.json 20 | #yaf tips 21 | .yaf.php 22 | 23 | 24 | #localfile 25 | #本地文件 26 | #secret config 27 | *.local 28 | conf/secret.product.ini 29 | 30 | # Windows image file caches 31 | Thumbs.db 32 | ehthumbs.db 33 | 34 | # Folder config file 35 | Desktop.ini 36 | 37 | # Recycle Bin used on file shares 38 | $RECYCLE.BIN/ 39 | 40 | # Windows Installer files 41 | *.cab 42 | *.msi 43 | *.msm 44 | *.msp 45 | 46 | # Windows shortcuts 47 | *.lnk 48 | 49 | # ========================= 50 | # Operating System Files 51 | # ========================= 52 | 53 | # OSX 54 | # ========================= 55 | 56 | .DS_Store 57 | .AppleDouble 58 | .LSOverride 59 | 60 | # Thumbnails 61 | ._* 62 | 63 | # Files that might appear on external disk 64 | .Spotlight-V100 65 | .Trashes 66 | 67 | # Directories potentially created on remote AFP share 68 | .AppleDB 69 | .AppleDesktop 70 | Network Trash Folder 71 | Temporary Items 72 | .apdisk 73 | 74 | #php-cs-fixer 75 | .php_cs 76 | .php_cs.cache -------------------------------------------------------------------------------- /library/Safe.php: -------------------------------------------------------------------------------- 1 | = $timesLimit) { 34 | $msg = '多次尝试警告:'.$key.'IP信息:'.self::ip(); 35 | Logger::write($msg, 'WARN'); 36 | return false; 37 | } 38 | 39 | Cache::set($name, ++$times, Config::get('try.expire')); 40 | return $times; 41 | } 42 | 43 | public static function del($key) 44 | { 45 | Cache::del('s_t_'.$key); 46 | } 47 | 48 | public static function ip() 49 | { 50 | $request_ip = getenv('REMOTE_ADDR'); 51 | $orign_ip = getenv('HTTP_X_FORWARDED_FOR') ?: getenv('HTTP_CLIENT_IP'); 52 | return $request_ip.'[client:'.$orign_ip.']'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /library/Parse/Xml.php: -------------------------------------------------------------------------------- 1 | '; 26 | $xml .= ''; 27 | $xml .= self::data_to_xml($data); 28 | $xml .= ''; 29 | return $xml; 30 | } 31 | 32 | /** 33 | * 数据XML编码 34 | * 35 | * @param mixed $data 数据 36 | * @param string $item 数字索引时的节点名称 37 | * @param string $id 数字索引key转换为的属性名 38 | * 39 | * @return string 40 | */ 41 | private static function data_to_xml($data, $item = 'item', $id = 'id') 42 | { 43 | $xml = $attr = ''; 44 | foreach ($data as $key => $val) { 45 | if (is_numeric($key)) { 46 | $id && $attr = " {$id}=\"{$key}\""; 47 | $key = $item; 48 | } 49 | $xml .= "<{$key}{$attr}>"; 50 | $xml .= (is_array($val) || is_object($val)) ? self::data_to_xml($val, $item, $id) : $val; 51 | $xml .= ""; 52 | } 53 | return $xml; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /library/Random.php: -------------------------------------------------------------------------------- 1 | = 2) { 28 | return str_pad(mt_rand(1, pow(10, $n)), '0', STR_PAD_LEFT); 29 | } 30 | 31 | $str = str_repeat('1234567890', $n / 2); 32 | return substr(str_shuffle($str), 0, $n); 33 | } 34 | 35 | /*数字和字母组合的随机字符串*/ 36 | public static function word($n = 8) 37 | { 38 | return $n < 1 ? '' : substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', ($n + 3) / 4)), 0, $n); 39 | } 40 | 41 | /*只有字符*/ 42 | public static function char($n = 10) 43 | { 44 | return $n < 1 ? '' : substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ($n + 3) / 4)), 0, $n); 45 | } 46 | 47 | /** 48 | * 验证码生成 49 | * 会过滤掉0O1lL等不易辨识字符 50 | * 51 | * @param int $n 个数 52 | * 53 | * @return string 54 | */ 55 | public static function code($n = 6) 56 | { 57 | return $n < 1 ? '' : substr(str_shuffle(str_repeat('abcdefghijkmnpqrstuvwxyz23456789ABCDEFGHJKMNPQRSTUVWXYZ', 3)), 0, $n); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /library/Config.php: -------------------------------------------------------------------------------- 1 | getConfig(); 37 | } 38 | $value = $config->get($key); 39 | return null === $value ? $default : $value; 40 | } 41 | 42 | /** 43 | * 获取私密配置 44 | * 45 | * @param string $name 配置名 46 | * @param string $key [键] 47 | * 48 | * @return mixed 结果 49 | * 50 | * @example 51 | * Config::getSecrect('encrypt') 获取取私密配置中的encrypt所有配置 52 | * Config::getSecrect('encrypt','key') 获取取私密配置中的encrypt配置的secret值 53 | */ 54 | public static function getSecret($name, $key = null) 55 | { 56 | if (!$secret = &Config::$_secret) { 57 | $secret = new Ini(Config::get('secret_path')); 58 | } 59 | return $key ? $secret->get($name)->get($key) : $secret->get($name); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /library/Debug/LogListener.php: -------------------------------------------------------------------------------- 1 | debugInfo($level, $message); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/library/RsaTest.php: -------------------------------------------------------------------------------- 1 | 'abcdefgh'), 30 | array('str' => '哈哈哈哈'), 31 | array('str' => 1234567), 32 | array('str' => ' 427038942yhias '), 33 | array('str' => 'a long string z9m=U@Aj~y7X#d6cIMx^k_SfT$51:Z)2|[l%3VF+PL8?'), 34 | array('str' => 'abce','pre' => 1), 35 | array('str' => 'sss','pre' => 'somekey'), 36 | array('str' => 'ddd','pre' => 1), 37 | ); 38 | } 39 | 40 | /** 41 | * 加密解密测试 42 | * 43 | * @dataProvider strPairProvider 44 | * 45 | * @param string $str 46 | * @param string $prefix 47 | */ 48 | public function testRSA($str, $prefix = '') 49 | { 50 | $pe = Rsa::encrypt($str, $prefix); //加密 51 | $this->assertNotEquals(false, $pe, 'encode:'.$prefix); 52 | $this->assertNotEquals($pe, $str); 53 | $data = Rsa::decrypt($pe, $prefix);//解密 54 | $this->assertNotEquals(false, $data, 'decode:'.$prefix); 55 | $this->assertEquals($str, $data, 'encode decode :'.$prefix); 56 | } 57 | 58 | public function testUniq() 59 | { 60 | $this->assertNotEquals(Rsa::pubKey(), Rsa::pubKey(1)); 61 | $this->assertNotEquals(Rsa::pubKey('somekey'), Rsa::pubKey(1)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/controllers/Error.php: -------------------------------------------------------------------------------- 1 | disableView(); 24 | $code = $exception->getCode(); 25 | $data = array( 26 | 'code' => $code, 27 | 'uri' => $this->_request->getRequestUri(), 28 | 'exception' => $exception->__toString(), 29 | ); 30 | // log 31 | Logger::error('[exception: {code}]({uri}): {exception}', $data); 32 | 33 | // header 34 | if (!($code >= 100 && $code < 1000)) { 35 | $code = 500; 36 | } 37 | header('Content-Type: application/json; charset=utf-8', true, $code); 38 | // output 39 | if ('dev' !== Yaf_Application::app()->environ()) { 40 | //非开发环境下错误消息打码 41 | $data['exception'] = '请求异常!'; 42 | } 43 | try { 44 | $rest = Config::get('rest'); 45 | } catch (Exception $e) { 46 | //配置读取异常 47 | $data['exception'] .= '[配置获取出错]'; 48 | $rest = array( 49 | 'status' => 'status', 50 | 'data' => 'data', 51 | 'error' => -10, 52 | 'json' => JSON_UNESCAPED_UNICODE, 53 | ); 54 | } 55 | echo json_encode( 56 | array( 57 | $rest['status'] => intval($rest['error']), 58 | $rest['data'] => $data, 59 | ), 60 | $rest['json'] 61 | ); //json encode 输出错误状态 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /library/Session.php: -------------------------------------------------------------------------------- 1 | find(); //返回phone为1388888的一个用户 27 | 28 | # select 29 | #批量查询select($data) 30 | $list=UserModel::slecet('id,name,time'); //列出所有用户的id和name 31 | 32 | # where 33 | # 条件查询 34 | UserModel::where('id','=',1)->find();//查找id=1的一个用户 35 | UserModel::where('id','>','1')->where('status','>',0)->select('id,name');//查询id>1的个用户的id和name 36 | # orWhere 37 | #OR 条件查询 38 | UserModel::where('id','=','1')->orWhere('status','>',0)->select('id,name');//查询id>1的个用户的id和name 39 | 40 | # insert 41 | # 插入数据 42 | # insert($data=[]) 43 | $id = UserModel::insert(['name'=>'测试','pwd'=>'123']);//插入一个新用户 44 | # add 45 | # 保存插入数据 46 | $id = UserModel::set(['pwd'=>'mypwd','name'=>'test'])->add();//添加新用户 47 | $id = UserModel::set('name','测试')->set('pwd',123')->add();//插入一个新用户 48 | 49 | # update 50 | # 更新数据 51 | UserModel::where('id',1')->update(['pwd'=>'1234']);//更新数据 52 | # save 53 | UserModel::set('pwd','1234')->save(1);//更新数据 54 | UserModel::set('pwd','1234')->where('id',1)->save();//更新数据 55 | 56 | # delete 57 | #删除数据 58 | UserModel::delete(1);//删除id为1的用户 59 | 60 | # page($page,$n)翻页操作 61 | # order($field,'')//排序 62 | UserModel::page(2,10)->order('id',true)->select(); 63 | 64 | # 其他操作 65 | # count统计 66 | # sum求和 67 | # avg均值 68 | # min最小值 69 | # max最大值 70 | # increment增加自段值 71 | # decrement 72 | #alias 73 | 74 | #join 75 | #has 76 | #belongs 77 | 78 | 79 | 80 | ``` 81 | 也可以实例化操作 82 | `$user=new UserModel` 83 | 84 | 也可以使用orm直接实例化未定义的模型(性能和效率上更优) 85 | ```php 86 | $user=new orm('user') 87 | $user->find(1) 88 | ``` 89 | 90 | 其使用方法和以上相同 -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | library/ConfigTest.php 16 | 17 | 18 | library/LoggerTest.php 19 | 20 | 21 | library/Storage/FileTest.php 22 | library/KvTest.php 23 | library/CacheTest.php 24 | 25 | 26 | library/Service/DatabaseTest.php 27 | library/DbTest.php 28 | library/OrmTest.php 29 | 30 | 31 | library/ 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ../library 43 | 44 | ../library/Bootstrap/ 45 | ../library/Test/ 46 | ../library/Debug/ 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /library/Rsa.php: -------------------------------------------------------------------------------- 1 | enableView(); 23 | $url = $this->_request->getBaseUri(); 24 | $this->getView() 25 | ->assign('version', Config::get('version')) 26 | ->assign('url', $url); 27 | } 28 | 29 | /** 30 | * GET /Index/test?data='' 31 | * GET请求测试 32 | * 33 | * success() 和 fail() 快速返回示例 34 | */ 35 | public function GET_testAction() 36 | { 37 | if (Input::get('data', $data)) {//get参数中含data 38 | //success快速返回成功信息 39 | $this->success($data); 40 | } else {//未输入data参数 41 | //fail快速返回出错信息 42 | $this->fail('please send request data with field name "data"'); 43 | } 44 | } 45 | 46 | /** 47 | * POST /Index/test 48 | * POST请求测试 49 | * 50 | * response()函数自定义状态 51 | */ 52 | public function POST_testAction() 53 | { 54 | if (Input::post('data', $data)) { 55 | // response() 指定状态为1 等同于success 56 | $this->response(1, $data); 57 | } else { 58 | //错误码为0,并指定http 状态码为400 59 | $this->response(0, 'please POST data with field name "data"', 400); 60 | } 61 | } 62 | 63 | /** 64 | * GET /Index/:id 65 | * 参数id映射示例 66 | * 67 | * @param int $id 自动绑定输入参数 68 | * 69 | * response自定义返回数据示例 70 | */ 71 | public function GET_infoAction($id = 0) 72 | { 73 | //response 即为返回内容 74 | $this->response = array( 75 | 'id' => $id, 76 | 'action' => 'GET_infoAction', 77 | 'params' => $_REQUEST, 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/views/index/index.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | YYF-YUNYIN YAF FRAMEWORK 5 | 6 | 28 | 29 | 30 |

YYFv

31 |

高效,安全,简单,优雅 的框架

32 |
33 | 34 |
35 |
36 |

POST /Index/test 测试

37 | 38 |
39 |
40 |

GET /Index/test 测试

41 | 42 |
43 |
44 |

id参数绑定/index/123测试

45 |
46 |
47 | 48 |
49 | 57 | 58 | -------------------------------------------------------------------------------- /tests/library/CipherTest.php: -------------------------------------------------------------------------------- 1 | assertSame($email, Encrypt::decryptEmail($encrypted), $encrypted); 44 | } 45 | 46 | public function phoneProvider() 47 | { 48 | return array( 49 | array('13888888888','salt',1), 50 | array('+8617012345679','%*(UWdx([]x',rand()), 51 | array('13612345678',23445456), 52 | array('1234','xxx'), 53 | array('12345','ss'), 54 | array('123456','ss'), 55 | array('1234567','salt'), 56 | array('12345678','salt'), 57 | array('12345679','salt'), 58 | array('123456790','salt'), 59 | ); 60 | } 61 | 62 | /** 63 | * 安全BASE64编码测试 64 | * 65 | * @dataProvider phoneProvider 66 | * 67 | * @param mixed $phone 68 | * @param mixed $salt 69 | * @param mixed $offset 70 | */ 71 | public function testPhone($phone, $salt, $offset = false) 72 | { 73 | $encrypted = Encrypt::encryptPhone($phone, $salt, $offset); 74 | $decrypted = Encrypt::decryptPhone($encrypted, $salt, $offset); 75 | $this->assertSame($phone, $decrypted, $encrypted); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/library/AesTest.php: -------------------------------------------------------------------------------- 1 | array('YYF Encrypt','WVlGIEVuY3J5cHQ_'), 25 | 'zh' => array('测试字符串','5rWL6K-V5a2X56ym5Liy'), 26 | 'char' => array('·39~!@#$%……&¥(#@*(+}{PL|>:AD>}WQE~"[]', 27 | 'wrczOX7vvIFAIyQl4oCm4oCmJu-.pe-8iCNAKu-8iCt9e1BMfD46QUQ-fVdRRX4iW10_'), 28 | 'empty' => array('',''), 29 | ); 30 | } 31 | 32 | /** 33 | * 安全BASE64编码测试 34 | * 35 | * @dataProvider base64Provider 36 | * 37 | * @param mixed $str 编码字符串 38 | * @param mixed $result 结果 39 | */ 40 | public function testBase64($str, $result) 41 | { 42 | $this->assertSame($result, AES::base64Encode($str)); 43 | $this->assertSame($str, AES::base64Decode($result)); 44 | } 45 | 46 | public function aesProvider() 47 | { 48 | return array( 49 | array('YYF Encrypt','s'), 50 | array('测试字符串','mysecretkey'), 51 | 'empty' => array('',''), 52 | 'space' => array(' hh ','xxx'), 53 | 'emptykey' => array('the key in empty',''), 54 | ); 55 | } 56 | 57 | /** 58 | * AES加密解密测试 59 | * 60 | * @dataProvider aesProvider 61 | * 62 | * @param mixed $str 字符 63 | * @param mixed $key 密钥 64 | */ 65 | public function testAES($str, $key) 66 | { 67 | $cipher = AES::encrypt($str, $key, false); 68 | $this->assertNotEquals(false, $cipher); 69 | $this->assertSame($str, AES::decrypt($cipher, $key, false), 'raw'); 70 | $cipher = AES::encrypt($str, $key, true); 71 | $this->assertNotEquals(false, $cipher); 72 | $this->assertSame($str, AES::decrypt($cipher, $key, true), 'safe64'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /library/Aes.php: -------------------------------------------------------------------------------- 1 | '-', '=' => '_', '/' => '.')); 31 | } 32 | 33 | /** 34 | * 路径安全形base64解码 35 | * 36 | * @param string $str [解码前字符串] 37 | * 38 | * @return string [安全base64解码后字符串] 39 | */ 40 | public static function base64Decode($str) 41 | { 42 | return base64_decode(strtr($str, array('-' => '+', '_' => '=', '.' => '/'))); 43 | } 44 | 45 | /** 46 | * AES加密函数,$data引用传真,直接变成密码 47 | * 改用openssl加密 48 | * 49 | * @param string $data 原文 50 | * @param string $key 密钥 51 | * @param bool $safe64=false 是否进行安全Base64编码 52 | * 53 | * @return string [加密后的密文 或者 base64编码后密文] 54 | */ 55 | public static function encrypt($data, $key, $safe64 = false) 56 | { 57 | // openssl_cipher_iv_length(self::MOD); 58 | $iv = openssl_random_pseudo_bytes(16); 59 | $data = $iv.openssl_encrypt($data, Aes::MOD, $key, true, $iv); 60 | return $safe64 ? Aes::base64Encode($data) : $data; 61 | } 62 | 63 | /** 64 | * aes解密函数 65 | * 66 | * @param string $cipher 密文 67 | * @param string $key 密钥 68 | * @param bool $safe64=false 是否是安全Base64编码的密文 69 | * 70 | * @return string 解密后的明文 71 | */ 72 | public static function decrypt($cipher, $key, $safe64 = false) 73 | { 74 | if ($cipher) { 75 | $safe64 and $cipher = Aes::base64Decode($cipher); 76 | 77 | $iv = substr($cipher, 0, 16); 78 | $cipher = substr($cipher, 16); 79 | return openssl_decrypt($cipher, Aes::MOD, $key, true, $iv); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /library/Service/Ucpaas.php: -------------------------------------------------------------------------------- 1 | _appid = $appId; 31 | $this->_url = $baseUrl.$softVersion.'/Accounts/'.$accountSid.'/Messages/templateSMS?sig='.$sig; 32 | $this->auth = trim(base64_encode($accountSid.':'.$timestamp)); 33 | } 34 | 35 | /** 36 | * 发送短信 37 | * 38 | * @param $phone 到达手机号 39 | * @param $msg 短信参数 40 | * @param $templateId 短信模板ID 41 | */ 42 | public function send($phone, $msg, $templateId) 43 | { 44 | $body_json = array( 45 | 'templateSMS' => array( 46 | 'appId' => $this->_appid, 47 | 'templateId' => $templateId, 48 | 'to' => $phone, 49 | 'param' => $msg 50 | ) 51 | ); 52 | $data = json_encode($body_json); 53 | 54 | $result = $this->_connection($data); 55 | $result = json_decode($result); 56 | return isset($result->resp->respCode) ? ($result->resp->respCode == 0) : false; 57 | } 58 | 59 | /** 60 | * 连接服务器回尝试curl 61 | * 62 | * @param $data post数据 63 | * 64 | * @return mixed|string 65 | */ 66 | private function _connection($data) 67 | { 68 | $ch = curl_init($this->_url); 69 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 70 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json;charset=utf-8', 'Authorization:'.$this->auth)); 71 | curl_setopt($ch, CURLOPT_POST, true); 72 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 73 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 74 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 75 | $result = curl_exec($ch); 76 | curl_close($ch); 77 | 78 | return $result; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /library/Validate.php: -------------------------------------------------------------------------------- 1 | &#\\%') === false; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/library/ConfigTest.php: -------------------------------------------------------------------------------- 1 | app->environ(); 30 | $config = parse_ini_file(APP_PATH.'/conf/app.ini', true); 31 | $current = $config[$env.':common'] + $config['common']; 32 | 33 | foreach ($current as $key => $value) { 34 | $this->assertSame($current[$key], Config::get($key), $key); 35 | } 36 | } 37 | 38 | /*检测空值*/ 39 | public function testEmpty() 40 | { 41 | $this->assertSame(Config::get(uniqid('_te_', true)), null); 42 | } 43 | 44 | /*测试默认值*/ 45 | public function testDefault() 46 | { 47 | $key = uniqid('_td_', true); 48 | $default = array(false,null,1,true,array(1,2,4),'test'); 49 | foreach ($default as $k => $d) { 50 | $this->assertSame(Config::get($k.$key, $d), $d); 51 | } 52 | } 53 | 54 | /** 55 | * @depends testConfigConsistency 56 | */ 57 | public function testSecretPath() 58 | { 59 | $secret_ini = Config::get('secret_path'); 60 | $this->assertFileExists($secret_ini, $secret_ini.' Config cannot find'); 61 | return $secret_ini; 62 | } 63 | 64 | /** 65 | * @depends testSecretPath 66 | * @covers ::getSecret 67 | * 68 | * @param mixed $path 69 | */ 70 | public function testSecret($path) 71 | { 72 | $secret = parse_ini_file($path, true); 73 | foreach ($secret as $name => &$key) { 74 | foreach ($key as $k => $v) { 75 | $this->assertSame(Config::getSecret($name, $k), $v, "$name.$k"); 76 | } 77 | } 78 | } 79 | 80 | public function testSecretArray() 81 | { 82 | $default_db = Config::getSecret('database', 'db._'); 83 | $this->assertNotEmpty($default_db); 84 | $this->assertArrayHasKey('dsn', $default_db); 85 | } 86 | 87 | /*检测sceret空值*/ 88 | public function testSecretEmpty() 89 | { 90 | $key = uniqid('_ts_', true); 91 | $this->assertSame(Config::getSecret('database', $key), null); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/yyf.sql: -------------------------------------------------------------------------------- 1 | -- ##兼容SQL数据库脚本## 2 | -- 开发(测试)环境,虚拟机自动初始化 3 | -- * MySQL(MariadDB)数据库 4 | -- [/*MYSQL]到[MYSQL*/]之间会只会导入mysql(MariadDB)数据库 5 | -- * SQLite数据库(runtime/yyf.db) 6 | -- [/*SQLITE]标签到[SQLITE*/]之间只会创建sqlite3数据库 7 | -- * 标签之外sql为公用sql 8 | -- * 注意: 9 | -- - 文件UTF8格式,无BOM头保存 10 | -- - 结束标签可以省略,缺省时到文件末尾 11 | -- - 标签必须在一行开头前面无空格 12 | -- - 可以有多对标签 13 | -- - 如果不需要可以删除此文件 14 | -- - 如果只用mysql可以定义在 mysql.sql中 15 | /******************** 下面定义SQL语句 ********************/ 16 | /******************* DEFINE SQL CODE *******************/ 17 | 18 | 19 | /*MYSQL FOR MYSQ created_at DATABASE 20 | 21 | DROP DATABASE IF EXISTS `yyf`; 22 | CREATE DATABASE `yyf` 23 | DEFAULT CHARACTER SET utf8 24 | DEFAULT COLLATE utf8_general_ci; 25 | 26 | SET NAMES utf8; 27 | USE `yyf`; 28 | 29 | MYSQL*/ 30 | 31 | 32 | /*SQLITE -- FOR SQLITE 33 | PRAGMA encoding="UTF-8"; 34 | SQLITE*/ 35 | 36 | 37 | /* 通用部分 */ 38 | DROP TABLE IF EXISTS `user`; 39 | CREATE TABLE `user` ( 40 | `id` INTEGER PRIMARY KEY, 41 | `account` char(16) NOT NULL, 42 | `name` char(16) NOT NULL, 43 | `home` char(64) DEFAULT NULL, 44 | `status` char(64) NOT NULL DEFAULT '1', 45 | `email` char(64) DEFAULT NULL, 46 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 47 | ); 48 | 49 | DROP TABLE IF EXISTS `project`; 50 | CREATE TABLE `project` ( 51 | `id` INTEGER PRIMARY KEY, 52 | `user_id` INTEGER, 53 | `name` char(16) NOT NULL, 54 | `home` char(64) DEFAULT NULL, 55 | `status` char(64) NOT NULL DEFAULT '1', 56 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 57 | FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) 58 | ); 59 | 60 | INSERT INTO `user` (`id`, `account`, `name`, `home`, `status`, `created_at`) VALUES 61 | (1, 'newfuture', 'New Future', 'https://github.com/NewFuture/', '1', '2016-08-25 07:16:33'); 62 | INSERT INTO `user` (`id`, `account`, `name`, `home`, `status`, `created_at`) VALUES 63 | (2, '', '测试', '', '0', '2016-08-25 07:17:21'); 64 | 65 | INSERT INTO `project` (`id`, `user_id`, `name`, `home`, `status`, `created_at`) VALUES 66 | (1, 1, 'yyf-book', 'https://github.com/NewFuture/yyf-book', '1', '2016-08-25 07:17:33'); 67 | INSERT INTO `project` (`id`, `user_id`, `name`, `home`, `status`, `created_at`) VALUES 68 | (2, 1, 'YYF-Debugger', 'https://github.com/NewFuture/YYF-Debugger', '1', '2016-08-25 07:18:21'); 69 | 70 | 71 | /*MYSQL 72 | -- MySQL主键自增 73 | SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; 74 | ALTER TABLE `user` CHANGE `id` `id` INTEGER NOT NULL AUTO_INCREMENT; 75 | ALTER TABLE `project` CHANGE `id` `id` INTEGER NOT NULL AUTO_INCREMENT; 76 | SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; 77 | -- InnoDB 78 | ALTER TABLE `user` ENGINE=InnoDB; 79 | ALTER TABLE `project` ENGINE=InnoDB; 80 | ALTER TABLE `project` ADD FOREIGN KEY (user_id) REFERENCES `user`(`id`); 81 | MYSQL*/ 82 | -------------------------------------------------------------------------------- /tests/library/Storage/FileTest.php: -------------------------------------------------------------------------------- 1 | environ(); 32 | } 33 | 34 | public static function tearDownAfterClass() 35 | { 36 | File::cleanDir(static::$dir); 37 | rmdir(static::$dir); 38 | } 39 | 40 | /** 41 | * @requires OS Linux 42 | * @covers ::__construct 43 | */ 44 | public function testDirMode() 45 | { 46 | $mode = $this->assertFileMode(static::$dir, 0777); 47 | } 48 | 49 | public function testSet() 50 | { 51 | $name = uniqid('test_set', true); 52 | static::$file->set($name, FileTest::TEST_STRING); 53 | $filename = static::$dir.'.'.$name.'.php'; 54 | $this->assertFileExists($filename); 55 | $this->assertFileMode($filename); 56 | $this->assertStringEqualsFile($filename, FileTest::PRE.FileTest::TEST_STRING); 57 | return $name; 58 | } 59 | 60 | /** 61 | * @depends testSet 62 | * 63 | * @param mixed $name 64 | */ 65 | public function testGet($name) 66 | { 67 | $str = static::$file->get($name); 68 | $this->assertSame($str, FileTest::TEST_STRING); 69 | $this->assertFalse(static::$file->get(uniqid('_rand_', true))); 70 | } 71 | 72 | /** 73 | * @depends testSet 74 | * 75 | * @param mixed $name 76 | */ 77 | public function testDelete($name) 78 | { 79 | static::$file->delete($name); 80 | $this->assertFileNotExists(static::$dir.'.'.$name.'.php'); 81 | $this->assertFalse(static::$file->get($name)); 82 | } 83 | 84 | /** 85 | * @depends testDelete 86 | * @covers ::cleanDir 87 | * @covers ::delete 88 | */ 89 | public function testFlush() 90 | { 91 | for ($i = 0; $i < 10; ++$i) { 92 | static::$file->set('test_'.uniqid(rand(1000, 10000)), rand()); 93 | } 94 | static::$file->flush(); 95 | $this->assertCount(2, scandir(static::$dir)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /library/Bootstrap/dev.php: -------------------------------------------------------------------------------- 1 | getRouter()->addConfig($routes); 32 | } 33 | } 34 | 35 | /** 36 | * 断言设置 37 | */ 38 | public function _initAssert() 39 | { 40 | $config = Config::get('assert')->toArray(); 41 | Assertion::init($config); 42 | } 43 | 44 | /** 45 | * 开启调试输出 46 | */ 47 | public function _initDebug() 48 | { 49 | if ($debug = Config::get('debug.error')) { 50 | error_reporting(E_ALL); //错误回传 51 | switch (strtolower($debug)) { 52 | case 'dump': //直接输出错误 53 | ini_set('display_errors', 1); 54 | break; 55 | 56 | case 'log': //log到文件 57 | ini_set('log_errors', 1); 58 | ini_set('error_log', Config::get('runtime').'/error_log.txt'); 59 | break; 60 | 61 | default: 62 | exit('未知调试类型设置,请检查[conf/app.ini]中的debug.type参数配置
'. 63 | "\nunkown debug type: $debug . check 'debug.type' setting in [conf/app.ini]"); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 开启日志监控 70 | */ 71 | public function _initLogListener() 72 | { 73 | if ($listen = Config::get('debug.listen')) { 74 | Debug::instance()->initLog($listen); 75 | } 76 | } 77 | 78 | /** 79 | * 记录数据库查询 80 | */ 81 | public function _initSqlListener() 82 | { 83 | if ($config = Config::get('debug.sql')) { 84 | if ($output = $config->get('output')) { 85 | Debug::instance()->initSQL($output, $config->get('result')); 86 | } 87 | if ($config->get('dumpdo')) { 88 | \Service\Database::$debug = true; 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * 加载统计插件 95 | * 96 | * @param Yaf_Dispatcher $dispatcher 97 | */ 98 | public function _initTracer(Yaf_Dispatcher $dispatcher) 99 | { 100 | if ($tacerdebug = Config::get('debug.tracer')) { 101 | $dispatcher->registerPlugin(Tracer::Instance($tacerdebug)); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /library/Mail.php: -------------------------------------------------------------------------------- 1 | _config = Config::getSecret('mail'); 32 | $this->_smtp = new Smtp(); 33 | $server = $this->_config['server']; 34 | $this->_smtp->setServer($server['smtp'], $server['port'], $server['secure']); 35 | } 36 | 37 | /** 38 | * 发送验证邮件 39 | * 40 | * @param string $email [邮箱] 41 | * @param string $name [姓名] 42 | * @param string $link [验证链接] 43 | * 44 | * @return bool 发送结果 45 | */ 46 | public static function sendVerify($email, $name, $link) 47 | { 48 | $instance = self::getInstance(); 49 | $from = $instance->_config['verify']; 50 | $to = array('email' => $email, 'name' => $name ?: $email); 51 | $url = $instance->_config['verify']['baseuri'].$link; 52 | 53 | $msg['title'] = 'YYF验证邮件'; 54 | $msg['body'] = $instance->getView() 55 | ->assign('name', $name) 56 | ->assign('url', $url) 57 | ->render('verify.tpl'); 58 | return $instance->send($from, $to, $msg); 59 | } 60 | 61 | /** 62 | * 发送邮件 63 | * 64 | * @param string $to [接收方邮箱] 65 | * @param array $msg [发送信息] 66 | * @param mixed $from 67 | * 68 | * @return bool [发送结果] 69 | */ 70 | public function send($from, $to, $msg) 71 | { 72 | $Message = new Message(); 73 | $Message->setFrom($from['name'], $from['email']) 74 | ->addTo($to['name'], $to['email']) 75 | ->setSubject($msg['title']) 76 | ->setBody($msg['body']); 77 | return $this->_smtp 78 | ->setAuth($from['email'], $from['pwd']) 79 | ->send($Message); 80 | } 81 | 82 | /** 83 | * 获取邮件服务对象 84 | */ 85 | public static function getInstance() 86 | { 87 | if (!self::$_instance) { 88 | self::$_instance = new self(); 89 | } 90 | return self::$_instance; 91 | // return self::$_instance ?: (self::$_instance = new self()); 92 | } 93 | 94 | /** 95 | * 获取模板引擎 96 | */ 97 | private function getView() 98 | { 99 | if (!$this->_view) { 100 | $this->_view = new View(APP_PATH.'/app/email/'); 101 | } 102 | return $this->_view; 103 | // return $this->_view ?: ($this->_view = new View(self::TPL_DIR)); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /library/Service/Api.php: -------------------------------------------------------------------------------- 1 | 32 | ``` 33 | │ .htaccess Apache开发环境和SAE重定向url 34 | │ .travis.yml travis-ci测试配置 35 | │ init.cmd 开发环境初始化通用脚本 36 | │ LICENSE Apache 2.0 许可证 37 | │ README.MD 38 | │ 39 | ├─app 应用目录【添加代码目录】 40 | │ │ 41 | │ │ README.MD 42 | │ │ 43 | │ ├─controllers 控制器目录【添加代码的主战场】 44 | │ │ Error.php 默认错误 45 | │ │ Index.php DEMO控制器 46 | │ │ 47 | │ ├─email 邮件模板目录 48 | │ │ verify.tpl 默认验证邮件模板示例 49 | │ │ 50 | │ ├─models 数据模型目录 51 | │ │ README.md 52 | │ │ 53 | │ └─views 视图目录 54 | │ └─index 55 | │ index.phtml 56 | │ 57 | ├─conf 配置目录 58 | │ app.ini 基础配置 59 | │ secret.common.ini 示例私密配置 60 | │ secret.product.ini 生产环境私密配置 61 | │ 62 | ├─library 核心库目录 63 | │ │ Cache.php 缓存管理类 64 | │ │ Config.php 配置读取类 65 | │ │ Cookie.php 安全Cookie接口 66 | │ │ Db.php 数据库操作封装 67 | │ │ Debug.php 调试类 68 | │ │ Encrypt.php 加密库 69 | │ │ Input.php 输入过滤接口 70 | │ │ Kv.php key-value存取类 71 | │ │ Logger.php 日志管理类 72 | │ │ Mail.php 邮件发送 73 | │ │ Model.php 基础model 74 | │ │ Orm.php ORM数据库对象映射 75 | │ │ Random.php 随机字符生成类 76 | │ │ README.md 77 | │ │ Rest.php 基础REST类 78 | │ │ Rsa.php RSA加密类 79 | │ │ Safe.php 安全统计类 80 | │ │ Session.php session管理接口 81 | │ │ Validate.php 类型验证类 82 | │ │ Wechat.php 微信登录接口库类 83 | │ │ 84 | │ ├─Bootstrap 启动加载 85 | │ │ dev.php 开发环境启动加载 86 | │ │ product.php 生产环境启动加载 87 | │ │ 88 | │ ├─Debug 调试相关库(开发环境) 89 | │ │ Assertion.php 断言处理类 90 | │ │ Header.php header头输出类 91 | │ │ Listener.php 日志监听类 92 | │ │ Tracer.php 消耗统计类 93 | │ │ 94 | │ ├─Service 系统基础服务 95 | │ │ Api.php 96 | │ │ Database.php 97 | │ │ Message.php 98 | │ │ Qiniu.php 99 | │ │ README.MD 100 | │ │ Smtp.php 101 | │ │ Ucpaas.php 102 | │ │ 103 | │ ├─Storage 存储驱动 104 | │ │ File.php 文件缓存类 105 | │ │ 106 | │ └─Test 单元测试库 107 | │ YafTest.php Yaf框架测试基类 108 | │ 109 | ├─public 公共目录【前端资源目录,生产环境根目录】 110 | │ .htaccess url重写 111 | │ favicon.ico 112 | │ index.php 入口文件 113 | │ robots.txt 114 | │ 115 | ├─runtime 默认缓存日志临时文件夹【保证程序具有可读写权限】 116 | │ 117 | └─tests 单元测试目录 118 | ``` 119 | > 120 | 121 | ## 许可协议(LICENSE) 122 | 123 | [Apache2.0 开源协议](LICENSE) 授权 124 | -------------------------------------------------------------------------------- /library/Test/YafCase.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('YAF扩展未加载[not YAF extension]'); 44 | } 45 | if (static::$auto_init) { 46 | if (!$this->app = static::app()) { 47 | $this->markTestSkipped('APP 启动失败!'); 48 | } 49 | } 50 | } 51 | 52 | public function __sleep() 53 | { 54 | $keys = array(); 55 | return $keys; 56 | } 57 | 58 | public function __wakeup() 59 | { 60 | if (static::$auto_init) { 61 | if (!$this->app = static::app()) { 62 | $this->markTestSkipped('APP 启动失败!'); 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * 获取当前APP 69 | * 70 | * @return Application 71 | */ 72 | public static function app() 73 | { 74 | if (!$app = Application::app()) { 75 | //加载APP 76 | $conf = APP_PATH.'/conf/app.ini'; 77 | $app = new Application(APP_PATH.'/conf/app.ini'); 78 | $conf = $app->getConfig(); 79 | //加载启动项 app Bootstrap 80 | if (static::$bootstrap && $conf->get('application.bootstrap')) { 81 | $app->bootstrap(); 82 | } 83 | 84 | if (version_compare(PHP_VERSION, '5.4.8', '<') && $app->environ() == 'dev') { 85 | //低版本(php5.3)断言 86 | Assertion::init($conf->get('assert')->toArray()); 87 | } 88 | } 89 | return $app; 90 | } 91 | 92 | /** 93 | * 检查文件权限, 94 | * 95 | * @param mixed $path 路径 96 | * @param $base 基础值,文件0666 目录0777 97 | * @param $umask 98 | * @requires OS Linux 99 | */ 100 | public function assertFileMode($path, $base = 0666, $umask = null) 101 | { 102 | $umask = $umask ?: static::app()->getConfig()->umask; 103 | if (null === $umask) { 104 | $mode = 0700 & $base; 105 | } else { 106 | $mode = intval($umask, 8) & $base ^ $base; 107 | } 108 | clearstatcache(); 109 | $this->assertSame(fileperms($path) & $mode, $mode, $path.'文件权限与预设不符(file permission not the same)'); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/library/KvTest.php: -------------------------------------------------------------------------------- 1 | 'test_value', 23 | '_test_key_n' => 123, 24 | '_test_key_l' => 'ss', 25 | '_test_key_null' => null, 26 | ); 27 | 28 | protected static $mDATA = array( 29 | '_test_kv2_s' => '22test_value', 30 | '_test_kv2_n' => 22123, 31 | '_test_kv2_l' => '222ss' 32 | ); 33 | 34 | public static function tearDownAfterClass() 35 | { 36 | Kv::flush(); 37 | } 38 | 39 | public function testSet() 40 | { 41 | foreach (KvTest::$DATA as $key => &$value) { 42 | $this->assertTrue(Kv::set($key, $value)); 43 | } 44 | } 45 | 46 | public function testMSet() 47 | { 48 | //mset 49 | $this->assertTrue(Kv::set(static::$mDATA)); 50 | } 51 | 52 | /** 53 | * @depends testSet 54 | */ 55 | public function testGet() 56 | { 57 | foreach (KvTest::$DATA as $key => &$value) { 58 | $this->assertEquals($value, Kv::get($key), $key.'不一致'); 59 | } 60 | $this->assertFalse(Kv::get(uniqid('_t_kv_'))); 61 | $this->assertSame('default', Kv::get(uniqid('_t_kv_'), 'default')); 62 | } 63 | 64 | /** 65 | * @depends testSet 66 | */ 67 | public function testMget() 68 | { 69 | //mget 70 | $data = static::$mDATA; 71 | $keys = array_keys($data); 72 | $this->assertEquals($data, Kv::get($keys)); 73 | //mget with 74 | $key = '_no.ttkv_key1_.'.rand(); 75 | $keys[] = $key; 76 | $data[$key] = false; 77 | $key = '_no_testkv_key_'.rand(); 78 | $keys[] = $key; 79 | $data[$key] = false; 80 | $this->assertEquals($data, Kv::get($keys)); 81 | } 82 | 83 | /** 84 | * @depends testSet 85 | */ 86 | public function testDel() 87 | { 88 | $key = uniqid('_t_kv_d'); 89 | Kv::Handler()->set($key, 'value'); 90 | $this->assertEquals(true, Kv::del($key)); 91 | $this->assertFalse(Kv::get($key)); 92 | 93 | // $key='new'.$key; 94 | // Kv::Handler()->set($key, 'value for delete'); 95 | // $this->assertEquals(true, Kv::delete($key)); 96 | // $this->assertFalse(Kv::get($key)); 97 | } 98 | 99 | /** 100 | * @depends testDel 101 | */ 102 | public function testFlush() 103 | { 104 | $this->assertNotFalse(Kv::flush()); 105 | } 106 | 107 | /** 108 | * @depends testFlush 109 | */ 110 | public function testClear() 111 | { 112 | $key = uniqid('_t_kv_'); 113 | $this->assertEquals(true, Kv::set($key, 'test value')); 114 | $this->assertEquals(true, Kv::clear()->set($key.'new', 'newtest')); 115 | $this->assertFalse(Kv::get($key)); 116 | $this->assertSame('newtest', Kv::get($key.'new')); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/email/verify.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 云印服务验证邮件 7 | 8 | 9 | 10 | 11 | 12 | 13 | 38 | 39 | 40 |
14 |
15 | 16 | 17 | 34 | 35 |
18 |

亲爱的

19 |

这是来自云印南天yunyin.org绑定邮箱的验证邮件

20 |

请确定这是您的个人操作,将绑定您的邮箱

21 |

输入验证码或者点击一下链接完成验证

22 | 23 | 24 | 27 | 28 |
25 |

点击以验证邮箱

26 |
29 |

如果无法正常打开请复制到浏览器

30 |
31 |

关注新浪微博@云印南天

32 |

关注项目最新进度Github

33 |
36 |
37 |
41 | 42 | 43 | 44 | 45 | 56 | 57 | 58 |
46 |
47 | 48 | 49 | 52 | 53 |
50 |

Copyright©2014-2015云印南天

51 |
54 |
55 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # http://about.travis-ci.org/docs/user/languages/php/ 2 | # dist: trusty 3 | sudo: false 4 | language: php 5 | 6 | # list any PHP version 7 | php: 8 | # using major version aliases 9 | # aliased to a recent 7.x version 10 | - 7.1 11 | - 7.0 12 | - 5.6 13 | - 5.5 14 | - 5.4 15 | # - 5.3 16 | 17 | services: 18 | - mysql 19 | - memcached 20 | - redis-server 21 | 22 | git: 23 | depth: 2 24 | 25 | # https://docs.travis-ci.com/user/environment-variables/ 26 | # optionally specify a list of environments 27 | env: 28 | # global: 29 | 30 | matrix: 31 | - environ=dev 32 | - environ=product 33 | 34 | 35 | # optionally set up exclutions and allowed failures in the matrix 36 | # matrix: 37 | # # allow_failures: 38 | # # - php: 5.3 39 | 40 | 41 | #init tests environment 42 | install: 43 | # use init.cmd YYF ( test the server.cmd, when php>=5.4) 44 | - if [[ "$environ" == "dev" ]];then 45 | if [[ $TRAVIS_PHP_VERSION = "5.3" ]];then 46 | echo 3 |./init.cmd; 47 | else 48 | echo 2 |./init.cmd & 49 | fi; 50 | else 51 | echo 4 |./init.cmd; 52 | fi; 53 | - export PHP_CONF="$(dirname ~/ss)/.phpenv/versions/$(phpenv version-name)/etc" 54 | # Enable redis.so and memcached.so 55 | # test sqlite in dev and test mysql in product 56 | - if [[ "$environ" == "product" ]];then 57 | echo -e "extension = redis.so\nextension = memcached.so">> $PHP_CONF/php.ini; 58 | sed -i'' 's/options.3 = 2/options.3 = 0/' conf/secret.*.ini; 59 | else 60 | sed -i'' -e's/db\._\./db.tmp./' -e's/db\.test\./db._./' -e's/db\.tmp\./db.test./' conf/secret.common.ini; 61 | fi; 62 | # allow yaf autoload 63 | - echo 'yaf.use_spl_autoload = 1' >> $PHP_CONF/php.ini 64 | - '[[ "$environ" != "product" ]] || wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.8.0/php-cs-fixer.phar -O ../php-cs-fixer' 65 | - '[[ "$environ" != "product" ]] || chmod +x ../php-cs-fixer' 66 | 67 | # execute any number of scripts before the test run, custom env's are available as variables 68 | before_script: 69 | # init MySQL database 70 | - sed '/^\/\*MYSQL/d;/MYSQL\*\//d' tests/yyf.sql|mysql -uroot; 71 | # init Sqlite database 72 | - sed '/^\/\*SQLITE/d;/SQLITE\*\//d' tests/yyf.sql|sqlite3 runtime/yyf.db; 73 | # Waiting for the php dev server to start 74 | - if [[ "$environ" == "dev" && $TRAVIS_PHP_VERSION != "5.3" ]];then 75 | while [ ! -f "$PHP_CONF/conf.d/yaf.ini" ];do 76 | sleep 0.5; 77 | done; 78 | echo "Initialized!"; 79 | fi; 80 | 81 | # omitting "script:" will default to phpunit 82 | # use the env variable to determine the phpunit.xml to use 83 | script: 84 | # test php 85 | - phpunit -c tests/phpunit.xml 86 | # check format style 87 | # disabled xdebug to speed up 88 | - phpenv config-rm xdebug.ini 89 | - '[[ "$environ" != "product" || $TRAVIS_PHP_VERSION < "5.6" ]] || ../php-cs-fixer fix -v --dry-run --using-cache=no library/' 90 | - '[[ "$environ" != "product" || $TRAVIS_PHP_VERSION < "5.6" ]] || ../php-cs-fixer fix -v --dry-run --using-cache=no app/' 91 | - '[[ "$environ" != "product" || $TRAVIS_PHP_VERSION < "5.6" ]] || ../php-cs-fixer fix -v --dry-run --using-cache=no public/' 92 | # test server 93 | - if [[ "$environ" == "dev" && $TRAVIS_PHP_VERSION != "5.3" ]];then 94 | curl -sS localhost:1122|grep -iPo '(?<=)(.*)(?=)'; 95 | fi; 96 | 97 | 98 | #on fail 99 | after_failure: 100 | - if [[ "$environ" == "dev" && $TRAVIS_PHP_VERSION != "5.3" ]];then cat server.cmd ;fi; 101 | - cat runtime/log/*.log 102 | - test -f runtime/error* && cat runtime/error* 103 | 104 | #on success 105 | after_success: 106 | - cat runtime/phpunit/test.txt 107 | 108 | # configure notifications (email, IRC, campfire etc) 109 | notifications: 110 | email: 111 | on_failure: change 112 | on_success: change 113 | -------------------------------------------------------------------------------- /library/Model.php: -------------------------------------------------------------------------------- 1 | save(1);//将id为1的用户密码设置为1234 24 | * UserModel::where('id','=','1')->update(['pwd'=>'1234']);//同上,也可以使用save 25 | * 26 | * UserModel::set(['pwd'=>'mypwd','name'=>'test'])->add();//添加新用户 27 | * UserModel::add(['pwd'=>'mypwd','name'=>'test']);//同上,也可以使用insert() 28 | * 29 | * UserModel::where('id','>','10')->where('id','<','100')->select();//查找所有id在10到100之间的用户 30 | * UserModel::where('id','=','1')->get('name');//获取id为一的用户的name 31 | * UserModel::where('id','>','1')->limit(10)->select('id,name');//查询id>1的10个用户的id和name 32 | * UserModel::where('id','>','1')->field('id','uid')->field('name','uname')->select();//查询id>1的用户的uid和uname(表示id和name) 33 | * UserModel::where('name','LIKE','%future%')->count();//统计name中包含future的字数 34 | * 35 | * 也可以实例化操作 $user=new UserModel; 36 | * $user->find(1); 37 | */ 38 | abstract class Model 39 | { 40 | protected $name = null; //数据库表 41 | protected $pk = 'id'; //主键 42 | protected $prefix = true; //前缀 43 | protected $fields = null; //字段过滤 44 | protected $dbname = null; //使用指定数据库 45 | 46 | private $_orm = null; //底层orm 47 | 48 | /** 49 | * 构造函数 50 | * 51 | * @param array $data 传入数据 52 | */ 53 | final public function __construct(array $data = null) 54 | { 55 | if (!$name = &$this->name) { 56 | $name = strtolower(preg_replace('/.(?=[A-Z])/', '$1_', substr(get_called_class(), 0, -5))); 57 | } 58 | $this->_orm = new Orm($name, $this->pk, $this->prefix); 59 | 60 | if ($this->fields) { 61 | //字段设置 62 | $this->_orm->field($this->fields); 63 | } 64 | if ($data) { 65 | //预填充数据 66 | $this->_orm->set($data); 67 | } 68 | if ($this->dbname) { 69 | //指定数据库 70 | $this->_orm->setDb($this->dbname); 71 | } 72 | } 73 | 74 | /** 75 | * 直接修改字段 76 | * 77 | * @param string $name 字段名 78 | * @param mixed $value 对应值 79 | */ 80 | public function __set($name, $value) 81 | { 82 | return $this->_orm->set($name, $value); 83 | } 84 | 85 | /** 86 | * 直接读取字段 87 | * 88 | * @param string $name 字段名 89 | * 90 | * @return mixed 对应的值 91 | */ 92 | public function __get($name) 93 | { 94 | return $this->_orm->get($name, false); 95 | } 96 | 97 | /** 98 | * 直接调用model的操作 99 | * 100 | * @param string $method 101 | * @param array $params 102 | */ 103 | public function __call($method, $params) 104 | { 105 | return call_user_func_array(array($this->getOrm(), $method), $params); 106 | } 107 | 108 | /** 109 | * 静态调用model的操作 110 | * 111 | * @param string $method 112 | * @param array $params 113 | */ 114 | public static function __callStatic($method, $params) 115 | { 116 | $model = new static(); 117 | return call_user_func_array(array($model->getOrm(), $method), $params); 118 | } 119 | 120 | /** 121 | * 获取模型实例 122 | * 123 | * @return Orm 返回对应ORM对象 124 | */ 125 | public function getOrm() 126 | { 127 | return $this->_orm; 128 | } 129 | 130 | public function toArray() 131 | { 132 | return $this->_orm->get(); 133 | } 134 | 135 | /** 136 | * 数据转成json 137 | * 138 | * @param int $type JSON_ENCODE type 【256 是JSON_UNESCAPED_UNICODE值】 139 | */ 140 | public function toJson($type = 256) 141 | { 142 | return json_encode($this->_orm->get(), $type); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /conf/app.ini: -------------------------------------------------------------------------------- 1 | [common];公用配置 2 | version = 3.2.0 3 | application.directory = APP_PATH "/app/" 4 | application.library.directory = APP_PATH "/library" 5 | application.library.namespace = "Storage,Service,Parse" 6 | ;application.modules = "index";模块 7 | application.dispatcher.defaultController = 'Index';默认控制器名称 8 | 9 | ;配置路径 10 | secret_path = APP_PATH "/conf/secret.common.ini" 11 | runtime = APP_PATH "/runtime/";运行时文件目录和日志,缓存存于此处,生产环境可以设置别处 12 | 13 | ;REST 响应设置 14 | rest.param = 'id' ;id形默认绑定参数 如 /User/123 =>绑定参数$id值未123 15 | rest.action = 'info' ;默认绑定控制器如 /User/123 =>绑定到 infoAction 16 | rest.none = '_404' ;请求action不存在时调用控制器默认_404Action 17 | rest.status = 'status' ;返回数据的状态码字段 18 | rest.data = 'data' ;返回数据的数据字段 19 | rest.error = -10 ;错误状态码 20 | rest.json = JSON_NUMERIC_CHECK|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES; 21 | 22 | ;上传通用配置 23 | upload.type = 'qiniu' 24 | upload.max = 52428800; //50 * 1024 * 1024,文件大小限制 25 | upload.exts = 'pdf,doc,docx,odt,rtf,wps,ppt,pptx,odp,dps,xls,xlsx,ods,csv,et,jpg,png,jpeg';后缀名限制 26 | 27 | ;cookie配置 28 | ;cookie采用AES加密,客户端无法读取cookie 29 | ;清空密钥可让所有客户端cookie失效 30 | cookie.path = '/' 31 | cookie.expire = 259200 ;3天 32 | cookie.domain = '';设置cookie有效域名 33 | cookie.secure = 0;强制https 34 | cookie.httponly = 1;禁止JS获取cookie 35 | 36 | ;过多尝试的限制设置 37 | ;防止恶意请求或者爆破 38 | try.times = 5 39 | try.expire = 18000 40 | 41 | ;正则验证 42 | regex.account = '/^\w{3,16}$/';账号格式 43 | regex.phone = '/^1[34578]\d{9}$/';//手机号 44 | regex.email = '/^[\w\.\-]{1,17}@[A-Za-z,0-9,\-,\.]{1,30}\.[A-Za-z]{2,6}$/' 45 | regex.name = '/^[\x{4E00}-\x{9FA5}]{2,5}(·[\x{4E00}-\x{9FA5}]{2,8})?$/u';姓名支持少数民族 46 | regex.zh = '/^[\x{4E00}-\x{9FA5}]*$/u' 47 | 48 | ;cors 跨域设置 49 | ;Access-Control-Allow-Origin设置 50 | ;['']空,不允许跨站请求 51 | ;['*']允许所有域名不限制来源; 52 | ;['http://www.xx.com']允许www.xx.com的跨域请求 53 | ;允许多个域名用[,]隔开 54 | ;开发环境和生产环境可以用不同配置 55 | cors.Access-Control-Allow-Origin = '*'; 56 | cors.Access-Control-Allow-Credentials = 'false';是否允许跨域使用cookie,'true'允许,false禁止 57 | cors.Access-Control-Allow-Headers = 'x-requested-with,accept,content-type,session-id,token' 58 | cors.Access-Control-Allow-Methods = 'GET,POST,PUT,DELETE' 59 | cors.Access-Control-Max-Age = 3628800 60 | 61 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 62 | [dev:common];本地(开发调试)配置 63 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 64 | application.bootstrap = APP_PATH "/library/Bootstrap/dev.php" 65 | application.library.namespace = "Storage,Service,Parse,Debug" 66 | ;开发模式, 格式化json数据输出,方便调试阅读 67 | rest.json = JSON_PRETTY_PRINT|JSON_NUMERIC_CHECK|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES; 68 | ;特殊配置path 69 | ;secret_path = APP_PATH '/conf/secret.local' 70 | 71 | ;断言设置仅在开发环境中使用 72 | ;生产环境中将关闭 73 | assert.active = 1 ;开启断言ASSERT_ACTIVE 74 | assert.warning = 0 ;断言触发发出警告 75 | assert.bail = 1 ;断言错误停止程序 76 | 77 | ;调试配置,仅在开发模式有效 78 | debug.error = 'dump';'dump':直接输出;'log':写入文件temp/error_log.txt; 79 | debug.listen = '*';选择监听日志类型"ALERT,DEBUG";"*"表示全部制定 80 | debug.tracer = 'LOG,HEADER';//统计记录输出 81 | debug.sql.output = 'LOG,HEADER';//sql统计输出 82 | debug.sql.result = 0;//是否在header中输出结果,默认只输出结果条数 83 | debug.sql.dumpdo = 1;数据库pdo查询出错时,是否直接输出参数 84 | 85 | ;缓存日志相关 86 | cache.type = 'file';支持[file],[memcached],[redis],[memcache] 87 | kv.type = 'file';支持[file],[redis],[kvdb] 88 | ;日志设置,支持系统日志(system), 89 | log.type = 'file';日志类型 90 | log.timezone = 'Asia/Shanghai';时区设置;如果不设置跟随系统设置 91 | log.path = APP_PATH "/runtime/log/";日志文件目录,仅对file类型有效 92 | log.allow = 'EMERGENCY,ALERT,CRITICAL,ERROR,WARN,NOTICE,INFO,DEBUG,SQL,TRACER';允许记录的级别可以自定义 93 | ;文件权限过滤默认是077(生产环境 默认禁止其他用户读写) 94 | umask = 0;0其他用户完全可读写,适合开发调试 95 | 96 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 97 | [product:common];线上(生产环境)配置 98 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 99 | ;application.bootstrap = APP_PATH "/library/Bootstrap/product.php" 100 | application.dispatcher.throwException = 1 101 | application.dispatcher.catchException = 1 102 | application.system.cache_config = 1 103 | 104 | ;特殊配置path 105 | secret_path = APP_PATH "/conf/secret.product.ini" 106 | 107 | ;日志相关 108 | log.allow = 'EMERGENCY,ALERT,CRITICAL,ERROR,WARN';设置日志级别过滤不必要的调试信息 109 | log.type = 'system';sae设置成SAE,主机生产环境建议使用system,大量写入信能更佳 110 | log.timezone = 'UTC';时区设置;如果不设置跟随系统设置 111 | 112 | cache.type = 'memcached';支持文件[file],内存缓存系统[memcached],memcache 包括sae缓存[memcache] 113 | kv.type = 'redis';支持文件[file],键值对系统[redis],sae KVDB存储[kvdb] 114 | -------------------------------------------------------------------------------- /library/Debug/SqlListener.php: -------------------------------------------------------------------------------- 1 | _data) { 37 | $this->flush('异常终止'); 38 | } 39 | } 40 | 41 | /** 42 | * 监视 数据库sql查询 43 | * 44 | * @param string $type 监听方式 45 | * @param bool $show_detail 是否输出 46 | */ 47 | public static function init($type, $show_detail = null) 48 | { 49 | $instance = static::$instance ?: (static::$instance = new static); 50 | static::$sql_output = explode(',', strtoupper($type)); 51 | Database::$before = array($instance, 'beforeQuery'); 52 | Database::$after = array($instance, 'afterQuery'); 53 | static::showDetail($show_detail); 54 | } 55 | 56 | /** 57 | *是否在header中显示 58 | * 59 | * @param bool $is_enable 显示详情 60 | */ 61 | public static function showDetail($is_enable) 62 | { 63 | static::$show_detail_in_header = $is_enable; 64 | } 65 | 66 | /** 67 | * 数据库查询监听回调 68 | * 69 | * @param string $sql 查询语句 70 | * @param array $param 参数列表 71 | * @param string $name 调用方法名称 72 | * 73 | * @todo 提前回收 74 | */ 75 | public function beforeQuery(&$sql, &$param, $name) 76 | { 77 | if ($data = &$this->_data) { 78 | $this->flush('异常终止'); 79 | } 80 | $data = array( 81 | 'T' => microtime(true),//time 82 | 'Q' => $sql,//sql query 83 | 'N' => $name, 84 | ); 85 | if ($param) { 86 | //param 87 | $data['P'] = $param; 88 | } 89 | $id = &static::$_sql_id; 90 | Debug::log("[SQL]({$id}) {$name} called\n${sql}}\n".json_encode($param, 256)); 91 | ++$id; 92 | } 93 | 94 | /** 95 | * 数据库查询监听回调 96 | * 97 | * @param pdo $db 数据库事例 98 | * @param mixed $result 查询结果 99 | * @param string $name 调用方法名称 100 | */ 101 | public function afterQuery(&$db, &$result, $name) 102 | { 103 | $data = &$this->_data; 104 | $data['T'] = (microtime(true) - $data['T']) * 1000; 105 | $error = null; 106 | if ($db->isOk() && $name !== 'error') { 107 | $data['R'] = $result; 108 | } else { 109 | $error = $result; 110 | $data['E'] = $result[0]; 111 | } 112 | $this->flush($error); 113 | } 114 | 115 | /** 116 | * 输出数据 117 | * 118 | * @param null|mixed $error 119 | */ 120 | protected function flush($error = null) 121 | { 122 | $id = static::$_sql_id; 123 | $data = &$this->_data; 124 | if (in_array('LOG', static::$sql_output)) { 125 | $message = "\r\n"; 126 | $message .= ' [SQL'.str_pad($id, 3, '0', STR_PAD_LEFT).'] '.$data['Q'].PHP_EOL; 127 | if (isset($data['P'])) { 128 | $message .= ' [PARAMS] '.json_encode($data['P'], 256).PHP_EOL; 129 | } 130 | if (isset($data['R'])) { 131 | $message .= ' [RESULT] '.json_encode($data['R'], 256).PHP_EOL; 132 | } 133 | if ($error) { 134 | $message .= '!![ERROR!] '.json_encode($error, 256).PHP_EOL; 135 | Debug::log("[SQL]({$id}) error!!!"); 136 | } 137 | $message .= " [INFORM] ${data['T']} ms (${data['N']})\n\r"; 138 | Debug::log($message, 'SQL'); 139 | } 140 | 141 | if (in_array('HEADER', static::$sql_output)) { 142 | unset($data['N']); 143 | if (!static::$show_detail_in_header && isset($data['R'])) { 144 | $data['R'] = count($data['R']); 145 | } 146 | Debug::header()->debugInfo("Sql-$id", $data); 147 | } 148 | $data = null; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /library/Debug.php: -------------------------------------------------------------------------------- 1 | \n(\s+)/m', '] => ', $data); 75 | } elseif (extension_loaded('xdebug')) { 76 | xdebug_var_dump($data); 77 | } else { 78 | ob_start(); 79 | var_dump($data); 80 | $data = ob_get_clean(); 81 | $data = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $data); 82 | $data = htmlspecialchars($data, ENT_NOQUOTES); 83 | echo '
', $data, '
'; 84 | } 85 | } 86 | 87 | /** 88 | * 测试代码运行资源消耗 89 | * 90 | * @param callable $function 运行函数 91 | * @param string $lable 显示标签,为空时返回数组,否则直接输出 92 | */ 93 | public static function run($function, $lable = '') 94 | { 95 | $m0 = memory_get_usage(); 96 | $mp = memory_get_peak_usage(); 97 | $t0 = microtime(true); 98 | $function(); 99 | $t = microtime(true) - $t0; 100 | $m = memory_get_peak_usage(); 101 | $m = $m > $mp ? ($m - $m0) : memory_get_usage() - $m0; 102 | 103 | if (!$lable) { 104 | return array('t' => $t, 'm' => $m); 105 | } 106 | 107 | $time_unit = array('s', 'ms', 'us', 'ns'); 108 | $mem_unit = array('B', 'KB', 'MB', 'GB', 'TB', 'PB'); 109 | 110 | $i = ($t == 0 || $t >= 1) ? 0 : ceil(log($t, 1 / 1000)); 111 | $t = round($t / pow(1 / 1000, $i), 2).' '.$time_unit[$i]; 112 | 113 | $i = ($m == 0) ? 0 : floor(log(abs($m), 1024)); 114 | $m = round($m / pow(1024, $i), 3).' '.$mem_unit[$i]; 115 | 116 | echo "$lable: 时间消耗[time] $t ; 内存预估[memory]: $m
"; 117 | } 118 | 119 | /** 120 | * header头中dump数据 121 | * 122 | * @param mixed $data 要输出的数据,可以多个 123 | */ 124 | public static function header() 125 | { 126 | $header = static::$header ?: (static::$header = Header::instance()); 127 | foreach (func_get_args() as $data) { 128 | $header->dump($data); 129 | } 130 | return $header; 131 | } 132 | 133 | /** 134 | * 获取Debug实体 135 | */ 136 | public static function instance() 137 | { 138 | return static::$_instance ?: (static::$_instance = new static); 139 | } 140 | 141 | /** 142 | * 监视 数据库sql查询 143 | * 144 | * @param string $type 数据库监听方式 145 | * @param bool $show_result 是否显示结果 146 | */ 147 | public function initSQL($type, $show_result) 148 | { 149 | SqlListener::init($type, $show_result); 150 | } 151 | 152 | /** 153 | * 监视 日志写入记录 154 | * 155 | * @param string $type 日志类型 156 | */ 157 | public function initLog($type) 158 | { 159 | LogListener::init($type); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /library/Kv.php: -------------------------------------------------------------------------------- 1 | &$v) { 46 | $result = $result && $handler->set($key, $v); 47 | } 48 | return $result; 49 | } 50 | return $handler->mset($name); 51 | } 52 | assert('is_scalar($value)||is_null($value)', '[Kv::set]只支持保存字符串'); 53 | return $handler->set($name, $value); 54 | } 55 | 56 | /** 57 | * 读取缓存数据 58 | * 59 | * @param string|array $name [缓存名称] 60 | * @param mixed $default 61 | * 62 | * @return mixed [获取值] 63 | */ 64 | public static function get($name, $default = false) 65 | { 66 | $handler = Kv::handler(); 67 | if (is_array($name)) { 68 | //数组获取 69 | assert('false===$default', '[Kv::get]数组获取时,不能设置默认值'); 70 | if ('file' === Kv::$type) { 71 | return $handler->mget($name); 72 | } 73 | return array_combine($name, $handler->mget($name)); 74 | } 75 | //单个值获取 76 | $result = $handler->get($name); 77 | return (false === $result) ? $default : $result; 78 | } 79 | 80 | /** 81 | * delete 别名 82 | * 83 | * @param string $name 键 84 | * @param int $time 时间(redis有效) 85 | */ 86 | public static function del($name, $time = 0) 87 | { 88 | return Kv::handler()->delete($name, $time); 89 | } 90 | 91 | /** 92 | * 清空存储 93 | */ 94 | public static function flush() 95 | { 96 | $handler = Kv::handler(); 97 | if ('redis' === Kv::$type) { 98 | return $handler->flushDB(); 99 | } elseif ('kvdb' === Kv::$type) { 100 | /*sae KVDB 逐个删除*/ 101 | while ($ret = $handler->pkrget('', 100)) { 102 | foreach ($ret as $k => &$v) { 103 | $handler->delete($k); 104 | } 105 | } 106 | return true; 107 | } 108 | return $handler->flush(); 109 | } 110 | 111 | /** 112 | * 清空存储 113 | */ 114 | public static function clear() 115 | { 116 | Kv::flush(); 117 | return Kv::$_handler; 118 | } 119 | 120 | /** 121 | * 获取处理方式 122 | * 123 | * @return Object 存储管理类 $_handler 124 | */ 125 | public static function handler() 126 | { 127 | if ($handler = &Kv::$_handler) { 128 | return $handler; 129 | } 130 | 131 | switch (Kv::$type = Config::get('kv.type')) { 132 | case 'redis': //redis 存储 133 | $config = Config::getSecret('redis'); 134 | $config = $config->get('kv') ?: $config->get('_'); 135 | 136 | $handler = new \Redis(); 137 | $handler->connect($config->get('host'), $config->get('port')); 138 | //密码验证 139 | ($value = $config->get('auth')) && $handler->auth($value); 140 | //限定数据库 141 | ($value = $config->get('db')) && $handler->select($value); 142 | break; 143 | 144 | case 'file': //文件存储 145 | $handler = new Storage\File(Config::get('runtime').'kv', false); 146 | break; 147 | 148 | case 'kvdb': //sae KVdb 149 | $handler = new \SaeKV(); 150 | if (!$handler->init()) { 151 | Logger::write('SAE KV cannot init'.$handler->errmsg(), 'ERROR'); 152 | } 153 | break; 154 | 155 | default: 156 | throw new Exception('未定义方式'.Kv::$type); 157 | } 158 | return $handler; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | YYF核心库 2 | ======== 3 | 4 | 详细说明参考文档 5 | 6 | * Aes.php : 高级对称加密管理类(Aes) 7 | * Cache.php : 缓存管理类(Cache) 8 | * Cipher.php : 安全加密类(Cipher)* 9 | * Config.php : 配置读取类(Config) 10 | * Cookie.php : 安全Cookie操作类(Cookie) 11 | * Db.php : 数据相关操作辅助封装(Db) 12 | * Debug.php : 调试类(Debug) 13 | * Input.php : 输入管理类(Input) 14 | * Kv.php : 键值对存储类(Kv) 15 | * Logger.php : 日志管理类(Logger) 16 | * Mail.php : 邮件管理类(Mail) 17 | * Model.php : 核心model类(Model) 18 | * Orm.php : 数据库查询核心封装 19 | * Random.php : 随机数生成器(Random) 20 | * Rest.php : REST核心controller类(Rest) 21 | * Rsa.php : Rsa 加解密类(Rsa) 22 | * Session.php : session操作管理(Session) 23 | * Validate.php : 格式验证(Validate) 24 | * Wecaht.php : 微信接口操作库(Wecaht) 25 | 26 | AES 27 | ----- 28 | 对称加密 29 | ```php 30 | /*基础编码和加密*/ 31 | Aes::encode($data, $key, $safe64 = false)#AES加密 32 | Aes::decode($data, $cipher, $safe64 = false)#AES解密 33 | 34 | Aes::base64Encode($str) #路径安全的Base64编码 35 | Aes::base64Decode($str) #安全base64解码 36 | 37 | ``` 38 | 39 | Cache 40 | ------ 41 | 缓存管理接口 42 | 本地缓存采用文件存储 43 | SAE采用memcache 44 | ```php 45 | Cache::set($name, $value, $expire = false) #设置缓存 46 | Cache::get($name) #读取缓存 47 | Cache::del($name) #删除缓存 48 | Cache::flush() #清空缓存 49 | ``` 50 | 51 | Config 52 | ------ 53 | 配置读取与管理 54 | ```php 55 | Config::get($name) #快速获取配置支持多级操作 56 | Config::getSecret($name,$key=null) #获取私有配置项 57 | ``` 58 | 59 | Cookie 60 | ------ 61 | 安全Cookie管理 62 | ```php 63 | Cookie::set($name, $value, $path='/',$expire = false) #设置cookie 64 | Cookie::get($name) #读取cookie 65 | Cookie::del($name) #删除cookie 66 | Cookie::flush() #清空cookie 67 | ``` 68 | 69 | Db 70 | ------ 71 | Db数据常用操作静态封装 72 | ```php 73 | Db::table('user')->where('id',2)->get('name'); 74 | Db::query('select * from user where id=:id',['id'=>2]); 75 | ``` 76 | 77 | Encrypt 78 | ----- 79 | 加密库 80 | ```php 81 | /*格式保留加密方法*/ 82 | Cipher::encryptEmail($email) #加密邮箱(密码从配置中读取) 83 | Cipher::decryptEmail($email) #解密邮箱 84 | Cipher::encryptPhone($phone,$salt,$id) #手机号加密 85 | Cipher::decryptPhone($phone,$salt,$id) #手机号解密 86 | ``` 87 | 88 | Input 89 | ----- 90 | 输入过滤库 91 | 92 | * 返回true(输入存在且有效)或者false, 93 | * 输入结果存在$export中 94 | * $filter为参数格式验证或者过滤方法支持:正则表达式,系统函数,php的filter_var常量,自定义的验证过滤函数 95 | ```php 96 | Input::post($name, &$export, $filter = null, $default = null) 97 | Input::get($name, &$export, $filter = null, $default = null) 98 | Input::put($name, &$export, $filter = null, $default = null) 99 | Input::I($name, &$export, $filter = null, $default = null) 100 | #其中I包含以上三种方式支持cookie和env,$name未指定方法时读取$_RESUQET 101 | ``` 102 | 103 | Kv 104 | ------ 105 | 键值对存储类 106 | 本地缓存暂时采用文件存储 107 | 支持字符串 108 | SAE采用memcache 109 | ```php 110 | Kv::set($name, $str) #设置 111 | Kv::get($name) #读取 112 | Kv::del($name) #删除 113 | Kv::flush() #清空 114 | ``` 115 | 116 | Logger 117 | ------- 118 | 日志记录类,兼容psr3日志接口 119 | $level 日志标签,配置中开启的则记录 120 | ``` 121 | Logger::write($msg, $level = 'NOTICE') 122 | Logger::error($msg, $level = 'NOTICE') 123 | ``` 124 | 125 | Mail 126 | --------- 127 | 邮件发送类 128 | 129 | ```php 130 | Mail::send($from, $to, $msg) #发送邮件 131 | Mail::sendVerify($email, $name, $link)#发送验证邮件 132 | ``` 133 | 134 | 135 | Orm 和 Model 136 | -------- 137 | 安全数据库操作 138 | 未非静态封装(这样比静态化封装的效率要高) 139 | 在**Model**则进行静态化处理 140 | ```php 141 | $User = new Orm('user');#创建model参数表名和主键 142 | $User->find(123);#查找id为123的用户 143 | $User->set('time',time())->save();#保存 144 | $User->Insert(['name'=>'test']);新建用户 145 | 146 | $Book = new Orm('book'); #创建book 147 | $Book->where('amount','>',10) #选择amount>10 148 | ->order('amount','DESC') #amount倒序 149 | ->select('id AS NO,name,amount'); #选出id作为NO,name和account 150 | $Book->where('amount',0)->delete(); #删除 151 | ``` 152 | 153 | Random 154 | ------- 155 | 快速随机数生成器 156 | ```php 157 | Random::n($n = 4) #生成随机number[0-9] 158 | Random::w($n = 8) #随机word[0-9|a-Z] 159 | Random::c($n=10) #生成随机char[a-Z] 160 | Random::code($n=6) #随机验证码验证码,去除0,1等不易辨识字符 161 | ``` 162 | 163 | Rest 164 | ------- 165 | REST控制器核心基类 166 | 167 | * 自动把GET,POST,PUT,DELETE 映射到 对应的Action 如get detail 映射到GET_detailAction() 168 | * 自动绑定参数id 169 | * 自动输出xml或者json格式数据 170 | 171 | `protected $response `响应的数据 172 | `protected response(status,info)`快速设置响应方法 173 | 174 | Rsa 175 | ------- 176 | Rsa 非对称加密库 177 | ```php 178 | Rsa::pubKey() #获取公钥 179 | Rsa::encode($s) #加密 180 | Rsa::decode($s) #解密 181 | ``` 182 | 183 | 184 | Safe 185 | -------- 186 | 安全防护 187 | 获取客户端IP和检查尝试次数 188 | ```php 189 | Safe::checkTry($key, [$maxTryTimes]) #检查计数器 190 | Safe::del($key) #删除计数器 191 | Safe::ip() #获取客户IP 192 | ``` 193 | 194 | Session 195 | -------- 196 | Session操作管理 197 | 支持数组 198 | ```php 199 | Session::set($name, $data) #设置 200 | Session::get($name) #读取 201 | Session::del($name) #删除 202 | Session::flush() #清空 203 | ``` -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | exclude('runtime/') 23 | ->in(__DIR__) 24 | ; 25 | 26 | $rules = array( 27 | '@PSR2' => true, //以PSR2为基准 28 | 'array_syntax' => array('syntax' => 'long'), //数组统一转array 29 | 'binary_operator_spaces' => array(//箭头对齐 30 | 'operators' => array( 31 | '=' => 'align_single_space_minimal', 32 | '==' => 'single_space', 33 | '===' => 'single_space', 34 | '=>' => 'align_single_space_minimal', 35 | ), 36 | ), 37 | 'blank_line_after_namespace' => true, 38 | 'combine_consecutive_unsets' => true, 39 | 'concat_space' => array('spacing' => 'none'), //.字符串连接空格 40 | 'cast_spaces' => true, //case 空格 41 | 'declare_equal_normalize' => true, 42 | 'elseif' => true, //else if 转elseif 43 | 'encoding' => true, //utf8 无bom头编码 44 | 'full_opening_tag' => true, 45 | 'hash_to_slash_comment' => true, //注释#转// 46 | 'heredoc_to_nowdoc' => true, //heredoc转nowdoc 47 | 'header_comment' => array( 48 | 'header' => $header, 49 | 'separate' => 'none', 50 | 'commentType' => 'PHPDoc', 51 | ), 52 | 'indentation_type' => true, 53 | 'line_ending' => true, 54 | 'lowercase_cast' => true, //case 小写 55 | 'lowercase_constants' => true, //常量小写 56 | 'method_argument_space' => true, //函数参数空格 57 | 'normalize_index_brace' => true, 58 | // 'no_alias_functions' => true, //替换别名函数 59 | 'no_blank_lines_after_class_opening' => true, 60 | 'no_empty_comment' => true, //删除空注释 61 | 'no_empty_statement' => true, //删除无用空语句 62 | 'no_extra_consecutive_blank_lines' => array( 63 | 'continue', 64 | 'extra', 65 | 'throw', 66 | 'use', 67 | 'parenthesis_brace_block', 68 | 'square_brace_block', 69 | 'curly_brace_block', 70 | ), 71 | 'no_leading_namespace_whitespace' => true, 72 | 'no_multiline_whitespace_around_double_arrow' => true, 73 | 'no_mixed_echo_print' => array('use' => 'echo'), //print转echo 74 | 'no_spaces_inside_parenthesis' => true, 75 | 'no_spaces_around_offset' => array('inside', 'outside'), 76 | 'no_singleline_whitespace_before_semicolons' => true, 77 | 'no_trailing_comma_in_list_call' => true, 78 | 'no_trailing_comma_in_singleline_array' => true, //单行数组去逗号 79 | 'no_trailing_whitespace_in_comment' => true, //去掉注释中尾部空格 80 | 'no_whitespace_in_blank_line' => true, //删除空白行中多余的空格 81 | 'no_whitespace_before_comma_in_array' => true, //数组去空格 82 | 83 | // 'no_unreachable_default_argument_value' => true, //去掉不可达的默认参数 84 | 'no_unused_imports' => true, //删除无用引入 85 | 'no_useless_else' => true, //删除无用else 86 | 'no_useless_return' => true, //删除无用return 87 | 'ordered_class_elements' => true, 88 | 'ordered_imports' => true, 89 | 'phpdoc_add_missing_param_annotation' => true, //添加参数说明 90 | 'phpdoc_align' => true, 91 | 'phpdoc_indent' => true, 92 | 'phpdoc_order' => true, 93 | 'phpdoc_scalar' => true, 94 | 'phpdoc_separation' => true, 95 | 'phpdoc_trim' => true, 96 | 'pre_increment' => true, //++前置 97 | 'single_blank_line_before_namespace' => true, 98 | 'single_blank_line_at_eof' => true, //文件结束后换行 99 | 'single_import_per_statement' => true, 100 | 'single_quote' => true, //转单引号 101 | 'short_scalar_cast' => true, //短名称integer=>int 102 | 'ternary_operator_spaces' => true, //三元操作符空格 103 | 'trim_array_spaces' => true, //数组去空格 104 | 'unary_operator_spaces' => true, 105 | 'visibility_required' => true, 106 | ); 107 | 108 | return PhpCsFixer\Config::create() 109 | ->setRules($rules) 110 | ->setFinder($finder) 111 | ; 112 | -------------------------------------------------------------------------------- /library/Cookie.php: -------------------------------------------------------------------------------- 1 | toArray(); 28 | self::$_config['key'] = self::key(); 29 | } 30 | 31 | /** 32 | * 设置cookie 33 | * 34 | * @param string $name cookie名称 35 | * @param mixed $value cookie值 36 | * @param string $path [存取路径] 37 | * @param int $expire 有效时间 38 | * @param null|string $domain 域名 39 | */ 40 | public static function set($name, $value, $path = '', $expire = null, $domain = null) 41 | { 42 | if ($value = self::encode($value)) { 43 | $path = $path ?: self::config('path'); 44 | 45 | if (!$expire) { 46 | $expire = ($expire === 0) ? null : self::config('expire'); 47 | } 48 | $expire = $expire ? $_SERVER['REQUEST_TIME'] + $expire : null; 49 | if ($domain === null) { 50 | $domain = self::config('domain'); 51 | } 52 | return setrawcookie($name, $value, $expire, $path, $domain, self::config('secure'), self::config('httponly')); 53 | } 54 | } 55 | 56 | /** 57 | * 获取cookie 58 | * 59 | * @param string $name cookie名称 60 | * @param mixed $default 默认值 61 | * 62 | * @return mixed string|array 63 | */ 64 | public static function get($name, $default = null) 65 | { 66 | if (isset($_COOKIE[$name]) && $data = $_COOKIE[$name]) { 67 | return self::decode($data); 68 | } 69 | return $default; 70 | } 71 | 72 | /** 73 | * 删除 74 | * 75 | * @param string $name cookie名称 76 | * @param null|string $path 路径 77 | * @param null|string $domain 域名 78 | */ 79 | public static function del($name, $path = null, $domain = null) 80 | { 81 | if (isset($_COOKIE[$name])) { 82 | unset($_COOKIE[$name]); 83 | $path = $path ?: self::config('path'); 84 | $domain = $domain === null ? self::config('domain') : $domain; 85 | setrawcookie($name, '', 100, $path, $domain, self::config('secure'), self::config('httponly')); 86 | } 87 | } 88 | 89 | /** 90 | * 清空cookie 91 | */ 92 | public static function flush() 93 | { 94 | if (empty($_COOKIE)) { 95 | return null; 96 | } 97 | /*逐个删除*/ 98 | foreach ($_COOKIE as $key => $val) { 99 | self::del($key); 100 | } 101 | } 102 | 103 | /** 104 | * 获取加密密钥 105 | * 106 | * @return string 密钥 107 | */ 108 | public static function key() 109 | { 110 | if (!$key = Kv::get('COOKIE_aes_key')) { 111 | /*重新生成加密密钥*/ 112 | $key = Random::word(32); 113 | Kv::set('COOKIE_aes_key', $key); 114 | } 115 | return $key; 116 | } 117 | 118 | /** 119 | * 设置cookie配置 120 | * 121 | * @param string $key 配置变量名 122 | * @param mixed $value 配置值 123 | * 124 | * @return Cookie self 设置 125 | */ 126 | public static function setConfig($key, $value) 127 | { 128 | //修改配置参数 129 | $config[$key] = $value; 130 | return self; 131 | } 132 | 133 | /** 134 | * Cookie数据加密编码 135 | * 136 | * @param mixed $data 数据 137 | * 138 | * @return string 139 | */ 140 | private static function encode($data) 141 | { 142 | return Aes::encrypt(json_encode($data), self::config('key'), true); 143 | } 144 | 145 | /** 146 | * Cookie数据解密 147 | * 148 | * @param string $data 待解密字符串 149 | * 150 | * @return mixed 151 | */ 152 | private static function decode($data) 153 | { 154 | if ($data = Aes::decrypt($data, self::config('key'), true)) { 155 | return @json_decode($data, true); 156 | } 157 | } 158 | 159 | /** 160 | * 获取cookie配置 161 | * 162 | * @param string $name 配置变量名 163 | * 164 | * @return mixed 配置项 165 | */ 166 | private static function config($name) 167 | { 168 | if (!$config = self::$_config) { 169 | $config = Config::get('cookie')->toArray(); 170 | 171 | $config['key'] = self::key(); 172 | self::$_config = $config; 173 | } 174 | return isset($config[$name]) ? $config[$name] : null; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /library/Storage/File.php: -------------------------------------------------------------------------------- 1 | _dir = $dir.DIRECTORY_SEPARATOR; 45 | $this->_serialized = $serialized; 46 | } 47 | 48 | /** 49 | * 保存数据 50 | * 51 | * @param string $name 键 52 | * @param mxied $value 值 53 | * @param int $expire [有效时间] 54 | */ 55 | public function set($name, $value, $expire = 0) 56 | { 57 | if ($this->_serialized) { 58 | //序列化写入文件 59 | $expire = $expire > 0 ? $_SERVER['REQUEST_TIME'] + $expire : 0; 60 | $value = serialize(array($value, $expire)); 61 | } 62 | assert('is_scalar($value)||is_null($value)', '保存的数据应该是基本类型'); 63 | $filename = $this->_dir.'.'.$name.'.php'; 64 | return file_put_contents($filename, ' 0; 65 | } 66 | 67 | /** 68 | * 批量保存数据 69 | * 70 | * @param array $data [数据(键值对)] 71 | * @param int $expire [有效时间] 72 | */ 73 | public function mset(array $data, $expire = 0) 74 | { 75 | $dir = $this->_dir; 76 | $result = true; 77 | if ($this->_serialized) { 78 | //序列化写入文件 79 | $expire = $expire > 0 ? $_SERVER['REQUEST_TIME'] + $expire : 0; 80 | foreach ($data as $key => &$value) { 81 | $result = $result && file_put_contents($dir.'.'.$key.'.php', ' &$value) { 85 | $result = $result && file_put_contents($dir.'.'.$key.'.php', '_dir.'.'.$name.'.php'; 101 | if (is_file($filename)) { 102 | $content = substr(file_get_contents($filename), 12); 103 | } else { 104 | return false; /*不存在返回null*/ 105 | } 106 | 107 | if ($this->_serialized) { 108 | /*反序列化的文件*/ 109 | $content = unserialize($content); 110 | if ($content[1] && $_SERVER['REQUEST_TIME'] > $content[1]) { 111 | @unlink($filename); 112 | return false; 113 | } 114 | return $content[0]; 115 | } 116 | return $content; 117 | } 118 | 119 | /** 120 | * 批量读取数据 121 | * 122 | * @param array $data [数据(键值对)] 123 | */ 124 | public function mget(array $data) 125 | { 126 | $result = array(); 127 | foreach ($data as &$k) { 128 | $result[$k] = $this->get($k); 129 | } 130 | unset($k); 131 | return $result; 132 | } 133 | 134 | /** 135 | * 删除数据 136 | * 137 | * @param string $name 数据文件名称 138 | * 139 | * @return bool 操作结果 140 | */ 141 | public function delete($name) 142 | { 143 | $filename = $this->_dir.'.'.$name.'.php'; 144 | return is_file($filename) ? unlink($filename) : false; 145 | } 146 | 147 | /** 148 | * 删除全部缓存数据 149 | * 150 | * @return File 返回自身 151 | */ 152 | public function flush() 153 | { 154 | File::cleanDir($this->_dir); 155 | return $this; 156 | } 157 | 158 | /** 159 | * 清空目录 160 | * 161 | * @param string $dir [存储目录] 162 | */ 163 | public static function cleanDir($dir) 164 | { 165 | /*获取全部文件*/ 166 | if (!is_dir($dir)) { 167 | return true; 168 | } 169 | $files = scandir($dir); 170 | unset($files[0], $files[1]); 171 | 172 | $result = 0; 173 | foreach ($files as &$f) { 174 | $result += @unlink($dir.$f); 175 | } 176 | unset($files); 177 | return $result; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /conf/secret.common.ini: -------------------------------------------------------------------------------- 1 | [encrypt] 2 | ;加密密钥32位字符 3 | key_email = '123e#Pe65qg2ARw9asf3*KelRM74j42a';邮箱加密密钥 4 | key_phone_mid = 'asdyfadusihahke123&*@asdas123131';手机中间号码加密密钥 5 | key_phone_end = 'shjdkadadlaksddakl213adsjjasjadf';尾号加密密钥 6 | 7 | [database] 8 | ;数据库配置 9 | prefix = '';数据库表前缀 10 | 11 | ;sql执行出错是否抛出异常,生产环境可以关闭 12 | 13 | ;[默认数据库参数配置] 14 | ;PDO::ATTR_* 15 | ;可以在据库中配置options会覆盖此设置 16 | ; ATTATTR_AUTOCOMMIT 自动提交查询结果 17 | ;options.0 = 1 ;设置0或者1 18 | ; ATTR_TIMEOUT:超时时间 19 | options.2 = 3;单位s 20 | ; ATTR_ERRMODE执行出错显示: 21 | options.3 = 2 ;枚举:ERRMODE_SILENT[0]; ERRMODE_WARNING[1]; ERRMODE_EXCEPTION[2]; 22 | ; ATTR_CASE 返回字段名大小写转换 23 | options.8 = 0 ;枚举: CASE_NATURAL[0]不处理;CASE_UPPER[1]强制大写;CASE_LOWER[2]强制小写 24 | ; ATTR_ORACLE_NULLS 返回结果空值转换 25 | options.11 = 0 ;枚举:NULL_NATURAL[0]不处理;NULL_EMPTY_STRING[1]''->NULL;NULL_TO_STRING[2]NULL->'' 26 | ; ATTR_STRINGIFY_FETCHES 是否转成字符串 27 | options.17 = 0 ;bool: 是否将数字转成字符串[部分数据库不支持] 28 | ; ATTR_DEFAULT_FETCH_MODE 默认fetch模式 29 | options.19 = 2 ;枚举: 关联数组FETCH_ASSOC[2];索引数组FETCH_NUM[3]; 索引和关联数组FETCH_BOTH[4]; 对象数组FETCH_OBJ[5] 30 | ; ATTR_EMULATE_PREPARES [需要数据库驱动支持] 31 | options.20 = 0 ;bool: 是否使用模拟预处理:防止sql注入务必设为0! 32 | 33 | ;数据连接统一使用DSN设置 34 | ;可以同时使用不同类型的数据库和主从数据库分离 35 | ;MySQL DSN 设置如【'mysql:host=localhost;port=3306;dbname=yyf;charset=utf8'】或【mysql:unix_socket=/tmp/mysql.sock;dbname=testdb】 36 | ; 详细参考http://php.net/manual/zh/ref.pdo-mysql.connection.php#refsect1-ref.pdo-mysql.connection-description 37 | ;SQLite DSN 设置如 ["sqlite:filepath"] 38 | ; 详细参考http://php.net/manual/zh/ref.pdo-sqlite.connection.php#refsect1-ref.pdo-sqlite.connection-description 39 | ;其他类型数据库DSN设置参看http://php.net/manual/zh/pdo.drivers.php 40 | ;每个数据库dsn必须设置,username(用户名),password(密码)可选 41 | 42 | ;【默认数据库】[_](主数据库)必须设置 43 | db._.dsn = 'mysql:host=127.0.0.1;port=3306;dbname=yyf;charset=utf8' 44 | db._.username = 'root' 45 | db._.password = '' 46 | db._.options.1002 = "SET NAMES utf8"; MYSQL_ATTR_INIT_COMMAND 47 | 48 | ;【读操作度数据库】[_read](从数据库) 49 | ; 可选设置此数据库后,读操作优先使用此据库 50 | ;db._read.dsn = "sqlite:/temp/databases/mydb.sq3"; 以sqlite配置为例 51 | ;db._read.username = 'username' 52 | ;db._read.password = 'password' 53 | 54 | ;【强制写操作数据库】[_write] 55 | ; 设置此数据库后写model操作会优先使用此数据库 以SAE配置为例[SAE 写数据】 56 | ;db._write.dsn = "mysql:host=" SAE_MYSQL_HOST_M ";port=" SAE_MYSQL_PORT ";dbname=" SAE_MYSQL_DB ";charset=utf8" 57 | ;db._write.username = SAE_MYSQL_USER 58 | ;db._write.password = SAE_MYSQL_PASS 59 | 60 | ;以上三个数据库配置会被自动识别和切换 61 | ;可以添加 更多数据库配置,在程序中用【配置名称】进行切换 62 | ;db.[配置名称].dsn = 63 | ;db.[配置名称].username = 64 | ;db.[配置名称].password = 65 | 66 | db.test.dsn = "sqlite:" APP_PATH "/runtime/yyf.db"; 以sqlite配置为例 67 | 68 | [redis] 69 | ;Redis 存储连接配置 依赖redis扩展 70 | 71 | ;_.host = [必填]主机地址 72 | ;_.port = 端口号如6379,不填是用Redis默认 73 | ;_.auth = password redis连接密码如果配置了 74 | ;_.db = 数据库id,制定redis数据库,默认不用 75 | 76 | ;Kv键值对存储Redis服务设置 77 | kv.host = 127.0.0.1 78 | cache.host = 127.0.0.1 79 | 80 | 81 | [memcached] 82 | ;Memcached 内存对象缓存系统, 依赖memcached扩展 83 | 84 | ;_.host = [必填]memcached服务器地址 85 | ;_.port = [必填]端口号如11211 86 | ;_.mcid = 长连接id,如果使用长连接方式 87 | 88 | ;Cache缓存 mecached连接配置 89 | cache.host = 127.0.0.1 90 | cache.port = 11211 91 | 92 | [memcache] 93 | ;Memcache 内存对象缓存, 依赖memcache扩展[支持sae memcache缓存] 94 | 95 | ;_.host = [必填,sae时设置为access key常量或者'']memcache服务器地址 96 | ;_.port = [必填,sae不填]端口号如11211 97 | ;_.time 98 | ;Cache缓存 mecache连接配置 99 | cache.host = 127.0.0.1 100 | cache.port = 11211 101 | 102 | [mail] 103 | ;系统邮箱相关配置 104 | server.smtp = 'smtp.yunyin.org' 105 | server.port = 465 106 | server.secure = 'ssl' 107 | verify.name = '云小印[yyf]' 108 | verify.email = 'yyf@mail.yunyin.org' 109 | verify.pwd = '邮箱密码' 110 | verify.baseuri = 'https://yyf.yunyin.org/?' 111 | 112 | [sms] 113 | ;短信接口相关配置 114 | account = '' 115 | appid = '' 116 | token = '' 117 | 118 | [qiniu] 119 | ;七牛上传配置 120 | secretkey = '8nfzvI5ontAmER33n40O6U2LGCj1wVHUcEAlmByS' 121 | accesskey = 'EfZW44Kjkjp8T92qMqLkkt4awRTRP1ucd-n6wUhn' 122 | expire = 86400;超时时间 123 | ;bucket配置 124 | file = 'uploadbucket' 125 | task = 'taskbucket' 126 | share = 'sharebucket' 127 | ;bucket对应域名 128 | domain.uploadbucket = 'http://1.qiniudn.com/' 129 | domain.taskbucket = 'http://2.qiniudn.com/' 130 | domain.sharebucket = 'http://3.qiniudn.com/' 131 | 132 | [wechat] 133 | ;微信配置 134 | appid = '微信开发appid' ;微信开发应用ID 135 | secret = '微信开发secret' ;微信开发APPKEY 136 | state = 'cookie' ;回调自动验证方式 'cookie' 或这 'session',为空['']不进行自动验证 137 | ;微信js配置额外设置,如 debug,jsApiList 138 | ;取消下面一行注释可开启微信JSSDK调试模式 139 | ;js.debug = 1;开启调试 140 | ;js.jsApiList.0 = onMenuShareTimeline;分享权限,从0开始递增 141 | ;js.jsApiList.1 = scanQRCode;扫描权限 142 | ;微信跳转回调设置,可以指向同一个登录入口 143 | redirect_base = 'http://your.site/callback' ;微信静默授权回调 snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid) 144 | redirect_userinfo = 'http://your.site/callback' ;微信内授权回调URL ;snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地) 145 | redirect_login = 'http://your.site/callback' ;微信网页登录回调URL 开发者接口 ;snsapi_login (网页端登录) 146 | -------------------------------------------------------------------------------- /library/Input.php: -------------------------------------------------------------------------------- 1 | =')) { //for php7 29 | //判断环境 30 | if (-1 == ini_get('zend.assertions')) { 31 | $msg = <<<'EOF' 32 | 调试环境,请开启php7的断言,以便更早发现问题!
33 | (在php.ini 中的设置 zend.assertions = 1 开启断言【推荐】; 34 | 或者在 conf/app.ini 中设置 assert.active = 0 关闭此警告【不推荐】。)
35 | In development environment, please open assertion for php7 to debug !
36 | (set 'zend.assertions = 1' in [php.ini][recommended]; 37 | or set 'assert.active = 0' in [conf/app.ini] to ignore this [not recommender].) 38 | EOF; 39 | exit($msg); 40 | } 41 | //PHP7配置 42 | ini_set('zend.assertions', 1);//开启断言 43 | ini_set('assert.exception', 0);//关闭断言异常 44 | } elseif (version_compare(PHP_VERSION, '5.4.8', '<')) { 45 | //低版本(php5.3)断言 46 | set_error_handler(array(__CLASS__, 'php53'), E_WARNING); 47 | } 48 | 49 | assert_options(ASSERT_ACTIVE, true); 50 | //断言错误回调 51 | assert_options(ASSERT_CALLBACK, array(__CLASS__, 'callback')); 52 | } else { 53 | assert_options(ASSERT_ACTIVE, false); 54 | } 55 | 56 | assert_options(ASSERT_QUIET_EVAL, false);//关闭在断言表达式求值时禁用error_reporting 57 | assert_options(ASSERT_WARNING, $warning);//为每个失败的断言产生一个 PHP 警告(warning) 58 | assert_options(ASSERT_BAIL, $bail);//在断言失败时中止执行 59 | } 60 | 61 | public function __destory() 62 | { 63 | ob_flush(); 64 | } 65 | 66 | /** 67 | * 初始化断言拦截 68 | */ 69 | public static function init(array $config) 70 | { 71 | if (!static::$instance) { 72 | $active = isset($config['active']) ? $config['active'] : true; 73 | $warning = isset($config['warning']) ? $config['warning'] : false; 74 | $bail = isset($config['bail']) ? $config['bail'] : true; 75 | static::$instance = new static($active, $warning, $bail); 76 | } 77 | } 78 | 79 | /** 80 | * 兼容PHP5.3的assert(只支持一个参数) 81 | * 参数表$number, $message, $file, $line, $context 82 | * 为了不影响恢复断言环境,此方法内避免使用任何局部参数或变量 83 | */ 84 | public static function php53() 85 | { 86 | if (func_get_arg(1) !== 'assert() expects exactly 1 parameter, 2 given') { 87 | return false; //非断言参数错误继续传递 88 | } 89 | static::$assertion = debug_backtrace(false); 90 | static::$assertion = static::$assertion[1];//从trace栈获取assert参数内容 91 | extract(func_get_arg(4));//恢复assert上下文环境 92 | assert(static::$assertion['args'][0]);//运行断言 93 | /*清除断言数据*/ 94 | static::$assertion = null; 95 | return true; 96 | } 97 | 98 | /** 99 | * Assertion Handler 100 | * 断言错误回调 101 | * 102 | * @param string $file 103 | * @param int $line 104 | * @param string $code 105 | * @param string $message 106 | */ 107 | public static function callback($file, $line, $code, $message = null) 108 | { 109 | header('Content-type: text/html; charset=utf-8', true, 500); 110 | $trace = debug_backtrace(false); 111 | array_shift($trace); 112 | array_shift($trace); 113 | array_pop($trace); 114 | 115 | if ($assertion = &static::$assertion) { 116 | //php53 assert 双参数回调 117 | $file = $assertion['file']; 118 | $line = $assertion['line']; 119 | $code = $assertion['args'][0]; 120 | if (isset($assertion['args'][1])) { 121 | $message = $assertion['args'][1]; 122 | } 123 | array_shift($trace); 124 | } 125 | 126 | echo "\n断言错误触发:\n
", 127 | '', $message, "
\n", 128 | "触发位置$file 第$line 行:
\n 判断逻辑 $code \n
", 129 | '(这里通常不是错误位置,是错误的调用方式或者参数引起的,请仔细检查)', 130 | "
\n(tips:断言错误是在正常逻辑中不应出现的情况,生产环境关闭系统断言提高性能)\n
"; 131 | 132 | echo '

函数调用栈【call stack】

    '; 133 | foreach (array_reverse($trace) as $i => $t) { 134 | echo '
  1. ';
    135 |             if ($arg = $t['args']) {
    136 |                 $arg = '('.array_reduce($arg, function ($s, &$v) {
    137 |                     return $s.print_r($v, true).',';
    138 |                 });
    139 |                 $arg[strlen($arg) - 1] = ')';
    140 |             } else {
    141 |                 $arg = '()';
    142 |             }
    143 |             if (isset($t['class'])) {
    144 |                 echo "${t['class']}${t['type']}${t['function']}$arg;\n";
    145 |             } else {
    146 |                 echo "[${t['file']}${t['line']}]${t['function']}$arg;\n";
    147 |             }
    148 |             echo '
  2. '; 149 | } 150 | echo '
'; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/library/CacheTest.php: -------------------------------------------------------------------------------- 1 | array('_test_key_n',1234577), 33 | 'bool Value' => array('_test_key_bool',true,2), 34 | 'string_value' => array('_test_key_s','test_value',0), 35 | 'array Value' => array('_test_key_array',array('a',2,'c' => 3),60), 36 | ); 37 | } 38 | 39 | //多组测试数据 40 | public function multiProvider() 41 | { 42 | return array( 43 | array( 44 | array('_mkey1_s' => 'test_value','_mkey1_i' => 10),0 45 | ), 46 | array( 47 | array('_mkey3_a' => array('test_value2',122),'_mkey3_bool' => true),2 48 | ), 49 | array( 50 | array('_mkey2_s' => 'test_value2','_mkey2bool' => true), 51 | ), 52 | ); 53 | } 54 | 55 | /** 56 | * @dataProvider singleProvider 57 | * 58 | * @param mixed $key 59 | * @param mixed $value 60 | * @param mixed $exp 61 | */ 62 | public function testSet($key, $value, $exp = 0) 63 | { 64 | $this->assertTrue(Cache::set($key, $value, $exp)); 65 | } 66 | 67 | /** 68 | * @dataProvider multiProvider 69 | * 70 | * @param mixed $data 71 | * @param mixed $exp 72 | */ 73 | public function testMSet($data, $exp = 0) 74 | { 75 | $this->assertTrue(call_user_func_array(array('Cache', 'set'), func_get_args())); 76 | } 77 | 78 | /** 79 | * @dataProvider singleProvider 80 | * @depends testSet 81 | * 82 | * @param string $key 83 | * @param mixed $value 84 | * @param int $exp 85 | */ 86 | public function testGet($key, $value, $exp = 0) 87 | { 88 | $this->assertSame($value, Cache::get($key)); 89 | } 90 | 91 | /** 92 | * @dataProvider multiProvider 93 | * @depends testMSet 94 | * 95 | * @param array $key 96 | * @param mixed $value 97 | * @param mixed $data 98 | * @param mixed $exp 99 | */ 100 | public function testMultiGet($data, $exp = 0) 101 | { 102 | $keys = array_keys($data); 103 | $this->assertSame($data, Cache::get($keys)); 104 | } 105 | 106 | /** 107 | * 测试过期失效 108 | * 109 | * @dataProvider singleProvider 110 | * @depends testGet 111 | * @depends testMultiGet 112 | * 113 | * @param string $key 114 | * @param mixed $value 115 | * @param int $exp 116 | */ 117 | public function testExpire($key, $value, $exp = 0) 118 | { 119 | if ($exp > 0 && $exp < 10) { 120 | if ($this->app->getConfig()->get('cache.type') === 'file') { 121 | $_SERVER['REQUEST_TIME'] += $exp + 0.1; 122 | $this->assertFalse(Cache::get($key), $key); 123 | } else { 124 | static::sleep($exp); 125 | $this->assertFalse(Cache::get($key), $key); 126 | } 127 | } else { 128 | $this->assertSame($value, Cache::get($key), $key.'不应该过期'); 129 | } 130 | } 131 | 132 | /** 133 | *测试过期失效 134 | * 135 | * @dataProvider multiProvider 136 | * @depends testGet 137 | * @depends testMultiGet 138 | * 139 | * @param array $key 140 | * @param int $exp 141 | * @param mixed $data 142 | */ 143 | public function testMultiExpire($data, $exp = 0) 144 | { 145 | $keys = array_keys($data); 146 | if ($exp > 0 && $exp < 10) { 147 | $data = array_fill_keys($keys, false); 148 | if ($this->app->getConfig()->get('cache.type') === 'file') { 149 | //文件存储 150 | $_SERVER['REQUEST_TIME'] += $exp + 0.1; 151 | } else { 152 | //内存存储 153 | static::sleep($exp); 154 | } 155 | } 156 | $this->assertSame($data, Cache::get($keys)); 157 | } 158 | 159 | /** 160 | * @dataProvider singleProvider 161 | * @depends testGet 162 | * 163 | * @param string $key 164 | * @param mixed $value 165 | * @param int $exp 166 | */ 167 | public function testDel($key, $value, $exp = 0) 168 | { 169 | $result = Cache::del($key); 170 | if ($exp == 0) { 171 | $this->assertNotFalse($result); 172 | } 173 | $this->assertFalse(Cache::get($key)); 174 | } 175 | 176 | /** 177 | * @dataProvider singleProvider 178 | * @depends testDel 179 | * 180 | * @param string $key 181 | * @param mixed $value 182 | * @param int $exp 183 | */ 184 | public function testFlush($key, $value, $exp = 0) 185 | { 186 | $key = uniqid('_t_cache_'); 187 | $this->assertTrue(Cache::handler()->set($key, 'test value')); 188 | $this->assertNotFalse(Cache::flush()); 189 | $this->assertFalse(Cache::get($key)); 190 | } 191 | 192 | protected static function sleep($time) 193 | { 194 | // usleep($time * 1000000 + 100000); 195 | $end_time = $_SERVER['REQUEST_TIME_FLOAT'] + $time; 196 | if ($end_time > microtime(true)) { 197 | time_sleep_until($end_time); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /library/Debug/Tracer.php: -------------------------------------------------------------------------------- 1 | output = explode(',', strtoupper($type)); 38 | $this->time['request'] = isset($_SERVER['REQUEST_TIME_FLOAT']) ? 39 | $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; 40 | $this->time['start'] = YYF_INIT_TIME; 41 | $this->mem['startm'] = YYF_INIT_MEM / 1024; //启动内存,包括调试插件占用 42 | $this->mem['start'] = memory_get_usage() / 1024; //启动内存,包括调试插件占用 43 | } 44 | 45 | public function __destruct() 46 | { 47 | $files = get_included_files(); 48 | array_walk($files, function (&$file) { 49 | if (strpos($file, APP_PATH) === 0) { 50 | $file = ltrim(substr($file, strlen(APP_PATH)), '\\/'); 51 | } 52 | }); 53 | 54 | $mem = &$this->mem; 55 | $time = &$this->time; 56 | 57 | $mem['max'] = memory_get_peak_usage() / 1024; 58 | $time['end'] = microtime(true); 59 | 60 | foreach ($time as &$t) { 61 | $t *= 1000; 62 | } 63 | 64 | /*传递回调*/ 65 | foreach (static::$observer as &$callback) { 66 | call_user_func($callback, $mem, $time, $files); 67 | } 68 | $this->dispaly($mem, $time, $files); 69 | ob_get_length() && ob_end_flush(); 70 | } 71 | 72 | public static function instance($type = null) 73 | { 74 | return static::$_instance ?: (static::$_instance = new static($type)); 75 | } 76 | 77 | //在路由之前触发,这个是7个事件中, 最早的一个. 但是一些全局自定的工作, 还是应该放在Bootstrap中去完成 78 | public function routerStartup(Request $request, Response $response) 79 | { 80 | $this->time['routerstart'] = microtime(true); 81 | } 82 | 83 | //分发循环开始之前被触发 84 | public function dispatchLoopStartup(Request $request, Response $response) 85 | { 86 | $this->time['dispatchstart'] = microtime(true); 87 | $this->mem['dispatchm'] = memory_get_peak_usage() / 1024; 88 | $this->mem['dispatch'] = memory_get_usage() / 1024; 89 | } 90 | 91 | //分发结束之后触发,此时动作已经执行结束, 视图也已经渲染完成. 和preDispatch类似, 此事件也可能触发多次 92 | public function postDispatch(Request $request, Response $response) 93 | { 94 | $this->time['postdispatch'] = microtime(true); 95 | } 96 | 97 | /** 98 | * 添加统计观察者 99 | * 统计完成时输传递调结果 100 | * 101 | * @param callable $callback 回调 102 | */ 103 | public static function addObserver($callback) 104 | { 105 | static::$observer[] = $callback; 106 | } 107 | 108 | /** 109 | * dispaly 资源统计监听回调 110 | * 111 | * @param array $mem 内存消耗记录 112 | * @param array $time 时间消耗记录 113 | * @param array $files 文件加载记录 114 | */ 115 | public function dispaly(array $mem, array $time, array $files) 116 | { 117 | if (!isset($time['dispatchstart'])) { 118 | return false; 119 | } 120 | 121 | if (in_array('LOG', $this->output)) { 122 | $header = (isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : getenv('REQUEST_METHOD')) 123 | .' 资源消耗统计:'.PHP_EOL; 124 | 125 | $file_msg = ltrim(strstr(print_r($files, true), '('), '('); 126 | $file_msg = str_replace(' ', ' ', $file_msg); 127 | $file_msg = '[文件加载顺序] (总计:'.count($files).')'.strstr($file_msg, ')', true); 128 | 129 | $mem_msg = PHP_EOL.'[内存消耗估计] (峰值: '.$mem['max'].' KB)'; 130 | $mem_msg .= PHP_EOL.' 启动消耗内存: '.$mem['startm'].' KB'; 131 | $mem_msg .= PHP_EOL.' 路由消耗内存: '.($mem['dispatchm'] - $mem['start']).' KB'; 132 | $mem_msg .= PHP_EOL.' 处理消耗内存: '.($mem['max'] - $mem['dispatch']).' KB'; 133 | $mem_msg .= PHP_EOL.' 最大消耗内存: '.$mem['max'].' KB'; 134 | 135 | $time_msg = '[时间消耗统计] (总计: '.($time['end'] - $time['request']).' ms)'; 136 | $time_msg .= PHP_EOL.' 定位启动耗时:'.($time['start'] - $time['request']).' ms'; 137 | $time_msg .= PHP_EOL.' 加载插件耗时:'.($time['routerstart'] - $time['start']).' ms'; 138 | $time_msg .= PHP_EOL.' 路由分发耗时:'.($time['dispatchstart'] - $time['routerstart']).' ms'; 139 | $time_msg .= PHP_EOL.' 处理过程耗时: '.($time['end'] - $time['dispatchstart']).' ms'; 140 | 141 | $time_msg .= PHP_EOL.' 执行总共耗时:'.($time['end'] - $time['start']).' ms'; 142 | Debug::log($header.$file_msg.$time_msg.$mem_msg.PHP_EOL, 'TRACER'); 143 | } 144 | 145 | if (in_array('HEADER', $this->output)) { 146 | $memory = array( 147 | 'S' => $mem['startm'], 148 | 'M' => $mem['max'], 149 | 'U' => $mem['max'] - $mem['start'], 150 | ); 151 | $time = array( 152 | 'S' => $time['start'] - $time['request'], 153 | 'P' => $time['dispatchstart'] - $time['start'], 154 | 'U' => $time['end'] - $time['dispatchstart'], 155 | ); 156 | Debug::header() 157 | ->debugInfo('Mem', $memory) 158 | ->debugInfo('Time', $time) 159 | ->debugInfo('File', $files); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /library/Service/Qiniu.php: -------------------------------------------------------------------------------- 1 | _config['bucket']; 57 | $op = '/move/'.self::qiniuEncode($from).'/'.self::qiniuEncode($to); 58 | return self::opration($op); 59 | } 60 | 61 | /** 62 | * 复制文件 63 | * 64 | * @param string $from 源文件 65 | * @param string $saveas 目标文件名 66 | * 67 | * @return bool 操作结果 68 | */ 69 | public static function copy($from, $saveas) 70 | { 71 | // $bucket = $this->_config['bucket']; 72 | $op = '/copy/'.self::qiniuEncode($from).'/'.self::qiniuEncode($saveas); 73 | return self::opration($op); 74 | } 75 | 76 | /** 77 | * 获取上传token 78 | * 79 | * @param mixed $bucket 存储位置 80 | * @param mixed $key 文件名 81 | * @param mixed $max 文件上限 82 | * @param int $timeout 过期时间 83 | * 84 | * @return string 85 | */ 86 | public static function getToken($bucket, $key, $max = 10485760, $timeout = 600) 87 | { 88 | $setting = array( 89 | 'scope' => $bucket, 90 | 'saveKey' => $key, 91 | 'deadline' => $timeout + $_SERVER['REQUEST_TIME'], 92 | 'fsizeLimit' => intval($max), 93 | ); 94 | $setting = self::qiniuEncode(json_encode($setting)); 95 | return self::sign($setting).':'.$setting; 96 | } 97 | 98 | /** 99 | * 删除 100 | * 101 | * @param string $uri [完整文件名] 102 | * 103 | * @return bool [description] 104 | */ 105 | public static function delete($uri) 106 | { 107 | $file = self::qiniuEncode($uri); 108 | return self::opration('/delete/'.$file); 109 | } 110 | 111 | /** 112 | * 判断文件是否存在 113 | * 114 | * @param string $uri 115 | * 116 | * @return bool 是否存在 117 | */ 118 | public static function has($uri) 119 | { 120 | $op = '/stat/'.self::qiniuEncode($uri); 121 | return self::opration($op); 122 | } 123 | 124 | /** 125 | * 转pdf 126 | * 127 | * @param mixed $bucket 存储位置 128 | * @param mixed $key 文件名 129 | * @param mixed $saveas 保存文件名 130 | * 131 | * @return bool 操作结果 132 | */ 133 | public static function toPdf($bucket, $key, $saveas) 134 | { 135 | $API = 'http://api.qiniu.com'; 136 | $op = '/pfop/'; 137 | $data = 'bucket='.$bucket.'&key='.$key.'&fops=yifangyun_preview|saveas/'.self::qiniuEncode($saveas); 138 | return self::opration($op, $data, $API); 139 | } 140 | 141 | /** 142 | * 七牛操作 143 | * 144 | * @param string $op [操作命令] 145 | * @param null|array $data 146 | * @param string $host 主机 147 | * 148 | * @return bool [操作结果] 149 | */ 150 | private static function opration($op, $data = null, $host = self::QINIU_RS) 151 | { 152 | $token = self::sign(is_string($data) ? $op."\n".$data : $op."\n"); 153 | $url = $host.$op; 154 | $header = array('Authorization: QBox '.$token); 155 | 156 | if ($ch = curl_init($url)) { 157 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 158 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 159 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); 160 | if ($data) { 161 | curl_setopt($ch, CURLOPT_POST, true); 162 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 163 | } 164 | curl_setopt($ch, CURLOPT_HEADER, 1); 165 | $response = curl_exec($ch); 166 | $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 167 | curl_close($ch); 168 | 169 | if ($status == 200) { 170 | return true; 171 | } 172 | // elseif (\Config::get('debug')) 173 | // { 174 | // /*操作出错*/ 175 | // \PC::debug($response, '七牛请求出错'); 176 | // } 177 | } 178 | Log::write('[QINIU]七牛错误'.$url.':'.($response ?: '请求失败'), 'ERROR'); 179 | return false; 180 | } 181 | 182 | /** 183 | * 获取url签名 184 | * 185 | * @param string $url url 186 | * 187 | * @return string 签名字符串 188 | */ 189 | private static function sign($url) 190 | { 191 | $config = self::$_config ?: (self::$_config = \Config::getSecret('qiniu')); 192 | $sign = hash_hmac('sha1', $url, $config['secretkey'], true); 193 | $ak = $config['accesskey']; 194 | return $ak.':'.self::qiniuEncode($sign); 195 | } 196 | 197 | /** 198 | * 七牛安全编码 199 | * 200 | * @param string $str 201 | */ 202 | private static function qiniuEncode($str) 203 | { 204 | return strtr(base64_encode($str), array('+' => '-', '/' => '_')); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /tests/library/DbTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($connection); 28 | $this->assertInstanceOf('\PDO', $connection); 29 | } 30 | 31 | /** 32 | * @dataProvider configProvider 33 | * 34 | * @param mixed $config 35 | */ 36 | public function testSet($config) 37 | { 38 | $connection = Db::set('x', $config); 39 | $this->assertSame($connection, Db::get('x')); 40 | Db::set('xx', $connection); 41 | $this->assertSame($connection, Db::get('xx')); 42 | } 43 | 44 | public function testGet() 45 | { 46 | $connection = Db::current(); 47 | $this->assertSame($connection, Db::get('xxxxx')); 48 | } 49 | 50 | /** 51 | * @dataProvider configProvider 52 | * @depends testGet 53 | * 54 | * @param mixed $config 55 | */ 56 | public function testCurrent($config) 57 | { 58 | $connection = Db::connect($config); 59 | Db::current($connection); 60 | $this->assertSame($connection, Db::current()); 61 | } 62 | /** 63 | * @dataProvider configProvider 64 | * @depends testConnect 65 | * 66 | * @param mixed $config 67 | */ 68 | public function testQuery($config) 69 | { 70 | Db::set('_read', $config); 71 | $this->assertCount(2, Db::query('SELECT * FROM user')); 72 | $dataset = array( 73 | array( 74 | 'id' => '1', 75 | 'account' => 'newfuture', 76 | 'name' => 'New Future', 77 | ) 78 | ); 79 | $user = Db::query('SELECT id,account,name FROM user WHERE id=?', array(1)); 80 | $this->assertEquals($dataset, $user); 81 | //单条查询 82 | $user = Db::query('SELECT id,account,name FROM user WHERE id=?', array(1), false); 83 | $this->assertEquals($dataset[0], $user); 84 | } 85 | 86 | /** 87 | * @dataProvider configProvider 88 | * @depends testConnect 89 | * 90 | * @param mixed $config 91 | */ 92 | public function testColumn($config) 93 | { 94 | Db::set('_read', $config); 95 | $name = '测试'; 96 | $this->assertSame($name, Db::column('SELECT name FROM user WHERE id=2')); 97 | $this->assertSame($name, Db::column('SELECT name FROM user WHERE id=?', array(2))); 98 | $this->assertSame($name, Db::column('SELECT name FROM user WHERE id=:id', array('id' => 2))); 99 | $this->assertSame($name, Db::column('SELECT name FROM user WHERE id=:id', array(':id' => 2))); 100 | } 101 | 102 | /** 103 | * @dataProvider configProvider 104 | * @depends testQuery 105 | * 106 | * @param mixed $config 107 | */ 108 | public function testExec($config) 109 | { 110 | Db::set('_read', $config); 111 | Db::set('_write', $config); 112 | 113 | $data = array( 114 | 'id' => 3, 115 | 'account' => 'dbtester', 116 | 'name' => 'Db Test', 117 | 'created_at' => date('Y-m-d h:i:s'), 118 | ); 119 | $insert = 'INSERT INTO`user`(`id`,`name`,`account`,`created_at`)VALUES(:id,:name,:account,:created_at)'; 120 | $this->assertSame(1, Db::exec($insert, $data)); 121 | $this->assertEquals(3, Db::column('SELECT COUNT(*) FROM user')); 122 | $user = Db::query('SELECT id,account,name,created_at FROM user WHERE id=?', array($data['id']), false); 123 | $this->assertEquals($data, $user); 124 | 125 | $update = 'UPDATE`user`SET`name`= ? WHERE(`id`= ?)'; 126 | $UPDATE_NAME = 'UPDATE_TEST DB'; 127 | $this->assertSame(1, Db::execute($update, array($UPDATE_NAME, 3))); 128 | $data['name'] = $UPDATE_NAME; 129 | $user = Db::query('SELECT id,account,name,created_at FROM user WHERE id=?', array($data['id']), false); 130 | $this->assertEquals($data, $user); 131 | 132 | $delete = 'DELETE FROM`user`WHERE(`id`= :id)'; 133 | $this->assertSame(1, Db::exec($delete, array(':id' => $data['id']))); 134 | $this->assertEquals(2, Db::column('SELECT COUNT(*) FROM user')); 135 | } 136 | 137 | /** 138 | * @dataProvider tableProvider 139 | * 140 | * @param mixed $name 141 | * @param string $pk 142 | * @param null|mixed $prefix 143 | */ 144 | public function testTable($name, $pk = 'id', $prefix = null) 145 | { 146 | $orm = call_user_func_array(array('Db', 'table'), func_get_args()); 147 | $this->assertInstanceOf('Orm', $orm); 148 | $this->assertAttributeEquals($pk, '_pk', $orm); 149 | if (func_num_args() > 2) { 150 | $this->assertAttributeEquals($prefix, '_pre', $orm); 151 | } 152 | } 153 | 154 | /** 155 | *测试数据库 156 | */ 157 | public function configProvider() 158 | { 159 | return array( 160 | 'default' => array('_'), 161 | 'test' => array('test'), 162 | 'mysql' => array( 163 | array( 164 | 'dsn' => getenv('MYSQL_DSN') ?: 'mysql:host=localhost;port=3306;dbname=yyf;charset=utf8', 165 | 'username' => getenv('DB_USER') ?: 'root', 166 | 'password' => getenv('DB_PWD'), 167 | ), 168 | ), 169 | 'sqlite' => array( 170 | array( 171 | 'dsn' => getenv('SQLITE_DSN') ?: 'sqlite:'.APP_PATH.'/runtime/yyf.db', 172 | ), 173 | ), 174 | ); 175 | } 176 | 177 | /** 178 | *测试数据库 179 | */ 180 | public function tableProvider() 181 | { 182 | return array( 183 | 'only name' => array('user'), 184 | 'name with pk' => array('article','aid'), 185 | 'name pk with prefix' => array('user','uid','yyf_'), 186 | ); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /library/Debug/Header.php: -------------------------------------------------------------------------------- 1 | dump($data); 29 | * 30 | * @author NewFuture 31 | */ 32 | class Header 33 | { 34 | const HEADER_BASE = 'Yyf'; 35 | private static $_processed; 36 | private static $_instance = null; 37 | 38 | protected function __construct() 39 | { 40 | $verion = Config::get('version'); 41 | if ($app = Application::app()) { 42 | $env = $app->environ(); 43 | } else { 44 | $env = ini_get('yaf.environ'); 45 | } 46 | header(strtoupper(static::HEADER_BASE).": $verion,$env"); 47 | } 48 | 49 | /** 50 | * 通过http header dump数据 51 | * 52 | * @param string $key header键 53 | * @param mixed $value 对应值 54 | */ 55 | public function __invoke($key, $value) 56 | { 57 | assert('ctype_alnum($key)', 'header 键必须是字符或数字组合'); 58 | switch (gettype($value)) { 59 | case 'string': 60 | if (ctype_print($value) && (strpos($value, "\n") === false)) { 61 | $key = '-S-'.$key;//字符串 62 | } else { 63 | $key = '-E-'.$key;//编码字符串 64 | $value = rawurlencode($value); 65 | } 66 | break; 67 | 68 | case 'integer': 69 | case 'double': 70 | $key = '-N-'.$key;//数字 71 | break; 72 | 73 | case 'boolean': 74 | $key .= '-B-'.$key;//bool 75 | $value = $value ? 'true' : 'false'; 76 | break; 77 | 78 | case 'array': 79 | case 'null': 80 | case null: 81 | $key = '-J-'.$key;//数组JSON 82 | $value = json_encode($value); 83 | break; 84 | 85 | case 'object': 86 | //对象 87 | $key = '-O-'.$key; 88 | static::$_processed = array(); 89 | $value = json_encode(static::_convertObject($value)); 90 | break; 91 | 92 | case 'unkown': 93 | default: 94 | $key = '-U-'.$key; 95 | $value = rawurlencode(str_replace(' ', "\t", print_r($value, true))); 96 | $value = str_replace(array('+', '%3D', '%3E', '%28', '%29', '%5B', '%5D', '%3A'), array(' ', '=', '>', '(', ')', '[', ']', ':'), $value); 97 | } 98 | headers_sent() || header(static::HEADER_BASE.$key.': '.$value, false); 99 | return $this; 100 | } 101 | 102 | /** 103 | * 静态调用 104 | * 105 | * @param string $Type 函数名 106 | * @param array $params 参数 107 | */ 108 | public static function __callStatic($Type, $params) 109 | { 110 | $head = static::instance(); 111 | if (isset($params[1])) { 112 | //多参数 113 | return $head($Type, $params); 114 | } elseif (isset($params[0])) { 115 | //单参数 116 | return $head($Type, $params[0]); 117 | } 118 | } 119 | 120 | /** 121 | * 链式调用 122 | * 123 | * @param string $Type 函数名 124 | * @param array $params 参数 125 | */ 126 | public function __call($Type, $params) 127 | { 128 | return static::__callStatic($Type, $params); 129 | } 130 | 131 | /** 132 | * 添加header调试信息 133 | * 134 | * @param string $type 字段名 135 | * @param array|string $info [输出的调试信息] 136 | * @param int $N=3 数字保留精度0不处理(压缩数字小数点后数据减小header数据量) 137 | */ 138 | public function debugInfo($type, $info, $N = 3) 139 | { 140 | if (is_array($info)) { 141 | if ($N > 0) { 142 | array_walk_recursive($info, function (&$v) use ($N) { 143 | is_numeric($v) && $v = round($v, $N); 144 | }); 145 | } 146 | $info = json_encode($info, 64);//64 JSON_UNESCAPED_SLASHES 147 | } 148 | headers_sent() || header(static::HEADER_BASE."-$type: $info", false); 149 | return $this; 150 | } 151 | 152 | /** 153 | * 获取事例 154 | * 155 | * @return Header 自身实体 156 | */ 157 | public static function instance() 158 | { 159 | return static::$_instance ?: (static::$_instance = new static()); 160 | } 161 | 162 | /** 163 | * 转换object->array 164 | * 165 | * @param Object $object [description] 166 | * 167 | * @return array 168 | */ 169 | private static function _convertObject($object) 170 | { 171 | if (!is_object($object)) { 172 | return $object; 173 | } 174 | static::$_processed[] = $object; 175 | $object_as_array = array(); 176 | 177 | $object_as_array['__CLASS__'] = get_class($object); 178 | 179 | /* vars*/ 180 | $object_vars = get_object_vars($object); 181 | foreach ($object_vars as $key => $value) { 182 | // same instance as parent object 183 | $object_as_array[$key] = in_array($value, static::$_processed, true) ? '__CLASS__['.get_class($value).']' : static::_convertObject($value); 184 | } 185 | 186 | $reflection = new ReflectionClass($object); 187 | /* properties */ 188 | foreach ($reflection->getProperties() as $property) { 189 | if (array_key_exists($property->getName(), $object_vars)) { 190 | // if one of these properties was already added above then ignore it 191 | continue; 192 | } 193 | $type = $property->getName(); 194 | $type .= $property->isStatic() ? '_STATIC' : ''; 195 | $type .= $property->isPrivate() ? '_PRIVATE' : $property->isProtected() ? '_PROTECTED' : '_PUBLIC'; 196 | 197 | $property->setAccessible(true); 198 | $value = $property->getValue($object); 199 | 200 | $object_as_array[$type] = in_array($value, static::$_processed, true) ? '__CLASS__['.get_class($value).']' : static::_convertObject($value); 201 | } 202 | return $object_as_array; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /library/Rest.php: -------------------------------------------------------------------------------- 1 | response !== false) { 49 | header('Content-Type: application/json; charset=utf-8', true, $this->code); 50 | echo json_encode($this->response, $this->_config['json']); 51 | } 52 | } 53 | 54 | /** 55 | * 初始化 REST 路由 56 | * 修改操作 和 绑定参数 57 | * 58 | * @access protected 59 | */ 60 | protected function init() 61 | { 62 | Yaf_Dispatcher::getInstance()->disableView(); //立即输出响应,并关闭视图模板 63 | $request = $this->_request; 64 | 65 | /*请求来源,跨站cors响应*/ 66 | if ($cors = Config::get('cors')) { 67 | $this->corsHeader($cors->toArray()); 68 | } 69 | 70 | /*请求操作判断*/ 71 | $method = $request->getMethod(); 72 | $type = $request->getServer('CONTENT_TYPE'); 73 | if ($method === 'OPTIONS') { 74 | /*cors 跨域header应答,只需响应头即可*/ 75 | exit; 76 | } elseif (strpos($type, 'application/json') === 0) { 77 | /*json 数据格式*/ 78 | if ($inputs = file_get_contents('php://input')) { 79 | $input_data = json_decode($inputs, true); 80 | if ($input_data) { 81 | $GLOBALS['_'.$method] = $input_data; 82 | } else { 83 | parse_str($inputs, $GLOBALS['_'.$method]); 84 | } 85 | } 86 | } elseif ($method === 'PUT' && ($inputs = file_get_contents('php://input'))) { 87 | /*直接解析*/ 88 | parse_str($inputs, $GLOBALS['_PUT']); 89 | } 90 | 91 | /*Action路由*/ 92 | $action = $request->getActionName(); 93 | $this->_config = Config::get('rest')->toArray(); 94 | if (is_numeric($action)) { 95 | /*数字id绑定参数*/ 96 | $request->setParam($this->_config['param'], intval($action)); 97 | //提取请求路径 98 | $path = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : 99 | strstr($_SERVER['REQUEST_URI'].'?', '?', true); 100 | $path = substr(strstr($path, $action), strlen($action) + 1); 101 | $action = $path ? strstr($path.'/', '/', true) : $this->_config['action']; 102 | } 103 | 104 | $rest_action = $method.'_'.$action; //对应REST_Action 105 | 106 | /*检查该action操作是否存在,存在则修改为REST接口*/ 107 | if (method_exists($this, $rest_action.'Action')) { 108 | /*存在对应的操作*/ 109 | $request->setActionName($rest_action); 110 | } elseif (!method_exists($this, $action.'Action')) { 111 | /*action和REST_action 都不存在*/ 112 | if (method_exists($this, $this->_config['none'].'Action')) { 113 | $request->setActionName($this->_config['none']); 114 | } else { 115 | $this->response(-8, array( 116 | 'error' => '未定义操作', 117 | 'method' => $method, 118 | 'action' => $action, 119 | 'controller' => $request->getControllerName(), 120 | 'module' => $request->getmoduleName(), 121 | ), 404); 122 | exit; 123 | } 124 | } elseif ($action !== $request->getActionName()) { 125 | /*修改后的$action存在而$rest_action不存在,绑定参数默认控制器*/ 126 | $request->setActionName($action); 127 | } 128 | } 129 | 130 | /** 131 | * 设置返回信息,立即返回 132 | * 133 | * @access protected 134 | * 135 | * @param int $status 返回状态 136 | * @param mixed $data 返回数据 137 | * @param int $code 可选参数,设置响应状态吗 138 | */ 139 | protected function response($status, $data = null, $code = null) 140 | { 141 | $this->response = array( 142 | $this->_config['status'] => $status, 143 | $this->_config['data'] => $data, 144 | ); 145 | ($code > 0) && $this->code = $code; 146 | exit; 147 | } 148 | 149 | /** 150 | * 快速返回成功信息(status为1) 151 | * 152 | * @access protected 153 | * 154 | * @param mixed $data 返回数据内容 155 | * @param int $code 设置状态码[默认200] 156 | */ 157 | protected function success($data = null, $code = 200) 158 | { 159 | $this->response = array( 160 | $this->_config['status'] => 1, 161 | $this->_config['data'] => $data, 162 | ); 163 | $this->code = $code; 164 | exit; 165 | } 166 | 167 | /** 168 | * 快速返回失败信息(status为0) 169 | * 170 | * @access protected 171 | * 172 | * @param mixed $data 返回数据内容 173 | * @param int $code 设置状态码[默认200] 174 | */ 175 | protected function fail($data = null, $code = 200) 176 | { 177 | $this->response = array( 178 | $this->_config['status'] => 0, 179 | $this->_config['data'] => $data, 180 | ); 181 | $this->code = $code; 182 | exit; 183 | } 184 | 185 | /** 186 | * CORS 跨域请求响应头处理 187 | * 188 | * @param array $cors CORS配置 189 | * @access private 190 | */ 191 | private function corsHeader(array $cors) 192 | { 193 | //请求来源站点 194 | $from = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : 195 | (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null); 196 | 197 | if ($from) { 198 | $domains = $cors['Access-Control-Allow-Origin']; 199 | if ($domains !== '*') {//非通配 200 | $domain = strtok($domains, ','); 201 | while ($domain) { 202 | if (strpos($from, rtrim($domain, '/')) === 0) { 203 | $cors['Access-Control-Allow-Origin'] = $domain; 204 | break; 205 | } 206 | $domain = strtok(','); 207 | } 208 | if (!$domain) { 209 | /*非请指定的求来源,自动终止响应*/ 210 | header('Forbid-Origin: '.$from); 211 | return; 212 | } 213 | } elseif ($cors['Access-Control-Allow-Credentials'] === 'true') { 214 | /*支持多域名和cookie认证,此时修改源*/ 215 | $cors['Access-Control-Allow-Origin'] = $from; 216 | } 217 | foreach ($cors as $key => $value) { 218 | header($key.': '.$value); 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /library/Db.php: -------------------------------------------------------------------------------- 1 | 20 | * @method bool isOk() 上次查询是否出错 21 | * @method bool transact($func) 执行事务 22 | * @method int beginTransaction()— 启动一个事务 23 | * @method bool commit() — 提交一个事务 24 | * @method int rollBack() — 回滚一个事务 25 | * @method int lastInsertId() — 返回最后插入行的ID或序列值 26 | * @method bool prepare($sql) — 查询预处理 27 | * @method boll setAttribute($key,$value) — 设置属性 28 | * 29 | * @author NewFuture 30 | */ 31 | class Db 32 | { 33 | private static $_dbpool = array(); // 数据库连接池 34 | private static $current = null; //当前数据库连接 35 | 36 | /** 37 | * 静态方式调用Database的方法 38 | * 39 | * @param string $method Database函数名 40 | * @param array $params 参数 41 | */ 42 | public static function __callStatic($method, $params) 43 | { 44 | assert('method_exists("\Service\Database",$method)', '[Db::Database]Database中不存在此方式:'.$method); 45 | return call_user_func_array(array(Db::current(), $method), $params); 46 | } 47 | 48 | /** 49 | * 数据库初始化 并取得数据库类实例 50 | * 51 | * @param mixed $config 连接配置 52 | * 53 | * @return Database 返回数据库对象 54 | */ 55 | public static function connect($config = '_') 56 | { 57 | if (is_array($config)) { 58 | assert('isset($config["dsn"])', '[Db::connect] 数组参数必须配置dsn'); 59 | $key = md5(json_encode($config)); 60 | } else { 61 | assert('is_string($config)', '[Db::connect]直接收数组或者字符串参数'); 62 | $key = $config; 63 | $config = Config::getSecret('database', 'db.'.$key)->toArray(); 64 | assert('!empty($config)', '[Db::connect]数据库配置未设置:db.'.$key); 65 | } 66 | if (isset(Db::$_dbpool[$key])) { 67 | return Db::$_dbpool[$key]; 68 | } 69 | $username = isset($config['username']) ? $config['username'] : null; 70 | $password = isset($config['password']) ? $config['password'] : null; 71 | 72 | $options = Config::getSecret('database', 'options')->toArray(); 73 | if (isset($config['options'])) { 74 | assert('is_array($config["options"])', '[Db::connect]数据库连接参数options配置应是数组'); 75 | $options = $config['options'] + $options; 76 | } 77 | 78 | try { 79 | return Db::$_dbpool[$key] = new Database($config['dsn'], $username, $password, $options); 80 | } catch (Exception $e) { 81 | Logger::log( 82 | 'ALERT', 83 | '[Db::connect]数据库[{KEY}]({DSN})无法连接:{MSG}', 84 | array('KEY' => $key, 'DSN' => $config['dsn'], 'MSG' => $e->getMessage()) 85 | ); 86 | throw $e; 87 | } 88 | } 89 | 90 | /** 91 | * 获取或者设置当前数据库连接,如果没有数据库事例将读取默认配置 92 | * 93 | * @param Database $db 要设定的数据库,空返回当前数据库 94 | * 95 | * @return Database 96 | */ 97 | public static function current(Database $db = null) 98 | { 99 | if (null === $db) { 100 | return Db::$current ?: (Db::$current = Db::connect()); 101 | } 102 | return Db::$current = $db; 103 | } 104 | 105 | /** 106 | * 获取数据库连接,用于读写分离,如果不存在配置使用默认数据库 107 | * 108 | * @param string $name [数据库名] 109 | * 110 | * @return Database 111 | */ 112 | public static function get($name = '_') 113 | { 114 | if (isset(Db::$_dbpool[$name])) { 115 | return Db::$_dbpool[$name]; 116 | } 117 | return Db::$_dbpool[$name] = (Config::getSecret('database', 'db.'.$name.'.dsn') ? 118 | Db::connect($name) : Db::connect('_')); 119 | } 120 | 121 | /** 122 | * 设定换数据库,可以覆盖默认数据库配置 123 | * 124 | * @param string $name 设置名称,‘_’,'_read','_write' 125 | * @param mixed $config 配置名称 126 | * 127 | * @return Database 128 | */ 129 | public static function set($name, $config) 130 | { 131 | $conf = array(); 132 | switch (func_num_args()) { 133 | case 2://一个参数,对象,数组,配置名或者dsn 134 | if ($config instanceof Database) { 135 | return Db::$_dbpool[$name] = $config; 136 | } 137 | if (is_string($config) && strpos($config, ':') > 0) { 138 | $conf['dsn'] = &$config; 139 | } else { 140 | assert('is_string($config)||is_array($config)', '[Db::set] 单个数组参数必须是字符串或者数组'); 141 | $conf = $config; 142 | } 143 | break; 144 | case 5://三参数最后一个为密码 145 | $conf['options'] = func_get_arg(4); 146 | // no break 147 | case 4://三参数最后一个为密码 148 | $conf['password'] = func_get_arg(3); 149 | // no break 150 | case 3://两参数第二个为账号 151 | assert('is_string($config)', '[Db::set]多参数dsn链接设置必须是字符串'); 152 | $conf['username'] = func_get_arg(2); 153 | $conf['dsn'] = $config; 154 | break; 155 | default: 156 | throw new Exception('无法解析参数,参数数目异常'.func_num_args()); 157 | } 158 | return Db::$_dbpool[$name] = Db::connect($conf); 159 | } 160 | 161 | /** 162 | * 获取数据库表进行后续操作 163 | * 164 | * @param string $name 数据库表名 165 | * @param string $pk 主键 166 | * @param string $prefix 数据库前缀 167 | * 168 | * @return Orm 数据库关系类 169 | */ 170 | public static function table($name, $pk = false, $prefix = true) 171 | { 172 | return new Orm($name, $pk, $prefix); 173 | } 174 | 175 | /** 176 | * exec 别名 覆盖Db类的的execute 177 | * 178 | * @param string $sql SQL语句 179 | * @param array $params 参数 180 | * 181 | * @return int 影响条数 182 | */ 183 | public static function execute($sql, array $params = null) 184 | { 185 | return Db::get('_write')->exec($sql, $params); 186 | } 187 | 188 | /** 189 | * 数据库操作(写入) 190 | * 191 | * @param string $sql SQL语句 192 | * @param array $params 参数 193 | * 194 | * @return int 影响条数 195 | */ 196 | public static function exec($sql, array $params = null) 197 | { 198 | return Db::get('_write')->exec($sql, $params); 199 | } 200 | 201 | /** 202 | * 数据库查询加速 203 | * 204 | * @param string $sql SQL语句 205 | * @param array $params 参数 206 | * @param bool $fetchAll 默认全部获取(selecy) 207 | * @param bool $fetchmode 获取模式 208 | * 209 | * @return mixed 查询结果 210 | */ 211 | public static function query($sql, array $params = null, $fetchAll = true, $fetchmode = null) 212 | { 213 | return Db::get('_read')->query($sql, $params, $fetchAll, $fetchmode); 214 | } 215 | 216 | /** 217 | * 数据库行查询加速 218 | * 219 | * @param string $sql SQL语句 220 | * @param array $params 参数 221 | * 222 | * @return mixed 查询结果 223 | */ 224 | public static function column($sql, array $params = null) 225 | { 226 | return Db::get('_read')->column($sql, $params); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /library/Cache.php: -------------------------------------------------------------------------------- 1 | setMulti($name, $value); 45 | 46 | case 'redis': 47 | $result = true; 48 | if ($value) { 49 | foreach ($name as $k => &$v) { 50 | $result = $result && $handler->setEx($k, $value, serialize($v));//memcache 原始时间 51 | } 52 | } else { 53 | foreach ($name as $k => &$v) { 54 | $result = $result && $handler->set($k, serialize($v));//memcache 原始时间 55 | } 56 | } 57 | return $result; 58 | 59 | case 'file': 60 | return $handler->mset($name, $value); 61 | 62 | case 'memcache': 63 | $result = true; 64 | foreach ($name as $k => &$v) { 65 | $result = $result && $handler->set($k, $v, null, $value);//memcache 原始时间 66 | } 67 | return $result; 68 | } 69 | } else { 70 | //单条设置 71 | assert(func_num_args() > 1, '[Cache::set]第一个参数为数组时(批量设置),最多两个参数'); 72 | if ('memcached' === $type || 'file' === $type) { 73 | return $handler->set($name, $value, $expire); 74 | } elseif ('redis' === $type) { 75 | $value = serialize($value); 76 | return $expire ? $handler->setEx($name, $expire, $value) : $handler->set($name, $value); 77 | } 78 | assert('"memcache" ===$type', '缓存驱动不支持'); 79 | return $handler->set($name, $value, null, $expire); 80 | } 81 | } 82 | 83 | /** 84 | * 读取缓存数据 85 | * 86 | * @param string|array $name 键 87 | * @param mixed $default [默认值false] 88 | * 89 | * @return mixed 获取结果 90 | */ 91 | public static function get($name, $default = false) 92 | { 93 | $handler = Cache::handler(); 94 | if (is_array($name)) { 95 | //数组批量获取 96 | assert(func_num_args() === 1, '[Cache::get]参数为数组时(批量设置),只能有一个参数'); 97 | switch (Cache::$type) { 98 | case 'memcached': 99 | $default = $handler->getMulti($name); 100 | if (count($default) === count($name)) { 101 | return $default; 102 | } 103 | return array_merge(array_fill_keys($name, false), $default); 104 | 105 | case 'file': 106 | return $handler->mget($name); 107 | 108 | case 'redis': 109 | if ($value = $handler->mget($name)) { 110 | return array_combine($name, array_map('unserialize', $value)); 111 | } 112 | return array_fill_keys($name, $default); 113 | 114 | case 'memcache': 115 | $result = array(); 116 | foreach ($name as &$key) { 117 | $result[$key] = $handler->get($key);//memcache 原始时间 118 | } 119 | return $result; 120 | } 121 | } else { 122 | $value = $handler->get($name); 123 | return false === $value ? $default : ('redis' === Cache::$type ? unserialize($value) : $value); 124 | } 125 | } 126 | 127 | /** 128 | * 删除缓存数据 129 | * 130 | * @param string $name 键值 131 | * 132 | * @return bool 133 | */ 134 | public static function del($name) 135 | { 136 | return Cache::handler()->delete($name); 137 | } 138 | 139 | /** 140 | * 清空缓存 141 | * 142 | * @return bool 操作结果 143 | */ 144 | public static function flush() 145 | { 146 | $handler = Cache::handler(); 147 | if ('redis' === Cache::$type) { 148 | return $handler->flushDB(); 149 | } 150 | return $handler->flush(); 151 | } 152 | 153 | /** 154 | * 获取处理方式 155 | * 156 | * @return objecct $_handler 157 | */ 158 | public static function handler() 159 | { 160 | if ($handler = &Cache::$_handler) { 161 | return $handler; 162 | } 163 | 164 | switch (Cache::$type = Config::get('cache.type')) { 165 | case 'memcached': //redis 存储 166 | $config = Config::getSecret('memcached'); 167 | $config = $config->get('cache') ?: $config->get('_'); 168 | if ($mcid = $config->get('mcid')) { 169 | //共享长连接 170 | $handler = new \Memcached($mcid); 171 | if (!$handler->getServerList()) { 172 | //无可用服务器时建立连接 173 | $handler->addServer($config->get('host'), $config->get('port')); 174 | } 175 | } else { 176 | $handler = new \Memcached(); 177 | $handler->addServer($config->get('host'), $config->get('port')); 178 | } 179 | break; 180 | 181 | case 'redis': //redis 存储 182 | $config = Config::getSecret('redis'); 183 | $config = $config->get('cache') ?: $config->get('_'); 184 | 185 | $handler = new \Redis(); 186 | $handler->connect($config->get('host'), $config->get('port')); 187 | //密码验证 188 | ($value = $config->get('auth')) && $handler->auth($value); 189 | //限定数据库 190 | ($value = $config->get('db')) && $handler->select($value); 191 | break; 192 | 193 | case 'file': //文件存储 194 | $handler = new File(Config::get('runtime').'cache', true); 195 | break; 196 | 197 | case 'memcache': // memcahe 包括 sae 198 | $config = Config::getSecret('memcahe', 'cache'); 199 | $handler = new \Memcache; 200 | if ($port = $config->get('port')) { 201 | $handler->addServer($config->get('host'), $port); 202 | } else { 203 | //sae memcahe 204 | $handler->connect($config->get('host')); 205 | } 206 | break; 207 | 208 | default: 209 | Logger::write('缓存初始化失败[cache init failed]'.$type, 'ALERT'); 210 | throw new Exception('未知缓存方式'.Cache::$type); 211 | } 212 | return $handler; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /tests/library/LoggerTest.php: -------------------------------------------------------------------------------- 1 | environ(); 33 | 34 | $conf = static::app()->getConfig()->log; 35 | static::$allow = explode(',', strtoupper($conf->allow)); 36 | static::$type = $conf->type; 37 | if ('system' === $conf->type) { 38 | //拦截系统日志 39 | ini_set('error_log', static::getLogFile()); 40 | } 41 | } 42 | 43 | public static function tearDownAfterClass() 44 | { 45 | clearstatcache(); 46 | Logger::clear(); 47 | if (is_file($file = static::getLogFile())) { 48 | unlink($file); 49 | } 50 | } 51 | 52 | public function setUp() 53 | { 54 | $m = &$this->message; 55 | $m = array(); 56 | Logger::$listener = function (&$level, &$msg) use (&$m) { 57 | $m[] = array($level => $msg); 58 | }; 59 | Logger::clear(); 60 | if (is_file($file = static::getLogFile())) { 61 | unlink($file); 62 | } 63 | } 64 | 65 | public function tearDown() 66 | { 67 | Logger::clear(); 68 | } 69 | 70 | public function testListener() 71 | { 72 | $level = 'NOTICE'; 73 | $msg = 'test message'; 74 | Logger::write($msg, $level); 75 | $this->assertPop($level, $msg); 76 | 77 | $level = 'ssssa'; 78 | $msg = 'message'; 79 | Logger::write($msg, $level); 80 | $this->assertPop($level, $msg); 81 | } 82 | 83 | public function testWrite() 84 | { 85 | $level = static::$LEVEL; 86 | $level[] = uniqid('test'); 87 | $log1 = 'test logger lower'; 88 | $log2 = 'isaverylongloggerstringfortesting'; 89 | if ('file' === static::$type) { 90 | //文件存储形式 91 | foreach ($level as $l) { 92 | $file = static::getLogFile($l); 93 | if (in_array($l, static::$allow)) { 94 | $this->assertNotFalse(Logger::write($l.$log1, strtolower($l))); 95 | $this->assertNotFalse(Logger::write($l.$log2, $l)); 96 | $pre = date('[d-M-Y H:i:s e] (').getenv('REQUEST_URI').') '; 97 | $message = $pre.$l.$log1.PHP_EOL.$pre.$l.$log2.PHP_EOL; 98 | $this->assertStringEqualsFile($file, $message); 99 | $this->assertFileMode($file); 100 | } else { 101 | $this->assertNotTrue(Logger::write($log1, strtolower($l))); 102 | $this->assertNotTrue(Logger::write($log2, $l)); 103 | $this->assertFileNotExists($file); 104 | } 105 | } 106 | } elseif ('system' === static::$type) { 107 | //系统日志 108 | $message = ''; 109 | foreach ($level as $l) { 110 | $r1 = Logger::write($l.$log1, strtolower($l)); 111 | $r2 = Logger::write($l.$log2, $l); 112 | if (in_array($l, static::$allow)) { 113 | $date = date('[d-M-Y H:i:s e] '); 114 | $message .= $date."$l: ${l}${log1}".PHP_EOL.$date."$l: ${l}${log2}".PHP_EOL; 115 | $this->assertNotFalse($r1); 116 | $this->assertNotFalse($r2); 117 | } else { 118 | $this->assertNotTrue($r1); 119 | $this->assertNotTrue($r2); 120 | } 121 | } 122 | $file = static::getLogFile(); 123 | $this->assertStringEqualsFile($file, $message); 124 | } 125 | } 126 | 127 | /** 128 | * @depends testListener 129 | */ 130 | public function testLog() 131 | { 132 | $message = 'just test Message'; 133 | $templete = '{key}-{test}'; 134 | $context = array('key' => 'somevalue','test' => 'tstring','t' => 'sss'); 135 | $templete_string = 'somevalue-tstring'; 136 | $json = array('test','key','value'); 137 | foreach (static::$LEVEL as $l) { 138 | Logger::log($l, $message); 139 | Logger::log(strtolower($l), $templete, $context); 140 | Logger::log($l, $json); 141 | $this->assertPop($l, json_encode($json, 256)); 142 | $this->assertPop($l, $templete_string); 143 | $this->assertPop($l, $message); 144 | } 145 | } 146 | 147 | /** 148 | * @depends testLog 149 | */ 150 | public function testInterface() 151 | { 152 | $message = 'test log message string'; 153 | 154 | Logger::emergency($message); 155 | $this->assertPop('EMERGENCY', $message); 156 | Logger::alert($message); 157 | $this->assertPop('ALERT', $message); 158 | Logger::critical($message); 159 | $this->assertPop('CRITICAL', $message); 160 | 161 | Logger::error($message); 162 | $this->assertPop('ERROR', $message); 163 | Logger::warning($message); 164 | $this->assertPop('WARN', $message); 165 | Logger::warn($message); 166 | $this->assertPop('WARN', $message); 167 | 168 | Logger::notice($message); 169 | $this->assertPop('NOTICE', $message); 170 | Logger::info($message); 171 | $this->assertPop('INFO', $message); 172 | Logger::debug($message); 173 | $this->assertPop('DEBUG', $message); 174 | } 175 | 176 | /** 177 | * @depends testWrite 178 | */ 179 | public function testClear() 180 | { 181 | if ('file' === static::$type) { 182 | $dir = $this->app->getConfig()->runtime.'log/'; 183 | Logger::write('test', 'ERROR'); 184 | Logger::write('test', 'ALERT'); 185 | $this->assertGreaterThan(2, count(scandir($dir))); 186 | Logger::clear(); 187 | $this->assertCount(2, scandir($dir)); 188 | } else { 189 | $this->markTestSkipped(static::$type.' log [无需测试]'); 190 | } 191 | } 192 | 193 | /** 194 | * @requires OS Linux 195 | * @covers ::getFile 196 | */ 197 | public function testDirMode() 198 | { 199 | if ('file' === static::$type) { 200 | $dir = APP_PATH.DIRECTORY_SEPARATOR.'runtime'.DIRECTORY_SEPARATOR.'log'.DIRECTORY_SEPARATOR; 201 | $this->assertFileMode($dir, 0777); 202 | } else { 203 | $this->markTestSkipped(static::$type.' log [无需检查权限]'); 204 | } 205 | } 206 | 207 | protected static function getLogFile($key = null) 208 | { 209 | if ($key) { 210 | return static::app()->getConfig()->runtime.'log/'.date('y-m-d-').strtoupper($key).'.log'; 211 | } 212 | return APP_PATH.'/runtime/logger_test_error_log.txt'; 213 | } 214 | 215 | protected function assertPop($level, $msg) 216 | { 217 | $message = array_pop($this->message); 218 | return $this->assertSame($message, array(strtoupper($level) => $msg)); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /tests/library/Service/DatabaseTest.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 33 | static::$db['mysql']->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 34 | } 35 | if (!isset(static::$db['sqlite'])) { 36 | $dsn = getenv('SQLITE_DSN') ?: 'sqlite:'.APP_PATH.'/runtime/yyf.db'; 37 | 38 | static::$db['sqlite'] = new Database($dsn); 39 | static::$db['sqlite']->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 40 | static::$db['sqlite']->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 41 | } 42 | } 43 | 44 | public static function tearDownAfterClass() 45 | { 46 | foreach (static::$db as $k => &$db) { 47 | $db = null; 48 | unset(static::$db[$k]); 49 | } 50 | } 51 | 52 | public function testConnection() 53 | { 54 | foreach (static::$db as $key => &$db) { 55 | $this->assertNotEmpty($db, $key.'database connected failed!'); 56 | } 57 | $this->assertCount(2, static::$db); 58 | } 59 | 60 | /** 61 | * @depends testConnection 62 | * @covers ::query 63 | * @covers ::column 64 | */ 65 | public function testQuery() 66 | { 67 | foreach (static::$db as &$db) { 68 | $this->queryAssert($db); 69 | $this->columnAssert($db); 70 | } 71 | } 72 | 73 | /** 74 | * @depends testConnection 75 | * @covers ::exec 76 | * @covers ::execute 77 | * @covers ::getType 78 | * 79 | * @param mixed $db 80 | */ 81 | public function testExec($db) 82 | { 83 | foreach (static::$db as &$db) { 84 | $this->execAssert($db); 85 | } 86 | } 87 | 88 | /** 89 | * @depends testQuery 90 | * @covers ::isOK 91 | * @covers ::errorInfo 92 | * @covers ::error 93 | * 94 | * @param mixed $db 95 | */ 96 | public function testError($db) 97 | { 98 | foreach (static::$db as &$db) { 99 | $this->errorAssert($db); 100 | } 101 | } 102 | 103 | /** 104 | * @depends testQuery 105 | * @covers ::isOK 106 | * @covers ::transact 107 | * 108 | * @param mixed $db 109 | */ 110 | public function testTransiction($db) 111 | { 112 | foreach (static::$db as $key => &$db) { 113 | $this->transactAssert($db, $key); 114 | } 115 | } 116 | 117 | public function queryAssert($db) 118 | { 119 | $this->assertCount(2, $db->query('SELECT * FROM user')); 120 | $dataset = array( 121 | array( 122 | 'id' => 1, 123 | 'account' => 'newfuture', 124 | 'name' => 'New Future', 125 | ) 126 | ); 127 | $user = $db->query('SELECT id,account,name FROM user WHERE id=?', array(1)); 128 | $this->assertEquals($dataset, $user); 129 | $user = $db->query('SELECT id,account,name FROM user WHERE id=:id', array(':id' => 1)); 130 | $this->assertEquals($dataset, $user); 131 | $user = $db->query('SELECT id,account,name FROM user WHERE id=:id LIMIT 1', array('id' => 1), false); 132 | $this->assertEquals($dataset[0], $user); 133 | } 134 | 135 | public function columnAssert($db) 136 | { 137 | $name = '测试'; 138 | $this->assertSame($name, $db->column('SELECT name FROM user WHERE id=2')); 139 | $this->assertSame($name, $db->column('SELECT name FROM user WHERE id=?', array(2))); 140 | $this->assertSame($name, $db->column('SELECT name FROM user WHERE id=:id', array('id' => 2))); 141 | $this->assertSame($name, $db->column('SELECT name FROM user WHERE id=:id', array(':id' => 2))); 142 | } 143 | 144 | public function execAssert($db) 145 | { 146 | $data = array( 147 | 'id' => 3, 148 | 'account' => 'tester', 149 | 'name' => 'Database Test', 150 | 'created_at' => date('Y-m-d h:i:s'), 151 | ); 152 | 153 | $insert = 'INSERT INTO`user`(`id`,`name`,`account`,`created_at`)VALUES(:id,:name,:account,:created_at)'; 154 | $this->assertSame(1, $db->exec($insert, $data)); 155 | $this->assertEquals(3, $db->column('SELECT COUNT(*) FROM user')); 156 | $user = $db->query('SELECT id,account,name,created_at FROM user WHERE id=?', array($data['id']), false); 157 | $this->assertEquals($data, $user); 158 | 159 | $update = 'UPDATE`user`SET`name`= ? WHERE(`id`= ?)'; 160 | $UPDATE_NAME = 'UPDATE_TEST name'; 161 | $this->assertSame(1, $db->exec($update, array($UPDATE_NAME, 3))); 162 | $data['name'] = $UPDATE_NAME; 163 | $user = $db->query('SELECT id,account,name,created_at FROM user WHERE id=?', array($data['id']), false); 164 | $this->assertEquals($data, $user); 165 | 166 | $delete = 'DELETE FROM`user`WHERE(`id`= :id)'; 167 | $this->assertSame(1, $db->exec($delete, array(':id' => $data['id']))); 168 | $this->assertEquals(2, $db->column('SELECT COUNT(*) FROM user')); 169 | } 170 | 171 | public function errorAssert($db) 172 | { 173 | $this->assertTrue($db->isOk()); 174 | $err = $db->errorInfo(); 175 | $this->assertEquals(0, $err[0]); 176 | 177 | $db->query('SELECT xxxx FROM user WHERE id=?', array(1)); 178 | $this->assertFalse($db->isOk()); 179 | 180 | $db->query('SELECT id FROM user WHERE id=1'); 181 | $this->assertTrue($db->isOK()); 182 | 183 | $db->exec('DELETE xxxx FROM xx'); 184 | $this->assertFalse($db->isOk()); 185 | $err = $db->errorInfo(); 186 | $this->assertNotEquals(0, $err[0]); 187 | 188 | $db->query('SELECT id FROM user WHERE id=1'); 189 | $this->assertTrue($db->isOK()); 190 | } 191 | 192 | public function transactAssert($db, $type) 193 | { 194 | if ('mysql' === $type || $db->exec('PRAGMA foreign_keys = ON')) { 195 | try { 196 | $db->beginTransaction(); 197 | if ($db->exec('DELETE FROM`user`WHERE(`id`= ?)', array(1)) === false) { 198 | $db->rollBack(); 199 | } else { 200 | $db->exec( 201 | 'INSERT INTO`user`(`name`,`account`)VALUES(:name,:account)', 202 | array('name' => 'New Future', 'account' => 'new_future') 203 | ); 204 | $db->commit(); 205 | } 206 | } catch (Exception $e) { 207 | $db->rollBack(); 208 | } 209 | $this->assertFalse($db->isOk(), $type); 210 | $this->assertSame('newfuture', $db->column('SELECT account FROM user WHERE id=1'), $type); 211 | } 212 | 213 | $result = $db->transact(function ($d) { 214 | $d->exec('INSERT INTO`user`(`id`,`name`)VALUES(?,?)', array(2, 'new test')); 215 | return $db->exec('DELETE FROM`user`WHERE(`id`= ?)', array(2)); 216 | }); 217 | $this->assertFalse($result, $type.'事务失败返回结果不为false'); 218 | $this->assertFalse($db->isOk(), $type); 219 | $this->assertEquals(2, $db->column('SELECT COUNT(*) FROM user'), $type); 220 | $this->assertTrue($db->isOk(), $type); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /tests/library/OrmTest.php: -------------------------------------------------------------------------------- 1 | User = new Orm('user'); 24 | } 25 | 26 | public function tearDown() 27 | { 28 | unset($this->User); 29 | } 30 | 31 | public function testSelect() 32 | { 33 | $u = $this->User->select('*'); 34 | $this->assertCount(2, $u); 35 | $u = $this->User->limit(1)->select('id,name'); 36 | $this->assertCount(1, $u); 37 | $this->assertCount(2, $u[0]); 38 | $this->assertArrayHasKey('id', $u[0]); 39 | $this->assertArrayHasKey('name', $u[0]); 40 | } 41 | 42 | public function testFind() 43 | { 44 | $u = $this->User->find(1); 45 | $this->assertArrayHasKey('id', $u->get()); 46 | $this->assertEquals(1, $u['id']); 47 | 48 | $u = $this->User->field('id,name')->find(2); 49 | $this->assertEquals(2, $u->id); 50 | $u = $u->get(); 51 | $this->assertCount(static::USER_AMOUNT, $u); 52 | $this->assertArrayHasKey('id', $u); 53 | $this->assertArrayHasKey('name', $u); 54 | } 55 | 56 | public function testGet() 57 | { 58 | $this->User->where('id', 1); 59 | $this->assertEquals(1, $this->User->get('id')); 60 | $this->assertEquals(1, $this->User->id); 61 | $this->assertSame('newfuture', $this->User->get('account')); 62 | } 63 | 64 | //插入测试数据 65 | public function insertProvider() 66 | { 67 | return array( 68 | array( 69 | array( 70 | 'id' => 3, 71 | 'account' => 'tester', 72 | 'name' => 'test '.substr(__CLASS__, -8), 73 | )), 74 | array( 75 | array( 76 | 'id' => 4, 77 | 'account' => 'new_tester', 78 | 'name' => 'New Tester 4', 79 | )), 80 | ); 81 | } 82 | 83 | /** 84 | * @dataProvider insertProvider 85 | * 86 | * @param array $data 87 | */ 88 | public function testInsert($data) 89 | { 90 | $id = $this->user()->insert($data); 91 | $this->assertEquals($data['id'], $id); 92 | return $id; 93 | } 94 | 95 | /** 96 | * @dataProvider insertProvider 97 | * @depends testInsert 98 | * 99 | * @param mixed $data 100 | */ 101 | public function testDelete($data) 102 | { 103 | if ($data['id'] < 4) { 104 | $result = $this->User->find($data['id']); 105 | $this->assertEquals($data['id'], $result->id, 'find the id'); 106 | $this->assertEquals(1, $result->delete(), 'delete by data'); 107 | } else { 108 | $result = $this->User->delete($data['id']); 109 | $this->assertEquals(1, $result, 'delete by id'); 110 | } 111 | } 112 | 113 | /** 114 | * @dataProvider insertProvider 115 | * 116 | * @param array $data 117 | */ 118 | public function testAdd($data) 119 | { 120 | foreach ($data as $key => $value) { 121 | $this->User->set($key, $value); 122 | } 123 | 124 | $this->assertInstanceOf('Orm', $this->User->add()); 125 | 126 | $this->assertEquals(1, $this->User->delete($data['id'])); 127 | } 128 | 129 | public function testInsertAll() 130 | { 131 | $data = $this->insertProvider(); 132 | $data = array_column($data, 0); 133 | $this->assertEquals(count($data), $this->User->insertAll($data)); 134 | 135 | foreach ($data as $u) { 136 | $this->assertEquals(1, $this->User->where($u)->delete()); 137 | } 138 | } 139 | 140 | //跟新测试数据 141 | public function updateProvider() 142 | { 143 | return array( 144 | array( 145 | 1, 146 | array( 147 | 'account' => 'tester_update', 148 | ), 149 | ), 150 | array( 151 | 2, 152 | array( 153 | 'name' => 'update Tester', 154 | ) 155 | ), 156 | ); 157 | } 158 | 159 | /** 160 | * @dataProvider updateProvider 161 | * 162 | * @param mixed $con 163 | */ 164 | public function testUpdate($con, array $data) 165 | { 166 | if (!is_array($con)) { 167 | $con = array('id' => $con); 168 | } 169 | $old_data = $this->User->where($con)->find()->get(); 170 | $this->assertEquals(1, $this->user()->where($con)->update($data)); 171 | foreach ($data as $key => $value) { 172 | $this->assertSame($value, $this->user()->where($con)->get($key)); 173 | } 174 | $this->assertEquals(1, $this->user()->where($con)->update($old_data)); 175 | } 176 | 177 | /** 178 | * @dataProvider updateProvider 179 | * 180 | * @param mixed $id 181 | */ 182 | public function testSave($id, array $data) 183 | { 184 | $old_data = $this->User->find($id)->get(); 185 | $this->assertInstanceOf('Orm', $this->user()->set($data)->save($id)); 186 | $user = $this->user()->where('id', $id); 187 | foreach ($data as $key => $value) { 188 | $this->assertSame($value, $user->get($key)); 189 | } 190 | $this->assertEquals(1, $user->update($old_data)); 191 | } 192 | 193 | /** 194 | * @dataProvider updateProvider 195 | * 196 | * @param mixed $id 197 | */ 198 | public function testPut($id, array $data) 199 | { 200 | $User = $this->User->find($id); 201 | foreach ($data as $key => $value) { 202 | $old_value = $User->get($key); 203 | $this->assertSame(1, $User->put($key, $value)); 204 | $ORM = new Orm('user'); 205 | $this->assertEquals($value, $ORM->where('id', $id)->get($key)); 206 | $this->assertSame(1, $User->put($key, $old_value)); 207 | } 208 | } 209 | 210 | public function testIncrement() 211 | { 212 | $id = 1; 213 | $User = $this->User->field('id')->find($id); 214 | $status = $User->get('status'); 215 | $this->assertEquals(1, $User->increment('status')); 216 | $this->assertEquals($status + 1, $this->user()->where('id', $id)->get('status')); 217 | return $id; 218 | } 219 | 220 | /** 221 | * @depends testIncrement 222 | * 223 | * @param mixed $id 224 | */ 225 | public function testDecrement($id) 226 | { 227 | $User = $this->User->where('id', $id); 228 | $status = $User->get('status'); 229 | $this->assertEquals(1, $User->decrement('status'), "id:$id"); 230 | $this->assertEquals($status - 1, $User->get('status'), "id:$id"); 231 | } 232 | 233 | public function testCount() 234 | { 235 | $this->assertEquals(static::USER_AMOUNT, $this->User->count()); 236 | } 237 | 238 | public function testSum() 239 | { 240 | $this->assertEquals(1, $this->User->sum('status')); 241 | } 242 | 243 | public function testOrder() 244 | { 245 | $user = $this->User->order('id')->select('id'); 246 | $this->assertEquals(1, $user[0]['id']); 247 | 248 | $user = $this->User->order('id', 'DESC')->select('id'); 249 | $this->assertEquals(2, $user[0]['id']); 250 | } 251 | 252 | public function testLimit() 253 | { 254 | $user = $this->User->limit(2)->select('id'); 255 | $this->assertCount(2, $user); 256 | $user = $this->User->limit(1, 1)->select('id'); 257 | $this->assertCount(1, $user); 258 | $this->assertEquals(2, $user[0]['id']); 259 | } 260 | 261 | public function testPage() 262 | { 263 | $user = $this->User->Page(1, 1)->select('id'); 264 | $this->assertCount(1, $user); 265 | $this->assertEquals(1, $user[0]['id']); 266 | $user = $this->User->limit(2, 1)->select('id'); 267 | $this->assertCount(1, $user); 268 | $this->assertEquals(2, $user[0]['id']); 269 | } 270 | 271 | protected function user() 272 | { 273 | return $this->User->clear(); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /library/Logger.php: -------------------------------------------------------------------------------- 1 | toArray(); 77 | $config['type'] = strtolower($config['type']); 78 | $config['allow'] = explode(',', strtoupper($config['allow'])); 79 | isset($config['timezone']) && date_default_timezone_set($config['timezone']); 80 | } 81 | 82 | if (in_array($level, $config['allow'])) { 83 | switch ($config['type']) { 84 | case 'system': // 系统日志 85 | return error_log($level.': '.$msg); 86 | 87 | case 'file': //文件日志 88 | return file_put_contents( 89 | Logger::getFile($level), 90 | date('[d-M-Y H:i:s e] (').$_SERVER['REQUEST_URI'].') '.$msg.PHP_EOL, 91 | FILE_APPEND); 92 | 93 | case 'sae': //sae日志 94 | return sae_debug($level.': '.$msg); 95 | 96 | default: 97 | throw new Exception('未知日志类型'.$config['type']); 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * 清空日志(仅对文件模式有效) 104 | */ 105 | public static function clear() 106 | { 107 | $type = Config::get('log.type'); 108 | if ('file' === $type) { 109 | File::cleanDir(Config::get('runtime').DIRECTORY_SEPARATOR.'log'.DIRECTORY_SEPARATOR); 110 | } elseif ('system' == $type) { 111 | if ($file = ini_get('error_log')) { 112 | file_put_contents($file, ''); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * System is unusable. 119 | * 120 | * @param string $message 121 | * @param array $context 122 | * 123 | * @return bool [写入状态] 124 | */ 125 | public static function emergency($message, array $context = array()) 126 | { 127 | return static::log('EMERGENCY', $message, $context); 128 | } 129 | 130 | /** 131 | * Action must be taken immediately. 132 | * 133 | * Example: Entire website down, database unavailable, etc. This should 134 | * trigger the SMS alerts and wake you up. 135 | * 136 | * @param string $message 137 | * @param array $context 138 | * 139 | * @return bool [写入状态] 140 | */ 141 | public static function alert($message, array $context = null) 142 | { 143 | return static::log('ALERT', $message, $context); 144 | } 145 | 146 | /** 147 | * Critical conditions. 148 | * 149 | * Example: Application component unavailable, unexpected exception. 150 | * 151 | * @param string $message 152 | * @param array $context 153 | * 154 | * @return bool [写入状态] 155 | */ 156 | public static function critical($message, array $context = null) 157 | { 158 | return static::log('CRITICAL', $message, $context); 159 | } 160 | 161 | /** 162 | * Runtime errors that do not require immediate action but should typically 163 | * be logged and monitored. 164 | * 165 | * @param string $message 166 | * @param array $context 167 | * 168 | * @return bool [写入状态] 169 | */ 170 | public static function error($message, array $context = null) 171 | { 172 | return static::log('ERROR', $message, $context); 173 | } 174 | 175 | /** 176 | * Exceptional occurrences that are not errors. 177 | * 178 | * Example: Use of deprecated APIs, poor use of an API, undesirable things 179 | * that are not necessarily wrong. 180 | * 181 | * @param string $message 182 | * @param array $context 183 | * 184 | * @return bool [写入状态] 185 | */ 186 | public static function warning($message, array $context = null) 187 | { 188 | return static::log('WARN', $message, $context); 189 | } 190 | 191 | /** 192 | * Exceptional occurrences that are not errors. 193 | * 194 | * Example: Use of deprecated APIs, poor use of an API, undesirable things 195 | * that are not necessarily wrong. 196 | * 197 | * @param string $message 198 | * @param array $context 199 | * 200 | * @return bool [写入状态] 201 | */ 202 | public static function warn($message, array $context = null) 203 | { 204 | return static::log('WARN', $message, $context); 205 | } 206 | 207 | /** 208 | * Normal but significant events. 209 | * 210 | * @param string $message 211 | * @param array $context 212 | * 213 | * @return null 214 | */ 215 | public static function notice($message, array $context = null) 216 | { 217 | return static::log('NOTICE', $message, $context); 218 | } 219 | 220 | /** 221 | * Interesting events. 222 | * 223 | * Example: User logs in, SQL logs. 224 | * 225 | * @param string $message 226 | * @param array $context 227 | * 228 | * @return null 229 | */ 230 | public static function info($message, array $context = null) 231 | { 232 | return static::log('INFO', $message, $context); 233 | } 234 | 235 | /** 236 | * Detailed debug information. 237 | * 238 | * @param string $message 239 | * @param array $context 240 | * 241 | * @return null 242 | */ 243 | public static function debug($message, array $context = null) 244 | { 245 | return static::log('DEBUG', $message, $context); 246 | } 247 | 248 | /** 249 | * Logs with an arbitrary level. 250 | * 251 | * @param mixed $level 252 | * @param string $message 253 | * @param array $context 254 | * 255 | * @return null 256 | */ 257 | public static function log($level, $message, array $context = null) 258 | { 259 | if ($context) { 260 | $replace = array(); 261 | foreach ($context as $key => &$val) { 262 | $replace['{'.$key.'}'] = is_scalar($val) || method_exists($val, '__toString') ? $val : json_endcode($val, 256); 263 | } 264 | $message = strtr($message, $replace); 265 | } elseif (!(is_scalar($message) || method_exists($message, '__toString'))) { 266 | //无法之间转成字符的数据json格式化 267 | $message = json_encode($message, 256); //256isJSON_UNESCAPED_UNICODE 兼容php5.3 268 | } 269 | return Logger::write($message, $level); 270 | } 271 | 272 | /** 273 | * 获取写入流 274 | * 275 | * @param string $tag [日志级别] 276 | * 277 | * @return string 写入的文件 278 | */ 279 | private static function getFile($tag) 280 | { 281 | $files = &Logger::$_files; 282 | if (!isset($files[$tag])) { 283 | /*打开文件流*/ 284 | if (!isset($files['_dir'])) { 285 | //日志目录 286 | umask(intval(Config::get('umask', 0077), 8)); 287 | $logdir = isset(Logger::$_conf['path']) ? Logger::$_conf['path'] : Config::get('runtime').'log'; 288 | if (!is_dir($logdir)) { 289 | mkdir($logdir, 0777, true); 290 | } 291 | $files['_dir'] = $logdir.DIRECTORY_SEPARATOR.date('y-m-d-'); 292 | 293 | //如果没有设置REQUEST_URI[命令行模式],自动补为null 294 | isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] = null; 295 | } 296 | 297 | $file = $files['_dir'].$tag.'.log'; 298 | $files[$tag] = $file; 299 | } 300 | return $files[$tag]; 301 | } 302 | } 303 | --------------------------------------------------------------------------------