/** * @return void */ function __construct() { $ok = false; if ($this->factory->getConfFile() === null) { $key = 'phprs_route3_' . sha1(serialize($this->factory->getConf())); } else { $key = 'phprs_route3_' . sha1($this->factory->getConfFile()); } $this->impl = $this->cache->get($key, $ok); if ($ok && is_object($this->impl)) { Logger::info("router loaded from cache"); return; } $this->impl = $this->factory->create('caoym\\phprs\\Router'); //缓存过期判断依据 //检查接口文件是否有修改\新增 $check_files = array_values($this->impl->getApiFiles()); $check_dirs = array(); foreach ($check_files as $file) { if (is_file($file)) { $check_dirs[] = dirname($file); } } $check_files = array_merge($check_files, $check_dirs); $check_files[] = $this->factory->getConfFile(); $this->cache->set($key, $this->impl, 0, new FileExpiredChecker($check_files)); //接口文件或者配置文件修改 }
/** * 加载api类 * @param array $routes * @param string $class_file * @param string $class_name * @param string $method * @return void */ private function loadApi(&$routes, $class_file, $class_name, $method = null) { Verify::isTrue(is_file($class_file), $class_file . ' is not an exist file'); Logger::debug("attempt to load api: {$class_name}, {$class_file}"); $this->class_loader->addClass($class_name, $class_file); $api = $this->factory->create('caoym\\phprs\\Container', array($class_name, $method), null, null); foreach ($api->routes as $http_method => $route) { if (!isset($routes[$http_method])) { $routes[$http_method] = new HttpRouterEntries(); } $cur = $routes[$http_method]; foreach ($route as $entry) { $realpath = preg_replace('/\\/+/', '/', '/' . $entry[0]); Verify::isTrue($cur->insert($realpath, $entry[1]), "repeated path {$realpath}"); Logger::debug("api: {$http_method} {$realpath} => {$class_name}::{$entry[1]->method_name} ok"); } } Logger::debug("load api: {$class_name}, {$class_file} ok"); }
/** * 修改用户 * @route({"POST","/current"}) * * @param({"password", "$._POST.password"}) 密码,可选 * @param({"alias", "$._POST.alias"}) 昵称,可选 * @param({"avatar", "$._FILES.avatar.tmp_name"}) 头像,需要更新头像时指定,可选 * @param({"token", "$._COOKIE.token"}) 登录后获取的cookie * * @throws({"caoym\util\exceptions\Forbidden","res", "403 Forbidden", {"error":"Forbidden"}}) code验证失败 * * @throws({"AliasConflict","status", "409 Conflict", {"error":"AliasConflict"}}) 昵称冲突 * */ public function updateUser($token, $alias = null, $password = null, $avatar = null) { $token = $this->factory->create('Tokens')->getToken($token); Verify::isTrue(isset($token['uid']) && $token['uid'] != 0, new Forbidden("invalid uid {$token['uid']}")); if ($avatar) { $avatar = $this->uploadAvatar($avatar); } $uid = $token['uid']; $pdo = $this->db; $pdo->beginTransaction(); try { if ($alias || $avatar) { $sets = array(); $params = array(); if ($alias) { //昵称不能重复 $res = Sql::select('uid')->from('pre_common_member_profile')->where('realname = ? AND uid <> ?', $alias, $uid)->forUpdate()->get($pdo); Verify::isTrue(count($res) == 0, new AliasConflict("alias {$alias} conflict")); $params['realname'] = $alias; } //TODO 头像 if ($avatar) { $params['avatar'] = $avatar; } Sql::update('pre_common_member_profile')->setArgs($params)->where('uid = ?', $uid)->exec($pdo); } if ($password !== null) { Sql::update('uc_members')->setArgs(['password' => $password, 'salt' => ''])->where('uid=?', $uid)->exec($pdo); } $pdo->commit(); } catch (Exception $e) { Logger::warning("updateUser({$uid}) failed with " . $e->getMessage()); $pdo->rollBack(); throw $e; } }
/** * * @param string $class 类名 * @param string $method ==null时load所有方法, !==null时load指定方法 */ public function load($class, $method) { $this->class = $class; //获取方法 $reflection = new \ReflectionClass($class); $reader = new AnnotationReader($reflection); $class_ann = $reader->getClassAnnotations($reflection); Verify::isTrue(isset($class_ann['path']), $class . ' @path not found'); Verify::isTrue(count($class_ann['path']) === 1, $class . ' @path ambiguity'); $path = $class_ann['path'][0]['value']; $this->path = $path; $specified = $method; foreach ($reflection->getMethods() as $method) { if ($specified !== null && $specified !== $method->getName()) { Logger::DEBUG("specified method: {$specified}, ignore {$class}::{$method->getName()}"); continue; } $anns = $reader->getMethodAnnotations($method, false); if (!isset($anns['route'])) { Logger::DEBUG("no @route, ignore {$class}::{$method->getName()}"); continue; } //Verify::isTrue(count($anns['route']) == 1, "$class::{$method->getName()} @route repeated set"); $invoker = $this->factory->create('caoym\\phprs\\Invoker', array($this, $method)); foreach ($anns['route'] as $ann) { $route = $ann['value']; Verify::isTrue(is_array($route) && count($route) == 2, "{$class}::{$method->getName()} syntax error @route, example: @route({\"GET\" ,\"/api?a=2\"})"); list($http_method, $uri) = $route; $this->routes[$http_method][] = array($path . '/' . $uri, $invoker); } foreach ($anns as $type => $v) { if ($type == 'route') { continue; } $id = 0; foreach ($v as $ann) { if (!is_array($ann) || !isset($ann['value'])) { continue; } $invoker->bind($id++, $type, $ann['value']); continue; } } //检查是否所有必须的参数均已绑定 $invoker->check(); } //属性注入 /*foreach ($reflection->getProperties() as $property ){ foreach ( $reader->getPropertyAnnotations($property) as $id => $ann){ if($id !== 'inject') { continue;} $name = $property->getName(); if($name == "ioc_factory"){// ioc_factory由工厂负责注入 //TODO: 用@ioc_factory替代ioc_factory continue; } Verify::isTrue(count($ann) ===1, "$class::$name ambiguity @inject"); Verify::isTrue(isset($ann[0]['value']), "$class::$name invalid @inject"); Verify::isTrue(is_string($ann[0]['value']), "$class::$name invalid @inject"); $this->injectors[] = new Injector($this, $name, $ann[0]['value']); } }*/ }
/** * 执行API * * @param Request $request 请求 * @param Response $response 响应 */ public function __invoke($request, &$response) { $args = array(); //绑定参数和返回值 $this->bind['param']->bind($request, $response, $args); $this->bind['return']->bind($request, $response, $args); //利用参数绑定的能力,提取@cache注释的信息 $cache_ttl = 0; $cache_check = null; $cache_res = new Response(array('ttl' => function ($param) use(&$cache_ttl) { $cache_ttl = $param; }, 'check' => function ($param) use(&$cache_check) { $cache_check = $param; }, 'body' => function ($_ = null) { })); $this->bind['cache']->bind($request, $cache_res, $args); $use_cache = !$this->bind['cache']->isEmpty(); $given = count($args); if ($given === 0) { $required_num = 0; } else { ksort($args, SORT_NUMERIC); end($args); $required_num = key($args) + 1; } Verify::isTrue($given === $required_num, new BadRequest("{$this->ins->class}::{$this->method_name} {$required_num} params required, {$given} given")); // 变量没给全 $cache_key = null; if ($use_cache) { //输入参数包括函数参数和类注入数据两部分 //所以以这些参数的摘要作为缓存的key $injected = $this->ins->getInjected(); $cache_res->flush(); //取出cache参数 $cache_key = "invoke_{$this->ins->class}_{$this->method_name}_" . sha1(serialize($args) . serialize($injected) . $cache_ttl); $succeeded = false; $data = $this->checkAbleCache->get($cache_key, $succeeded); if ($succeeded && is_array($data)) { $response->setBuffer($data); $response->flush(); Logger::info("{$this->ins->class}::{$this->method_name} get response from cache {$cache_key}"); return; } } $impl = $this->ins->getImpl($request); // if (!$this->bind['throws']->isEmpty()) { try { $res = call_user_func_array(array($impl, $this->method_name), $args); } catch (\Exception $e) { $response->clear(); // 清除之前绑定的变量, 异常发生时可能已经写入了一些参数 $this->bind['throws']->bind($request, $response, $e); $response['break'][][0] = true; $response->flush(); return; } } else { $res = call_user_func_array(array($impl, $this->method_name), $args); } $this->bind['return']->setReturn($res); if ($use_cache) { $this->checkAbleCache->set($cache_key, $response->getBuffer(), $cache_ttl, $cache_check); Logger::info("{$this->ins->class}::{$this->method_name} set response to cache {$cache_key}, ttl={$cache_ttl}, check=" . ($cache_check === null ? 'null' : get_class($cache_check))); } $response->flush(); }
<?php use caoym\util\IoCFactory; use caoym\util\Logger; /*************************************************************************** * * Copyright (c) 2014 . All Rights Reserved * **************************************************************************/ require_once __DIR__ . '/../../../lib/caoym/Autoload.php'; Logger::$writer = Logger::$to_echo; /** * RestfulApiContainer test case. */ class RestfulApTest extends PHPUnit_Framework_TestCase { /** * Tests RestfulApiContainer->invoke() */ public function testInvoke() { $factory = new IoCFactory(array('caoym\\phprs\\Router' => array('properties' => array('api_path' => dirname(__FILE__) . '/rest_test', 'apis' => 'MyTestApi', 'api_method' => 'func')))); $route = $factory->create('caoym\\phprs\\Router'); //$route = new \caoym\phprs\Router(dirname(__FILE__).'/rest_test', 'MyTestApi', 'func'); //输入数据 $req_buffer = ['_SERVER' => ['REQUEST_METHOD' => 'GET', 'REQUEST_URI' => '/testapi/func?a=1&b=2'], 'header' => 'test header', 'arg0' => 'test arg0', 'arg1' => 'test arg1']; $res_buffer = array(); // 输出数据缓存 $sender = array('status' => function () use(&$res_buffer) { $res_buffer[] = ['status', func_get_args()]; }, 'header' => function () use(&$res_buffer) {