/** * @return void */ function __construct() { $ok = false; if ($this->factory->getConfFile() === null) { $key = 'phprs_route3_' . sha1(serialize($this->factory->getConf())); } else { $key = 'phprs_route3_' . sha1($this->factory->getConfFile()); } $this->impl = $this->cache->get($key, $ok); if ($ok && is_object($this->impl)) { Logger::info("router loaded from cache"); return; } $this->impl = $this->factory->create('phprs\\Router'); //缓存过期判断依据 //检查接口文件是否有修改\新增 $check_files = array_values($this->impl->getApiFiles()); $check_dirs = array(); foreach ($check_files as $file) { if (is_file($file)) { $check_dirs[] = dirname($file); } } $check_files = array_merge($check_files, $check_dirs); $check_files[] = $this->factory->getConfFile(); $this->cache->set($key, $this->impl, 0, new FileExpiredChecker($check_files)); //接口文件或者配置文件修改 }
/** * * @param \Exception|string $e * @throws unknown */ public static function e($e) { if ($e === null || is_string($e)) { Logger::warning($e); throw new \Exception($e); } else { Logger::warning($e->__toString()); 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('phprs\\Invoker', array($this, $method)); foreach ($anns['route'] as $ann) { $route = $ann['value']; Verify::isTrue(is_array($route) && (count($route) == 2 || count($route) == 3), "{$class}::{$method->getName()} syntax error @route, example: @route({\"GET\" ,\"/api?a=2\"}) or @route({\"GET\" ,\"/api?a=2\",true})"); list($http_method, $uri, $strict) = $route + [null, null, null]; $this->routes[$http_method][] = [$path . '/' . $uri, $invoker, $strict]; } foreach ($anns as $type => $v) { if ($type == 'route') { continue; } $id = 0; foreach ($v as $ann) { if (!is_array($ann) || !isset($ann['value'])) { continue; } $invoker->bind($id++, $type, $ann['value']); continue; } } //检查是否所有必须的参数均已绑定 $invoker->check(); } //属性注入 /*foreach ($reflection->getProperties() as $property ){ foreach ( $reader->getPropertyAnnotations($property) as $id => $ann){ if($id !== 'inject') { continue;} $name = $property->getName(); if($name == "ioc_factory"){// ioc_factory由工厂负责注入 //TODO: 用@ioc_factory替代ioc_factory continue; } Verify::isTrue(count($ann) ===1, "$class::$name ambiguity @inject"); Verify::isTrue(isset($ann[0]['value']), "$class::$name invalid @inject"); Verify::isTrue(is_string($ann[0]['value']), "$class::$name invalid @inject"); $this->injectors[] = new Injector($this, $name, $ann[0]['value']); } }*/ }
/** * 执行API * * @param Request $request 请求 * @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(); }
/** * modify user's information * @route({"POST","/current"}) * * @param({"password", "$._POST.password"}) modify password, optional * @param({"alias", "$._POST.alias"}) modify alias, optional * @param({"avatar", "$._FILES.avatar.tmp_name"}) modify avatar, optional * @param({"token", "$._COOKIE.token"}) used for auth * * @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden", {"error":"Forbidden"}}) invalid cookie * * @throws({"AliasConflict","status", "409 Conflict", {"error":"AliasConflict"}}) alias conflict * */ 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; } 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; } }
<?php use phprs\util\IoCFactory; use phprs\util\Logger; require_once __DIR__ . '/../../lib/phprs/AutoLoad.php'; Logger::$writer = Logger::$to_echo; class ClassA { /** * @property */ public $classB; } class ClassB { /** * @property */ public $classA; } /** * IocFactory test case. */ class IocFactoryTest extends PHPUnit_Framework_TestCase { public function testCyclicDependenciesWithoutSingleton() { $this->setExpectedException('Exception'); $factory = new IoCFactory(array('ClassA' => array('properties' => array('classB' => '@ClassB')), 'ClassB' => array('properties' => array('classA' => '@ClassA')))); $classA = $factory->create('ClassA'); }
/** * 加载api类 * @param array $routes * @param string $class_file * @param string $class_name * @param string $method * @return void */ private function loadApi(&$routes, $class_file, $class_name, $method = null) { Verify::isTrue(is_file($class_file), $class_file . ' is not an exist file'); Logger::debug("attempt to load api: {$class_name}, {$class_file}"); $this->class_loader->addClass($class_name, $class_file); $api = null; if ($this->ignore_load_error) { try { $api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null); } catch (\Exception $e) { Logger::warning("load api: {$class_name}, {$class_file} failed with " . $e->getMessage()); return; } } else { $api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null); } foreach ($api->routes as $http_method => $route) { if (!isset($routes[$http_method])) { $routes[$http_method] = new HttpRouterEntries(); } $cur = $routes[$http_method]; foreach ($route as $entry) { list($uri, $invoke, $strict) = $entry; $realpath = preg_replace('/\\/+/', '/', '/' . $uri); $strict = $strict === null ? $this->default_strict_matching : $strict; Verify::isTrue($cur->insert($realpath, $invoke, $strict), "repeated path {$realpath}"); Logger::debug("api: {$http_method} {$realpath} => {$class_name}::{$entry[1]->method_name} ok, strict:{$strict}"); } } Logger::debug("load api: {$class_name}, {$class_file} ok"); }