Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
minified BEAR.Sunday Demo.Helloworld app
<?php
namespace Ray\Di { interface InstanceInterface { public function getInstance($class); } } namespace Ray\Di { use Doctrine\Common\Cache\Cache; interface InjectorInterface extends InstanceInterface { public function getContainer(); public function getModule(); public function setModule(AbstractModule $module); public function getLogger(); } } namespace Ray\Di { use Aura\Di\ContainerInterface; use Aura\Di\Lazy; use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\CachedReader; use Doctrine\Common\Cache\Cache; use LogicException; use Ray\Aop\Bind; use Ray\Aop\BindInterface; use Ray\Aop\Compiler; use Ray\Aop\CompilerInterface; use Ray\Di\Exception; use ReflectionClass; use ReflectionException; use ReflectionMethod; use SplObjectStorage; use ArrayObject; use PHPParser_PrettyPrinter_Default; use Serializable; use Ray\Di\Di\Inject; class Injector implements InjectorInterface, \Serializable { protected $config; protected $container; protected $module; private $preDestroyObjects; private $logger; private $compiler; private $classes = []; public function __construct( ContainerInterface $container, AbstractModule $module, BindInterface $bind, CompilerInterface $compiler, LoggerInterface $logger = null ) { $this->container = $container; $this->module = $module; $this->bind = $bind; $this->compiler = $compiler; $this->logger = $logger; $this->preDestroyObjects = new SplObjectStorage; $this->config = $container->getForge()->getConfig(); $this->module->activate($this); AnnotationRegistry::registerAutoloadNamespace('Ray\Di\Di', dirname(dirname(__DIR__))); } public function __destruct() { $this->notifyPreShutdown(); } public static function create(array $modules = [], Cache $cache = null) { $annotationReader = ($cache instanceof Cache) ? new CachedReader(new AnnotationReader, $cache) : new AnnotationReader; $injector = new self( new Container(new Forge(new Config(new Annotation(new Definition, $annotationReader)))), new EmptyModule, new Bind, new Compiler( sys_get_temp_dir(), new PHPParser_PrettyPrinter_Default ), new Logger ); if (count($modules) > 0) { $module = array_shift($modules); foreach ($modules as $extraModule) { $module->install($extraModule); } $injector->setModule($module); } return $injector; } public function getModule() { return $this->module; } public function setModule(AbstractModule $module) { $module->activate($this); $this->module = $module; return $this; } public function getContainer() { return $this->container; } public function getLogger() { return $this->logger; } public function getAopClassDir() { return $this->compiler->classDir; } public function __clone() { $this->container = clone $this->container; } public function __invoke(AbstractModule $module) { $this->module = $module; return $this; } public function getInstance($class) { $this->classes[] = $class; $bound = $this->getBound($class); if (is_object($bound)) { return $bound; } list($class, $isSingleton, $interfaceClass, $params, $setter, $definition) = $bound; $params = $this->instantiateParams($params); $this->constructorInject($class, $params, $this->module); $refClass = new \ReflectionClass($class); if ($refClass->isInterface()) { return $this->getInstance($class); } $module = $this->module; $bind = $module($class, new $this->bind); $object = $bind->hasBinding() ? $this->compiler->newInstance($class, $params, $bind) : $this->newInstance($class, $params) ; unset($setter['__construct']); $this->setterMethod($setter, $object); if ($this->logger) { $this->logger->log($class, $params, $setter, $object, $bind); } $this->postInject($object, $definition, $isSingleton, $interfaceClass); return $object; } private function notifyPreShutdown() { $this->preDestroyObjects->rewind(); while ($this->preDestroyObjects->valid()) { $object = $this->preDestroyObjects->current(); $method = $this->preDestroyObjects->getInfo(); $object->$method(); $this->preDestroyObjects->next(); } } private function instantiateParams(array $params) { $keys = array_keys($params); foreach ($keys as $key) { if ($params[$key] instanceof Lazy) { $params[$key] = $params[$key](); } } return $params; } private function postInject($object, Definition $definition, $isSingleton, $interfaceClass) { if ($definition) { $this->setLifeCycle($object, $definition); } if ($isSingleton) { $this->container->set($interfaceClass, $object); } } private function newInstance($class, array $params) { return call_user_func_array( [$this->config->getReflect($class), 'newInstance'], $params ); } private function getBound($class) { $class = $this->removeLeadingBackSlash($class); $isAbstract = $this->isAbstract($class); list($config, $setter, $definition) = $this->config->fetch($class); $interfaceClass = $isSingleton = false; if ($isAbstract) { $bound = $this->getBoundClass($this->module->bindings, $definition, $class); if (is_object($bound)) { return $bound; } list($class, $isSingleton, $interfaceClass) = $bound; list($config, $setter, $definition) = $this->config->fetch($class); } elseif (! $isAbstract) { try { $bound = $this->getBoundClass($this->module->bindings, $definition, $class); if (is_object($bound)) { return $bound; } } catch (Exception\NotBound $e) { } } $hasDirectBinding = isset($this->module->bindings[$class]); if ($definition->hasDefinition() || $hasDirectBinding) { list($config, $setter) = $this->bindModule($setter, $definition); } return [$class, $isSingleton, $interfaceClass, $config, $setter, $definition]; } private function isAbstract($class) { try { $refClass = new ReflectionClass($class); $isAbstract = $refClass->isInterface() || $refClass->isAbstract(); } catch (ReflectionException $e) { throw new Exception\NotReadable($class); } return $isAbstract; } private function removeLeadingBackSlash($class) { $isLeadingBackSlash = (strlen($class) > 0 && $class[0] === '\\'); if ($isLeadingBackSlash === true) { $class = substr($class, 1); } return $class; } private function getBoundClass($bindings, $definition, $class) { $this->checkNotBound($bindings, $class); $toType = $bindings[$class]['*']['to'][0]; if ($toType === AbstractModule::TO_PROVIDER) { return $this->getToProviderBound($bindings, $class); } list($isSingleton, $interfaceClass) = $this->getBindingInfo($class, $definition, $bindings); if ($isSingleton && $this->container->has($interfaceClass)) { $object = $this->container->get($interfaceClass); return $object; } if ($toType === AbstractModule::TO_INSTANCE) { return $bindings[$class]['*']['to'][1]; } if ($toType === AbstractModule::TO_CLASS) { $class = $bindings[$class]['*']['to'][1]; } return [$class, $isSingleton, $interfaceClass]; } private function getBindingInfo($class, $definition, $bindings) { $inType = isset($bindings[$class]['*'][AbstractModule::IN]) ? $bindings[$class]['*'][AbstractModule::IN] : null; $inType = is_array($inType) ? $inType[0] : $inType; $isSingleton = $inType === Scope::SINGLETON || $definition['Scope'] == Scope::SINGLETON; $interfaceClass = $class; return [$isSingleton, $interfaceClass]; } private function checkNotBound($bindings, $class) { if (!isset($bindings[$class]) || !isset($bindings[$class]['*']['to'][0])) { $msg = "Interface \"$class\" is not bound."; throw new Exception\NotBound($msg); } } private function getToProviderBound(ArrayObject $bindings, $class) { $provider = $bindings[$class]['*']['to'][1]; $in = isset($bindings[$class]['*']['in']) ? $bindings[$class]['*']['in'] : null; if ($in !== Scope::SINGLETON) { return $this->getInstance($provider)->get(); } if (!$this->container->has($class)) { $object = $this->getInstance($provider)->get(); $this->container->set($class, $object); } return $this->container->get($class); } private function bindModule(array $setter, Definition $definition) { $setterDefinitions = (isset($definition[Definition::INJECT][Definition::INJECT_SETTER])) ? $definition[Definition::INJECT][Definition::INJECT_SETTER] : null; if ($setterDefinitions) { $setter = $this->getSetter($setterDefinitions); } $params = isset($setter['__construct']) ? $setter['__construct'] : []; $result = [$params, $setter]; return $result; } private function getSetter(array $setterDefinitions) { $injected = []; foreach ($setterDefinitions as $setterDefinition) { try { $injected[] = $this->bindMethod($setterDefinition); } catch (Exception\OptionalInjectionNotBound $e) { } } $setter = []; foreach ($injected as $item) { list($setterMethod, $object) = $item; $setter[$setterMethod] = $object; } return $setter; } private function bindMethod(array $setterDefinition) { list($method, $settings) = each($setterDefinition); array_walk($settings, [$this, 'bindOneParameter']); return [$method, $settings]; } private function constructorInject($class, array &$params, AbstractModule $module) { $ref = method_exists($class, '__construct') ? new ReflectionMethod($class, '__construct') : false; if ($ref === false) { return; } $parameters = $ref->getParameters(); foreach ($parameters as $index => $parameter) { $this->constructParams($params, $index, $parameter, $module, $class); } } private function constructParams(&$params, $index, \ReflectionParameter $parameter, AbstractModule $module, $class) { $params = array_values($params); if (isset($params[$index])) { return; } $hasConstructorBinding = ($module[$class]['*'][AbstractModule::TO][0] === AbstractModule::TO_CONSTRUCTOR); if ($hasConstructorBinding) { $params[$index] = $module[$class]['*'][AbstractModule::TO][1][$parameter->name]; return; } if ($parameter->isDefaultValueAvailable() === true) { return; } $classRef = $parameter->getClass(); if ($classRef && !$classRef->isInterface()) { $params[$index] = $this->getInstance($classRef->getName()); return; } $msg = is_null($classRef) ? "Valid interface is not found. (array ?)" : "Interface [{$classRef->name}] is not bound."; $msg .= " Injection requested at argument #{$index} \${$parameter->name} in {$class} constructor."; throw new Exception\NotBound($msg); } private function setterMethod(array $setter, $object) { foreach ($setter as $method => $value) { call_user_func_array([$object, $method], $value); } } private function setLifeCycle($instance, Definition $definition = null) { $postConstructMethod = $definition[Definition::POST_CONSTRUCT]; if ($postConstructMethod) { call_user_func(array($instance, $postConstructMethod)); } if (!is_null($definition[Definition::PRE_DESTROY])) { $this->preDestroyObjects->attach($instance, $definition[Definition::PRE_DESTROY]); } } public function __toString() { return (string)($this->module); } private function bindOneParameter(array &$param, $key) { $annotate = $param[Definition::PARAM_ANNOTATE]; $typeHint = $param[Definition::PARAM_TYPEHINT]; $hasTypeHint = isset($this->module[$typeHint]) && isset($this->module[$typeHint][$annotate]) && ($this->module[$typeHint][$annotate] !== []); $binding = $hasTypeHint ? $this->module[$typeHint][$annotate] : false; $isNotBinding = $binding === false || isset($binding[AbstractModule::TO]) === false; if ($isNotBinding && array_key_exists(Definition::DEFAULT_VAL, $param)) { $param = $param[Definition::DEFAULT_VAL]; return; } if ($isNotBinding) { $binding = $this->jitBinding($param, $typeHint, $annotate, $key); } list($bindingToType, $target) = $binding[AbstractModule::TO]; $bound = $this->instanceBound($param, $bindingToType, $target, $binding); if ($bound) { return; } if ($typeHint === '') { $param = $this->getInstanceWithContainer(Scope::PROTOTYPE, $bindingToType, $target); return; } $this->typeBound($param, $typeHint, $bindingToType, $target); } private function typeBound(&$param, $typeHint, $bindingToType, $target) { list($param, , $definition) = $this->config->fetch($typeHint); $in = isset($definition[Definition::SCOPE]) ? $definition[Definition::SCOPE] : Scope::PROTOTYPE; $param = $this->getInstanceWithContainer($in, $bindingToType, $target); } private function instanceBound(&$param, $bindingToType, $target, $binding) { if ($bindingToType === AbstractModule::TO_INSTANCE) { $param = $target; return true; } if ($bindingToType === AbstractModule::TO_CALLABLE) { $param = $target(); return true; } if (isset($binding[AbstractModule::IN])) { $param = $this->getInstanceWithContainer($binding[AbstractModule::IN], $bindingToType, $target); return true; } return false; } private function getInstanceWithContainer($in, $bindingToType, $target) { if ($in === Scope::SINGLETON && $this->container->has($target)) { $instance = $this->container->get($target); return $instance; } $isToClassBinding = ($bindingToType === AbstractModule::TO_CLASS); $instance = $isToClassBinding ? $this->getInstance($target) : $this->getInstance($target)->get(); if ($in === Scope::SINGLETON) { $this->container->set($target, $instance); } return $instance; } private function jitBinding(array $param, $typeHint, $annotate, $key) { $typeHintBy = $param[Definition::PARAM_TYPEHINT_BY]; if ($typeHintBy == []) { $this->raiseNotBoundException($param, $key, $typeHint, $annotate); } if ($typeHintBy[0] === Definition::PARAM_TYPEHINT_METHOD_IMPLEMETEDBY) { return [AbstractModule::TO => [AbstractModule::TO_CLASS, $typeHintBy[1]]]; } return [AbstractModule::TO => [AbstractModule::TO_PROVIDER, $typeHintBy[1]]]; } private function raiseNotBoundException($param, $key, $typeHint, $annotate) { if ($param[Definition::OPTIONAL] === true) { throw new Exception\OptionalInjectionNotBound($key); } $name = $param[Definition::PARAM_NAME]; $class = array_pop($this->classes); $msg = "typehint='{$typeHint}', annotate='{$annotate}' for \${$name} in class '{$class}'"; $e = (new Exception\NotBound($msg))->setModule($this->module); throw $e; } public function serialize() { $data = serialize( [ $this->container, $this->module, $this->bind, $this->compiler, $this->logger, $this->preDestroyObjects, $this->config ] ); return $data; } public function unserialize($data) { list( $this->container, $this->module, $this->bind, $this->compiler, $this->logger, $this->preDestroyObjects, $this->config ) = unserialize($data); AnnotationRegistry::registerAutoloadNamespace('Ray\Di\Di', dirname(dirname(__DIR__))); register_shutdown_function(function () { $this->notifyPreShutdown(); }); } } } namespace Ray\Di { use ArrayAccess; use ArrayObject; use Doctrine\Common\Annotations\AnnotationReader as Reader; use Ray\Aop\AbstractMatcher; use Ray\Aop\Bind; use Ray\Aop\Matcher; use Ray\Aop\Pointcut; abstract class AbstractModule implements ArrayAccess { const BIND = 'bind'; const NAME = 'name'; const IN = 'in'; const TO = 'to'; const TO_CLASS = 'class'; const TO_PROVIDER = 'provider'; const TO_INSTANCE = 'instance'; const TO_CALLABLE = 'callable'; const TO_CONSTRUCTOR = 'constructor'; const TO_SETTER = 'setter'; const SCOPE = 'scope'; const NAME_UNSPECIFIED = '*'; public $bindings; protected $currentBinding; protected $currentName = self::NAME_UNSPECIFIED; protected $scope = [Scope::PROTOTYPE, Scope::SINGLETON]; public $pointcuts = []; protected $dependencyInjector; protected $activated = false; public $modules = []; private $stringer; public function __construct( AbstractModule $module = null, Matcher $matcher = null, ModuleStringerInterface $stringer = null ) { $this->modules[] = get_class($this); $this->matcher = $matcher ? : new Matcher(new Reader); $this->stringer = $stringer ?: new ModuleStringer; if (is_null($module)) { $this->bindings = new ArrayObject; $this->pointcuts = new ArrayObject; return; } $module->activate(); $this->bindings = $module->bindings; $this->pointcuts = $module->pointcuts; } public function activate(InjectorInterface $injector = null) { if ($this->activated === true) { return; } $this->activated = true; $this->dependencyInjector = $injector ? : Injector::create([$this]); $this->configure(); } abstract protected function configure(); protected function bind($interface = '') { if (strlen($interface) > 0 && $interface[0] === '\\') { $interface = substr($interface, 1); } $this->currentBinding = $interface; $this->currentName = self::NAME_UNSPECIFIED; return $this; } protected function annotatedWith($name) { $this->currentName = $name; $this->bindings[$this->currentBinding][$name] = [self::IN => Scope::SINGLETON]; return $this; } protected function in($scope) { $this->bindings[$this->currentBinding][$this->currentName][self::IN] = $scope; return $this; } protected function to($class) { $this->bindings[$this->currentBinding][$this->currentName] = [self::TO => [self::TO_CLASS, $class]]; return $this; } protected function toProvider($provider) { $hasProviderInterface = class_exists($provider) && in_array( 'Ray\Di\ProviderInterface', class_implements($provider) ); if ($hasProviderInterface === false) { throw new Exception\InvalidProvider($provider); } $this->bindings[$this->currentBinding][$this->currentName] = [self::TO => [self::TO_PROVIDER, $provider]]; return $this; } protected function toInstance($instance) { $this->bindings[$this->currentBinding][$this->currentName] = [self::TO => [self::TO_INSTANCE, $instance]]; } protected function toCallable(callable $callable) { $this->bindings[$this->currentBinding][$this->currentName] = [self::TO => [self::TO_CALLABLE, $callable]]; } protected function toConstructor(array $params) { $this->bindings[$this->currentBinding][$this->currentName] = [self::TO => [self::TO_CONSTRUCTOR, $params]]; } protected function bindInterceptor(AbstractMatcher $classMatcher, AbstractMatcher $methodMatcher, array $interceptors) { $id = uniqid(); $this->pointcuts[$id] = new Pointcut($classMatcher, $methodMatcher, $interceptors); } public function install(AbstractModule $module) { $module->activate($this->dependencyInjector); $this->pointcuts = new ArrayObject(array_merge((array)$module->pointcuts, (array)$this->pointcuts)); $this->bindings = $this->mergeBindings($module); if ($module->modules) { $this->modules = array_merge($this->modules, $module->modules); } } private function mergeBindings(AbstractModule $module) { return new ArrayObject($this->mergeArray((array)$this->bindings, (array)$module->bindings)); } private function mergeArray(array $origin, array $new) { foreach ($new as $key => $value) { $beMergeable = isset($origin[$key]) && is_array($value) && is_array($origin[$key]); $origin[$key] = $beMergeable ? $this->mergeArray($value, $origin[$key]) : $value; } return $origin; } public function requestInjection($class) { $di = $this->dependencyInjector; $module = $di->getModule(); $di($this); $instance = $di->getInstance($class); if ($module instanceof AbstractModule) { $di($module); } return $instance; } public function __invoke($class, Bind $bind) { $bind->bind($class, (array)$this->pointcuts); return $bind; } public function offsetExists($offset) { return isset($this->bindings[$offset]); } public function offsetGet($offset) { return isset($this->bindings[$offset]) ? $this->bindings[$offset] : null; } public function offsetSet($offset, $value) { throw new Exception\ReadOnly; } public function offsetUnset($offset) { throw new Exception\ReadOnly; } public function __toString() { $this->stringer = new ModuleStringer(); return $this->stringer->toString($this); } public function __sleep() { return ['bindings', 'pointcuts']; } } } namespace Demo\Helloworld\Module { use Ray\Di\AbstractModule; use BEAR\Package; use BEAR\Package\Module; use BEAR\Package\Provide as ProvideModule; use BEAR\Sunday\Module as SundayModule; use Ray\Di\Module\InjectorModule; class AppModule extends AbstractModule { protected function configure() { $this->bind()->annotatedWith('app_name')->toInstance('Demo\Helloworld'); $this->bind('BEAR\Sunday\Extension\Application\AppInterface')->to('Demo\Helloworld\App'); $this->install(new SundayModule\Framework\FrameworkModule); $this->install(new SundayModule\Resource\ResourceCacheModule); $this->install(new SundayModule\Constant\NamedModule(['tmp_dir' => sys_get_temp_dir()])); $this->install(new InjectorModule($this)); } } } namespace Ray\Di { class Scope { const SINGLETON = 'Singleton'; const PROTOTYPE = 'Prototype'; } } namespace Ray\Aop { abstract class AbstractMatcher { const TARGET_CLASS = true; const TARGET_METHOD = false; protected $method; protected $args; protected function createMatcher($method, $args) { $this->method = $method; $this->args = $args; return clone $this; } public function __invoke($class, $target) { $args = [$class, $target]; $thisArgs = is_array($this->args) ? $this->args : [$this->args]; foreach ($thisArgs as $arg) { $args[] = $arg; } $method = 'is' . $this->method; $matched = call_user_func_array([$this, $method], $args); return $matched; } public function __toString() { $result = $this->method . ':' . json_encode($this->args); return $result; } } } namespace Ray\Aop { interface Matchable { public function any(); public function annotatedWith($annotationName); public function subclassesOf($superClass); public function startsWith($prefix); public function logicalOr(Matchable $matcherA, Matchable $matcherB); public function logicalAnd(Matchable $matcherA, Matchable $matcherB); public function logicalXor(Matchable $matcherA, Matchable $matcherB); public function logicalNot(Matchable $matcher); public function __invoke($class, $target); } } namespace Ray\Aop { use Doctrine\Common\Annotations\Reader; use Ray\Aop\Exception\InvalidAnnotation; use Ray\Aop\Exception\InvalidArgument as InvalidArgumentException; use ReflectionClass; class Matcher extends AbstractMatcher implements Matchable { private $reader; public function __construct(Reader $reader) { $this->reader = $reader; } public function any() { return $this->createMatcher(__FUNCTION__, null); } public function annotatedWith($annotationName) { if (!class_exists($annotationName)) { throw new InvalidAnnotation($annotationName); } return $this->createMatcher(__FUNCTION__, $annotationName); } public function subclassesOf($superClass) { return $this->createMatcher(__FUNCTION__, $superClass); } public function startWith($prefix) { return $this->startsWith($prefix); } public function startsWith($prefix) { return $this->createMatcher(__FUNCTION__, $prefix); } public function isAnnotateBinding() { $isAnnotateBinding = $this->method === 'annotatedWith'; return $isAnnotateBinding; } public function logicalOr(Matchable $matcherA, Matchable $matcherB) { $this->method = __FUNCTION__; $this->args = func_get_args(); return clone $this; } public function logicalAnd(Matchable $matcherA, Matchable $matcherB) { $this->method = __FUNCTION__; $this->args = func_get_args(); return clone $this; } public function logicalXor(Matchable $matcherA, Matchable $matcherB) { $this->method = __FUNCTION__; $this->args = func_get_args(); return clone $this; } public function logicalNot(Matchable $matcher) { $this->method = __FUNCTION__; $this->args = $matcher; return clone $this; } protected function isAny($name, $target) { if ($target === self::TARGET_CLASS) { return true; } if (substr($name, 0, 2) === '__') { return false; } if (in_array( $name, [ 'offsetExists', 'offsetGet', 'offsetSet', 'offsetUnset', 'append', 'getArrayCopy', 'count', 'getFlags', 'setFlags', 'asort', 'ksort', 'uasort', 'uksort', 'natsort', 'natcasesort', 'unserialize', 'serialize', 'getIterator', 'exchangeArray', 'setIteratorClass', 'getIterator', 'getIteratorClass' ] ) ) { return false; } return true; } protected function isAnnotatedWith($class, $target, $annotationName) { $reader = $this->reader; if ($target === self::TARGET_CLASS) { $annotation = $reader->getClassAnnotation(new ReflectionClass($class), $annotationName); $hasAnnotation = $annotation ? true : false; return $hasAnnotation; } $methods = (new ReflectionClass($class))->getMethods(); $result = []; foreach ($methods as $method) { new $annotationName; $annotation = $reader->getMethodAnnotation($method, $annotationName); if ($annotation) { $matched = new Matched; $matched->methodName = $method->name; $matched->annotation = $annotation; $result[] = $matched; } } return $result; } protected function isSubclassesOf($class, $target, $superClass) { if ($target === self::TARGET_METHOD) { throw new InvalidArgumentException($class); } try { $isSubClass = (new ReflectionClass($class))->isSubclassOf($superClass); if ($isSubClass === false) { $isSubClass = ($class === $superClass); } return $isSubClass; } catch (\Exception $e) { return false; } } protected function isStartsWith($name, $target, $startsWith) { unset($target); $result = (strpos($name, $startsWith) === 0) ? true : false; return $result; } protected function isLogicalOr($name, $target, Matchable $matcherA, Matchable $matcherB) { $isOr = ($matcherA($name, $target) or $matcherB($name, $target)); if (func_num_args() <= 4) { return $isOr; } $args = array_slice(func_get_args(), 4); foreach ($args as $arg) { $isOr = ($isOr or $arg($name, $target)); } return $isOr; } protected function isLogicalAnd($name, $target, Matchable $matcherA, Matchable $matcherB) { $isAnd = ($matcherA($name, $target) and $matcherB($name, $target)); if (func_num_args() <= 4) { return $isAnd; } $args = array_slice(func_get_args(), 4); foreach ($args as $arg) { $isAnd = ($isAnd and $arg($name, $target)); } return $isAnd; } protected function isLogicalXor($name, $target, Matchable $matcherA, Matchable $matcherB) { $isXor = ($matcherA($name, $target) xor $matcherB($name, $target)); if (func_num_args() <= 4) { return $isXor; } $args = array_slice(func_get_args(), 4); foreach ($args as $arg) { $isXor = ($isXor xor $arg($name, $target)); } return $isXor; } protected function isLogicalNot($name, $target, Matchable $matcher) { $isNot = !($matcher($name, $target)); return $isNot; } } } namespace Doctrine\Common\Lexer { abstract class AbstractLexer { private $tokens = array(); private $position = 0; private $peek = 0; public $lookahead; public $token; public function setInput($input) { $this->tokens = array(); $this->reset(); $this->scan($input); } public function reset() { $this->lookahead = null; $this->token = null; $this->peek = 0; $this->position = 0; } public function resetPeek() { $this->peek = 0; } public function resetPosition($position = 0) { $this->position = $position; } public function isNextToken($token) { return null !== $this->lookahead && $this->lookahead['type'] === $token; } public function isNextTokenAny(array $tokens) { return null !== $this->lookahead && in_array($this->lookahead['type'], $tokens, true); } public function moveNext() { $this->peek = 0; $this->token = $this->lookahead; $this->lookahead = (isset($this->tokens[$this->position])) ? $this->tokens[$this->position++] : null; return $this->lookahead !== null; } public function skipUntil($type) { while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { $this->moveNext(); } } public function isA($value, $token) { return $this->getType($value) === $token; } public function peek() { if (isset($this->tokens[$this->position + $this->peek])) { return $this->tokens[$this->position + $this->peek++]; } else { return null; } } public function glimpse() { $peek = $this->peek(); $this->peek = 0; return $peek; } protected function scan($input) { static $regex; if ( ! isset($regex)) { $regex = '/(' . implode(')|(', $this->getCatchablePatterns()) . ')|' . implode('|', $this->getNonCatchablePatterns()) . '/i'; } $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; $matches = preg_split($regex, $input, -1, $flags); foreach ($matches as $match) { $type = $this->getType($match[0]); $this->tokens[] = array( 'value' => $match[0], 'type' => $type, 'position' => $match[1], ); } } public function getLiteral($token) { $className = get_class($this); $reflClass = new \ReflectionClass($className); $constants = $reflClass->getConstants(); foreach ($constants as $name => $value) { if ($value === $token) { return $className . '::' . $name; } } return $token; } abstract protected function getCatchablePatterns(); abstract protected function getNonCatchablePatterns(); abstract protected function getType(&$value); } } namespace Ray\Di { class ModuleStringer { public function toString(AbstractModule $module) { $output = ''; foreach ((array)$module->bindings as $bind => $bindTo) { foreach ($bindTo as $annotate => $to) { $type = $to['to'][0]; $output .= ($annotate !== '*') ? "bind:{$bind} annotatedWith:{$annotate}" : "bind:{$bind}"; if ($type === 'class') { $output .= " to:" . $to['to'][1]; } if ($type === 'instance') { $output .= $this->getInstanceString($to); } if ($type === 'provider') { $provider = $to['to'][1]; $output .= " toProvider:" . $provider; } $output .= PHP_EOL; } } return $output; } private function getInstanceString(array $to) { $instance = $to['to'][1]; $type = gettype($instance); switch ($type) { case "object": $instance = '(object) ' . get_class($instance); break; case "array": $instance = json_encode($instance); break; case "string": $instance = "'{$instance}'"; break; case "boolean": $instance = '(bool) ' . ($instance ? 'true' : 'false'); break; default: $instance = "($type) $instance"; } return " toInstance:" . $instance; } } } namespace Aura\Di { interface ContainerInterface { public function lock(); public function isLocked(); public function getForge(); public function has($key); public function set($key, $val); public function get($key); public function getServices(); public function getDefs(); public function lazyGet($key); public function newInstance($class, array $params = [], array $setters = []); public function lazyNew($class, array $params = [], array $setters = []); } } namespace Aura\Di { class Container implements ContainerInterface { protected $forge; protected $params; protected $setter; protected $defs = []; protected $services = []; protected $locked = false; public function __construct(ForgeInterface $forge) { $this->forge = $forge; $this->params = $this->getForge()->getConfig()->getParams(); $this->setter = $this->getForge()->getConfig()->getSetter(); } public function __get($key) { if ($this->isLocked()) { throw new Exception\ContainerLocked; } if ($key == 'params' || $key == 'setter') { return $this->$key; } throw new \UnexpectedValueException($key); } public function __clone() { $this->services = []; $this->forge = clone $this->forge; } public function lock() { $this->locked = true; } public function isLocked() { return $this->locked; } public function getForge() { return $this->forge; } public function has($key) { return isset($this->defs[$key]); } public function set($key, $val) { if ($this->isLocked()) { throw new Exception\ContainerLocked; } if (! is_object($val)) { throw new Exception\ServiceNotObject($key); } if ($val instanceof \Closure) { $val = new Lazy($val); } $this->defs[$key] = $val; return $this; } public function get($key) { if (! $this->has($key)) { throw new Exception\ServiceNotFound($key); } if (! isset($this->services[$key])) { $service = $this->defs[$key]; if ($service instanceof Lazy) { $service = $service(); } $this->services[$key] = $service; } return $this->services[$key]; } public function getServices() { return array_keys($this->services); } public function getDefs() { return array_keys($this->defs); } public function lazy(callable $callable) { return new Lazy($callable); } public function lazyGet($key) { $self = $this; return $this->lazy( function () use ($self, $key) { return $self->get($key); } ); } public function newInstance($class, array $params = [], array $setters = []) { return $this->forge->newInstance($class, $params, $setters); } public function lazyNew($class, array $params = [], array $setters = []) { $forge = $this->getForge(); return $this->lazy( function () use ($forge, $class, $params, $setters) { return $forge->newInstance($class, $params, $setters); } ); } public function lazyRequire($file) { return $this->lazy(function () use ($file) { return require $file; }); } public function lazyInclude($file) { return $this->lazy(function () use ($file) { return include $file; }); } public function lazyCall($callable) { $params = func_get_args(); array_shift($params); $call = function () use ($callable, $params) { if (is_array($callable)) { foreach ($callable as $key => $val) { if ($val instanceof Lazy) { $callable[$key] = $val(); } } } foreach ($params as $key => $val) { if ($val instanceof Lazy) { $params[$key] = $val(); } } return call_user_func_array($callable, $params); }; return $this->lazy($call); } public function newFactory($class, array $params = [], array $setters = []) { return new Factory($this->forge, $class, $params, $setters); } } } namespace Ray\Di { use Aura\Di\Container as AuraContainer; use Aura\Di\ContainerInterface; use Aura\Di\ForgeInterface; use Ray\Di\Di\Inject; class Container extends AuraContainer implements ContainerInterface { public function __construct(ForgeInterface $forge) { parent::__construct($forge); } } } namespace Aura\Di { interface ForgeInterface { public function getConfig(); public function newInstance($class, array $params = [], array $setters = []); } } namespace Aura\Di { class Forge implements ForgeInterface { protected $config; public function __construct(ConfigInterface $config) { $this->config = $config; } public function __clone() { $this->config = clone $this->config; } public function getConfig() { return $this->config; } public function newInstance( $class, array $merge_params = [], array $merge_setter = [] ) { list($params, $setter) = $this->config->fetch($class); $params = $this->mergeParams($params, $merge_params); $setter = array_merge($setter, $merge_setter); $rclass = $this->config->getReflect($class); $object = $rclass->newInstanceArgs($params); foreach ($setter as $method => $value) { if (method_exists($object, $method)) { if ($value instanceof Lazy) { $value = $value(); } $object->$method($value); } else { throw new Exception\SetterMethodNotFound("$class::$method"); } } return $object; } protected function mergeParams($params, array $merge_params = []) { $pos = 0; foreach ($params as $key => $val) { if (array_key_exists($pos, $merge_params)) { $val = $merge_params[$pos]; } elseif (array_key_exists($key, $merge_params)) { $val = $merge_params[$key]; } if ($val instanceof Lazy) { $val = $val(); } $params[$key] = $val; $pos += 1; } return $params; } } } namespace Ray\Di { use Aura\Di\ConfigInterface; use Aura\Di\Forge as AuraForge; use Aura\Di\ForgeInterface; use Ray\Di\Di\Inject; class Forge extends AuraForge implements ForgeInterface { public function __construct(ConfigInterface $config) { parent::__construct($config); } } } namespace Aura\Di { interface ConfigInterface { public function fetch($class); public function getParams(); public function getSetter(); public function getReflect($class); } } namespace Ray\Di { use Aura\Di\ConfigInterface; use ArrayObject; use ReflectionClass; use ReflectionMethod; use Ray\Di\Di\Inject; class Config implements ConfigInterface { const INDEX_PARAM = 0; const INDEX_SETTER = 1; const INDEX_DEFINITION = 2; protected $params; protected $reflect = []; protected $setter; protected $unified = []; protected $methodReflect; protected $definition; protected $annotation; public function __construct(AnnotationInterface $annotation) { $this->reset(); $this->annotation = $annotation; } public function __clone() { $this->reset(); } protected function reset() { $this->params = new ArrayObject; $this->params['*'] = []; $this->setter = new ArrayObject; $this->setter['*'] = []; $this->definition = new Definition([]); $this->definition['*'] = []; $this->methodReflect = new ArrayObject; } public function getParams() { return $this->params; } public function getSetter() { return $this->setter; } public function getDefinition() { return $this->definition; } public function getReflect($class) { if (!isset($this->reflect[$class])) { $this->reflect[$class] = new ReflectionClass($class); } return $this->reflect[$class]; } public function fetch($class) { if (isset($this->unified[$class])) { return $this->unified[$class]; } $parentClass = get_parent_class($class); list($parentParams, $parentSetter, $parentDefinition) = $parentClass ? $this->fetch($parentClass) : [$this->params['*'], $this->setter['*'], $this->annotation->getDefinition($class)]; $constructorReflection = $this->getReflect($class)->getConstructor(); $unifiedParams = $constructorReflection ? $this->getUnifiedParams($constructorReflection, $parentParams, $class) : []; $this->unified[$class] = $this->mergeConfig($class, $unifiedParams, $parentSetter, $parentDefinition); return $this->unified[$class]; } private function mergeConfig($class, $unifiedParams, $parentSetter, Definition $parentDefinition) { $unifiedSetter = isset($this->setter[$class]) ? array_merge($parentSetter, $this->setter[$class]) : $parentSetter; $definition = isset($this->definition[$class]) ? $this->definition[$class] : $this->annotation->getDefinition($class); $unifiedDefinition = new Definition(array_merge($parentDefinition->getArrayCopy(), $definition->getArrayCopy())); $this->definition[$class] = $unifiedDefinition; $this->unified[$class] = [$unifiedParams, $unifiedSetter, $unifiedDefinition]; return $this->unified[$class]; } private function getUnifiedParams(\ReflectionMethod $constructorReflection, $parentParams, $class) { $unifiedParams = []; $params = $constructorReflection->getParameters(); foreach ($params as $param) { $name = $param->name; $explicit = $this->params->offsetExists($class) && isset($this->params[$class][$name]); if ($explicit) { $unifiedParams[$name] = $this->params[$class][$name]; continue; } elseif (isset($parentParams[$name])) { $unifiedParams[$name] = $parentParams[$name]; continue; } elseif ($param->isDefaultValueAvailable()) { $unifiedParams[$name] = $param->getDefaultValue(); continue; } $unifiedParams[$name] = null; } return $unifiedParams; } public function getMethodReflect($class, $method) { if (is_object($class)) { $class = get_class($class); } if (!isset($this->reflect[$class]) || !is_array($this->reflect[$class])) { $methodRef = new ReflectionMethod($class, $method); $this->methodReflect[$class][$method] = $methodRef; } return $this->methodReflect[$class][$method]; } public function __sleep() { return ['params', 'setter', 'unified', 'definition', 'annotation']; } } } namespace Ray\Di { interface AnnotationInterface { public function getDefinition($class); } } namespace Ray\Di { use Doctrine\Common\Annotations\Reader; use Ray\Di\Exception\NotReadable; use ReflectionClass; use ReflectionMethod; use ReflectionParameter; use Ray\Di\Di\Inject; class Annotation implements AnnotationInterface { const USER = 'user'; protected $newDefinition; protected $definition; protected $definitions = []; protected $reader; public function __construct(Definition $definition, Reader $reader) { $this->newDefinition = $definition; $this->reader = $reader; } public function getDefinition($className) { if (! class_exists($className) && ! interface_exists($className)) { throw new NotReadable($className); } if (isset($this->definitions[$className])) { return $this->definitions[$className]; } $this->definition = clone $this->newDefinition; $class = new ReflectionClass($className); $annotations = $this->reader->getClassAnnotations($class); $classDefinition = $this->getClassDefinition($annotations); foreach ($classDefinition as $key => $value) { $this->definition[$key] = $value; } $this->setMethodDefinition($class); $this->definitions[$className] = $this->definition; return $this->definition; } private function getClassDefinition(array $annotations) { $result = []; foreach ($annotations as $annotation) { $annotationName = $this->getAnnotationName($annotation); $value = isset($annotation->value) ? $annotation->value : null; $result[$annotationName] = $value; } return $result; } private function getMethodDefinition(array $annotations) { $result = []; foreach ($annotations as $annotation) { $annotationName = $this->getAnnotationName($annotation); $value = $annotation; $result[$annotationName] = $value; } return $result; } private function getAnnotationName($annotation) { $classPath = explode('\\', get_class($annotation)); $annotationName = array_pop($classPath); return $annotationName; } private function setMethodDefinition(ReflectionClass $class) { $methods = $class->getMethods(); foreach ($methods as $method) { $annotations = $this->reader->getMethodAnnotations($method); $methodAnnotation = $this->getMethodDefinition($annotations); $keys = array_keys($methodAnnotation); foreach ($keys as $key) { $this->setAnnotationName($key, $method, $methodAnnotation); } foreach ($annotations as $annotation) { $annotationName = $this->getAnnotationName($annotation); $this->definition->setUserAnnotationByMethod($annotationName, $method->name, $annotation); } } } private function setAnnotationName($name, ReflectionMethod $method, array $annotations) { if ($name === Definition::POST_CONSTRUCT || $name == Definition::PRE_DESTROY) { if (isset($this->definition[$name]) && $this->definition[$name]) { $msg = "@{$name} in " . $method->getDeclaringClass()->name; throw new Exception\MultipleAnnotationNotAllowed($msg); } $this->definition[$name] = $method->name; return; } if ($name === Definition::INJECT) { $this->setSetterInjectDefinition($annotations, $method); return; } if ($name === Definition::NAMED) { return; } $this->definition->setUserAnnotationMethodName($name, $method->name); } private function setSetterInjectDefinition($methodAnnotation, ReflectionMethod $method) { $nameParameter = false; if (isset($methodAnnotation[Definition::NAMED])) { $named = $methodAnnotation[Definition::NAMED]; $nameParameter = $named->value; } $named = ($nameParameter !== false) ? $this->getNamed($nameParameter) : []; $parameters = $method->getParameters(); $paramInfo[$method->name] = $this->getParamInfo($methodAnnotation, $parameters, $named); $this->definition[Definition::INJECT][Definition::INJECT_SETTER][] = $paramInfo; } private function getParamInfo($methodAnnotation, array $parameters, $named) { $paramsInfo = []; foreach ($parameters as $parameter) { $class = $parameter->getClass(); $typehint = $class ? $class->getName() : ''; $typehintBy = $typehint ? $this->getTypeHintDefaultInjection($typehint) : []; $pos = $parameter->getPosition(); $name = $this->getName($named, $parameter); $optionalInject = $methodAnnotation[Definition::INJECT]->optional; $definition = [ Definition::PARAM_POS => $pos, Definition::PARAM_TYPEHINT => $typehint, Definition::PARAM_NAME => $parameter->name, Definition::PARAM_ANNOTATE => $name, Definition::PARAM_TYPEHINT_BY => $typehintBy, Definition::OPTIONAL => $optionalInject ]; if ($parameter->isOptional()) { $definition[Definition::DEFAULT_VAL] = $parameter->getDefaultValue(); } $paramsInfo[] = $definition; } return $paramsInfo; } private function getName($named, ReflectionParameter $parameter) { if (is_string($named)) { return $named; } if (is_array($named) && isset($named[$parameter->name])) { return $named[$parameter->name]; } return Definition::NAME_UNSPECIFIED; } private function getNamed($nameParameter) { if (preg_match("/^[a-zA-Z0-9_]+$/", $nameParameter)) { return $nameParameter; } preg_match_all('/([^=,]*)=("[^"]*"|[^,"]*)/', $nameParameter, $matches); if ($matches[0] === []) { throw new Exception\Named; } $result = []; $count = count($matches[0]); for ($i = 0; $i < $count; $i++) { $result[$matches[1][$i]] = $matches[2][$i]; } return $result; } private function getTypeHintDefaultInjection($typehint) { $annotations = $this->reader->getClassAnnotations(new ReflectionClass($typehint)); $classDefinition = $this->getClassDefinition($annotations); if (isset($classDefinition[Definition::IMPLEMENTEDBY])) { $result = [Definition::PARAM_TYPEHINT_METHOD_IMPLEMETEDBY, $classDefinition[Definition::IMPLEMENTEDBY]]; return $result; } if (isset($classDefinition[Definition::PROVIDEDBY])) { $result = [Definition::PARAM_TYPEHINT_METHOD_PROVIDEDBY, $classDefinition[Definition::PROVIDEDBY]]; return $result; } if (class_exists($typehint)) { $class = new ReflectionClass($typehint); if ($class->isAbstract() === false) { $result = [Definition::PARAM_TYPEHINT_METHOD_IMPLEMETEDBY, $typehint]; return $result; } } return []; } } } namespace Ray\Di { use ArrayObject; class Definition extends ArrayObject { const POST_CONSTRUCT = "PostConstruct"; const PRE_DESTROY = "PreDestroy"; const INJECT = "Inject"; const PROVIDE = "Provide"; const SCOPE = "Scope"; const IMPLEMENTEDBY = "ImplementedBy"; const PROVIDEDBY = "ProvidedBy"; const NAMED = "Named"; const NAME_UNSPECIFIED = '*'; const INJECT_SETTER = 'setter'; const PARAM_POS = 'pos'; const PARAM_TYPEHINT = 'typehint'; const PARAM_TYPEHINT_BY = 'typehint_by'; const PARAM_TYPEHINT_METHOD_IMPLEMETEDBY = 'implementedby'; const PARAM_TYPEHINT_METHOD_PROVIDEDBY = 'providedby'; const PARAM_NAME = 'name'; const PARAM_ANNOTATE = 'annotate'; const ASPECT = 'Aspect'; const USER = 'user'; const OPTIONS = 'options'; const BINDING = 'binding'; const BY_METHOD = 'by_method'; const BY_NAME = 'by_name'; const OPTIONAL = 'optional'; const DEFAULT_VAL = 'default_val'; private $defaults = [ self::SCOPE => Scope::PROTOTYPE, self::POST_CONSTRUCT => null, self::PRE_DESTROY => null, self::INJECT => [], self::IMPLEMENTEDBY => [], self::USER => [], self::OPTIONAL => [] ]; public function __construct(array $defaults = null) { $defaults = $defaults ? : $this->defaults; parent::__construct($defaults); } public function hasDefinition() { $hasDefinition = ($this->getArrayCopy() !== $this->defaults); return $hasDefinition; } public function setUserAnnotationMethodName($annotationName, $methodName) { $this[self::BY_NAME][$annotationName][] = $methodName; } public function getUserAnnotationMethodName($annotationName) { $hasUserAnnotation = isset($this[self::BY_NAME]) && isset($this[self::BY_NAME][$annotationName]); $result = $hasUserAnnotation ? $this[Definition::BY_NAME][$annotationName] : null; return $result; } public function setUserAnnotationByMethod($annotationName, $methodName, $methodAnnotation) { $this[self::BY_METHOD][$methodName][$annotationName][] = $methodAnnotation; } public function getUserAnnotationByMethod($methodName) { $result = isset($this[self::BY_METHOD]) && isset($this[self::BY_METHOD][$methodName]) ? $this[self::BY_METHOD][$methodName] : null; return $result; } public function __toString() { return var_export($this, true); } } } namespace Ray\Di { class EmptyModule extends AbstractModule { public function __construct() { $this->bindings = new \ArrayObject; $this->container = new \ArrayObject; $this->configure(); } protected function configure() { } } } namespace Ray\Aop { interface BindInterface { public function hasBinding(); public function bind($class, array $pointcuts); public function bindInterceptors($method, array $interceptors, $annotation = null); public function __invoke($name); public function __toString(); } } namespace Ray\Aop { use ArrayObject; use ReflectionClass; use ReflectionMethod; final class Bind extends ArrayObject implements BindInterface { public $annotation = []; public function hasBinding() { $hasImplicitBinding = (count($this)) ? true : false; return $hasImplicitBinding; } public function bind($class, array $pointcuts) { foreach ($pointcuts as $pointcut) { $classMatcher = $pointcut->classMatcher; $isClassMatch = $classMatcher($class, Matcher::TARGET_CLASS); if ($isClassMatch !== true) { continue; } if (method_exists($pointcut->methodMatcher, 'isAnnotateBinding') && $pointcut->methodMatcher->isAnnotateBinding()) { $this->bindByAnnotateBinding($class, $pointcut->methodMatcher, $pointcut->interceptors); continue; } $this->bindByCallable($class, $pointcut->methodMatcher, $pointcut->interceptors); } return $this; } public function bindInterceptors($method, array $interceptors, $annotation = null) { $this[$method] = !isset($this[$method]) ? $interceptors : array_merge($this[$method], $interceptors); if ($annotation) { $this->annotation[$method] = $annotation; } return $this; } public function __invoke($name) { $interceptors = isset($this[$name]) ? $this[$name] : false; return $interceptors; } public function __toString() { $binds = []; foreach ($this as $method => $interceptors) { $inspectorsInfo = []; foreach ($interceptors as $interceptor) { $inspectorsInfo[] .= get_class($interceptor); } $inspectorsInfo = implode(',', $inspectorsInfo); $binds[] = "{$method} => " . $inspectorsInfo; } $result = implode(',', $binds); return $result; } private function bindByCallable($class, AbstractMatcher $methodMatcher, array $interceptors) { $methods = (new ReflectionClass($class))->getMethods(ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { $isMethodMatch = ($methodMatcher($method->name, Matcher::TARGET_METHOD) === true); if ($isMethodMatch) { $this->bindInterceptors($method->name, $interceptors); } } } private function bindByAnnotateBinding($class, Matcher $methodMatcher, array $interceptors) { $matches = (array)$methodMatcher($class, Matcher::TARGET_METHOD); if (!$matches) { return; } foreach ($matches as $matched) { if ($matched instanceof Matched) { $this->bindInterceptors($matched->methodName, $interceptors, $matched->annotation); } } } } } namespace Ray\Aop { interface CompilerInterface { public function compile($class, Bind $bind); public function newInstance($class, array $args, Bind $bind); } } namespace Ray\Aop { use PHPParser_BuilderFactory; use PHPParser_Parser; use PHPParser_PrettyPrinterAbstract; use ReflectionClass; use ReflectionMethod; use PHPParser_Comment_Doc; use PHPParser_Builder_Class; use PHPParser_Node_Stmt_Class; use PHPParser_Builder_Method; use PHPParser_Lexer; use Serializable; final class Compiler implements CompilerInterface, Serializable { public $classDir; private $parser; private $factory; public function __construct( $classDir, PHPParser_PrettyPrinterAbstract $printer ) { $this->classDir = $classDir; $this->printer = $printer; } public function compile($class, Bind $bind) { $this->parser = new PHPParser_Parser(new PHPParser_Lexer); $this->factory = new PHPParser_BuilderFactory; $refClass = new ReflectionClass($class); $newClassName = $this->getClassName($refClass, $bind); if (class_exists($newClassName, false)) { return $newClassName; } $file = $this->classDir . "/{$newClassName}.php"; $stmt = $this ->getClass($newClassName, $refClass) ->addStmts($this->getMethods($refClass, $bind)) ->getNode(); $stmt = $this->addClassDocComment($stmt, $refClass); $code = $this->printer->prettyPrint([$stmt]); file_put_contents($file, '<?php ' . PHP_EOL . $code); include_once $file; return $newClassName; } public function newInstance($class, array $args, Bind $bind) { $class = $this->compile($class, $bind); $instance = (new ReflectionClass($class))->newInstanceArgs($args); $instance->rayAopBind = $bind; return $instance; } private function getClassName(\ReflectionClass $class, Bind $bind) { $className = str_replace('\\', '_', $class->getName()) . '_' . md5($bind) .'RayAop'; return $className; } private function getClass($newClassName, \ReflectionClass $class) { $parentClass = $class->name; $builder = $this->factory ->class($newClassName) ->extend($parentClass) ->implement('Ray\Aop\WeavedInterface') ->addStmt( $this->factory->property('rayAopIntercept')->makePrivate()->setDefault(true) )->addStmt( $this->factory->property('rayAopBind')->makePublic() ); return $builder; } private function addClassDocComment(PHPParser_Node_Stmt_Class $node, \ReflectionClass $class) { $docComment = $class->getDocComment(); if ($docComment) { $node->setAttribute('comments', [new PHPParser_Comment_Doc($docComment)]); } return $node; } private function getMethods(ReflectionClass $class) { $stmts = []; $methods = $class->getMethods(); foreach ($methods as $method) { if ($method->isPublic()) { $stmts[] = $this->getMethod($method); } } return $stmts; } private function getMethod(\ReflectionMethod $method) { $methodStmt = $this->factory->method($method->name); $params = $method->getParameters(); foreach ($params as $param) { $paramStmt = $this->factory->param($param->name); $typeHint = $param->getClass(); if ($typeHint) { $paramStmt->setTypeHint($typeHint->name); } if ($param->isDefaultValueAvailable()) { $paramStmt->setDefault($param->getDefaultValue()); } $methodStmt->addParam( $paramStmt ); } $methodInsideStatements = $this->getMethodInsideStatement(); $methodStmt->addStmts($methodInsideStatements); $node = $this->addMethodDocComment($methodStmt, $method); return $node; } private function addMethodDocComment(PHPParser_Builder_Method $methodStmt, \ReflectionMethod $method) { $node = $methodStmt->getNode(); $docComment = $method->getDocComment(); if ($docComment) { $node->setAttribute('comments', [new PHPParser_Comment_Doc($docComment)]); } return $node; } private function getMethodInsideStatement() { $code = $this->getWeavedMethodTemplate(); $node = $this->parser->parse($code)[0]; $node = $node->getMethods()[0]; return $node->stmts; } private function getWeavedMethodTemplate() { return file_get_contents(__DIR__ . '/Compiler/Template.php'); } public function serialize() { unset($this->factory); unset($this->parser); return serialize([$this->classDir, $this->printer]); } public function unserialize($data) { list($this->classDir, $this->printer) = unserialize($data); } } } namespace Ray\Di { use Ray\Aop\Bind; interface LoggerInterface { public function log($class, array $params, array $setter, $object, Bind $bind); } } namespace Ray\Di { use Ray\Aop\Bind; class Logger implements LoggerInterface, \IteratorAggregate, \Serializable { private $logMessages = []; private $logs = []; private $hashes = []; public function log($class, array $params, array $setter, $object, Bind $bind) { $this->logs[] = [$class, $params, $setter, $object, $bind]; $setterLog = []; foreach ($setter as $method => $methodParams) { $setterLog[] = $method . ':'. $this->getParamString((array)$methodParams); } $setter = $setter ? implode(' ', $setterLog) : ''; $logMessage = "class:{$class} $setter"; $this->logMessages[] = $logMessage; } private function getParamString(array $params) { foreach ($params as &$param) { if (is_object($param)) { $param = get_class($param) . '#' . $this->getScope($param); } elseif (is_callable($param)) { $param = "(callable) {$param}"; } elseif (is_scalar($param)) { $param = '(' . gettype($param) . ') ' . (string)$param; } elseif (is_array($param)) { $param = str_replace(["\n", " "], '', print_r($param, true)); } } return implode(', ', $params); } private function getScope($object) { $hash = spl_object_hash($object); if (in_array($hash, $this->hashes)) { return 'singleton'; } $this->hashes[] = $hash; return 'prototype'; } public function __toString() { return implode(PHP_EOL, $this->logMessages); } public function getIterator() { return new \ArrayIterator($this->logs); } public function serialize() { return ''; } public function unserialize($serialized) { unset($serialized); return ''; } } } namespace BEAR\Sunday\Module\Framework { use BEAR\Sunday\Module; use Ray\Di\AbstractModule; use Ray\Di\Injector; class FrameworkModule extends AbstractModule { protected function configure() { $this->install(new Module\Cache\CacheModule); $this->install(new Module\Code\CachedAnnotationModule); $this->install(new Module\Resource\ResourceModule($this)); } } } namespace BEAR\Sunday\Module\Cache { use Ray\Di\AbstractModule; use Ray\Di\Di\Scope; class CacheModule extends AbstractModule { protected function configure() { $this ->bind('Guzzle\Cache\AbstractCacheAdapter') ->toProvider('BEAR\Sunday\Module\Cache\CacheProvider') ->in(Scope::SINGLETON); } } } namespace Ray\Di { interface ProviderInterface { public function get(); } } namespace BEAR\Sunday\Inject { trait TmpDirInject { private $tmpDir; public function setTmpDir($tmpDir) { $this->tmpDir = $tmpDir; } } } namespace BEAR\Sunday\Module\Cache { use BEAR\Sunday\Inject\TmpDirInject; use Doctrine\Common\Cache\ApcCache; use Doctrine\Common\Cache\FilesystemCache; use Guzzle\Cache\DoctrineCacheAdapter as CacheAdapter; use Ray\Di\ProviderInterface as Provide; class CacheProvider implements Provide { use TmpDirInject; public function get() { if (ini_get('apc.enabled')) { return new CacheAdapter(new ApcCache); } return new CacheAdapter(new FilesystemCache($this->tmpDir . '/cache')); } } } namespace Ray\Di\Di { interface Annotation { } } namespace Ray\Di\Di { final class Scope implements Annotation { const SINGLETON = 'Singleton'; const PROTOTYPE = 'Prototype'; public $value = self::PROTOTYPE; } } namespace BEAR\Sunday\Module\Code { use Ray\Di\AbstractModule; class CachedAnnotationModule extends AbstractModule { protected function configure() { $this ->bind('Doctrine\Common\Annotations\Reader') ->toProvider('BEAR\Sunday\Module\Code\CachedReaderProvider'); } } } namespace BEAR\Sunday\Module\Code { use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\CachedReader; use Doctrine\Common\Cache\ApcCache; use Ray\Di\ProviderInterface as Provide; class CachedReaderProvider implements Provide { public function get() { $reader = new CachedReader(new AnnotationReader, new ApcCache, true); return $reader; } } } namespace BEAR\Sunday\Module\Resource { use Ray\Di\AbstractModule; use Ray\Di\Injector; use Ray\Di\Scope; use BEAR\Resource\Module\ResourceModule as BearResourceModule; use Ray\Di\Di\Inject; use Ray\Di\Di\Named; class ResourceModule extends AbstractModule { protected $appName; public function setAppName($appName) { $this->appName = $appName; } protected function configure() { $this->bind('BEAR\Resource\LoggerInterface')->toProvider(__NAMESPACE__ . '\ResourceLoggerProvider')->in(Scope::SINGLETON); $this->install(new BearResourceModule($this->appName)); } } } namespace BEAR\Sunday\Module\Resource { use BEAR\Resource\LoggerInterface; use BEAR\Sunday\Extension\Application\AppInterface; use Ray\Di\ProviderInterface; use Ray\Di\Di\Inject; use Ray\Di\Di\Named; class ResourceLoggerProvider implements ProviderInterface { private static $instance; private $logger; public function setLoggerClassName(LoggerInterface $logger) { $this->logger = $logger; } public function get() { if (!self::$instance) { self::$instance = $this->logger; } return self::$instance; } } } namespace BEAR\Resource\Module { use Ray\Di\AbstractModule; use Ray\Di\Module\InjectorModule; use Ray\Di\Scope; class ResourceModule extends AbstractModule { private $appName; public function __construct($appName) { $this->appName = $appName; } protected function configure() { $this->install(new InjectorModule($this)); $this->bind()->annotatedWith('app_name')->toInstance($this->appName); $this->bind('BEAR\Resource\ResourceInterface')->to('BEAR\Resource\Resource')->in(Scope::SINGLETON); $this->bind('BEAR\Resource\InvokerInterface')->to('BEAR\Resource\Invoker')->in(Scope::SINGLETON); $this->bind('BEAR\Resource\LinkerInterface')->to('BEAR\Resource\Linker')->in(Scope::SINGLETON); $this->bind('BEAR\Resource\LoggerInterface')->annotatedWith("resource_logger")->to('BEAR\Resource\Logger'); $this->bind('BEAR\Resource\HrefInterface')->to('BEAR\Resource\A'); $this->bind('BEAR\Resource\SignalParameterInterface')->to('BEAR\Resource\SignalParameter'); $this->bind('BEAR\Resource\FactoryInterface')->to('BEAR\Resource\Factory')->in(Scope::SINGLETON); $this->bind('BEAR\Resource\SchemeCollectionInterface')->toProvider('BEAR\Resource\Module\SchemeCollectionProvider')->in(Scope::SINGLETON); $this->bind('Aura\Signal\Manager')->toProvider('BEAR\Resource\Module\SignalProvider')->in(Scope::SINGLETON); $this->bind('Guzzle\Parser\UriTemplate\UriTemplateInterface')->to('Guzzle\Parser\UriTemplate\UriTemplate')->in(Scope::SINGLETON); $this->bind('Ray\Di\InjectorInterface')->toInstance($this->dependencyInjector); $this->bind('BEAR\Resource\ParamInterface')->to('BEAR\Resource\Param'); } } } namespace Ray\Di\Module { use Ray\Di\AbstractModule; use Ray\Di\Exception; use Ray\Di\Scope; use Ray\Aop\Bind; class InjectorModule extends AbstractModule { protected function configure() { $this->bind('Aura\Di\ConfigInterface')->to('Ray\Di\Config'); $this->bind('Aura\Di\ContainerInterface')->to('Ray\Di\Container'); $this->bind('Aura\Di\ForgeInterface')->to('Ray\Di\Forge'); $this->bind('Ray\Di\InjectorInterface')->to('Ray\Di\Injector')->in(Scope::SINGLETON); $this->bind('Ray\Di\AnnotationInterface')->to('Ray\Di\Annotation'); $this->bind('Ray\Aop\CompilerInterface')->toProvider(__NAMESPACE__ . '\Provider\CompilerProvider'); $this->bind('Ray\Aop\BindInterface')->toInstance(new Bind); $this->bind('Ray\Di\AbstractModule')->toInstance($this); $this->bind('Doctrine\Common\Annotations\Reader')->to('Doctrine\Common\Annotations\AnnotationReader')->in(Scope::SINGLETON); } } } namespace Ray\Di\Module\Provider { use Ray\Aop\Compiler; use PHPParser_PrettyPrinter_Default; use PHPParser_Parser; use PHPParser_Lexer; use PHPParser_BuilderFactory; use Ray\Di\ProviderInterface; class CompilerProvider implements ProviderInterface { public function get() { return new Compiler( sys_get_temp_dir(), new PHPParser_PrettyPrinter_Default ); } } } namespace BEAR\Resource\Module { use BEAR\Resource\Adapter\App as AppAdapter; use BEAR\Resource\Adapter\Http as HttpAdapter; use BEAR\Resource\Exception\AppName; use BEAR\Resource\SchemeCollection; use Ray\Di\ProviderInterface as Provide; use Ray\Di\InjectorInterface; use Ray\Di\Di\Inject; use Ray\Di\Di\Named; class SchemeCollectionProvider implements Provide { private $appName; public function setAppName($appName) { if (is_null($appName)) { throw new AppName($appName); } $this->appName = $appName; } public function setInjector(InjectorInterface $injector) { $this->injector = $injector; } public function get() { $schemeCollection = new SchemeCollection; $pageAdapter = new AppAdapter($this->injector, $this->appName, 'Resource\Page'); $appAdapter = new AppAdapter($this->injector, $this->appName, 'Resource\App'); $schemeCollection->scheme('page')->host('self')->toAdapter($pageAdapter); $schemeCollection->scheme('app')->host('self')->toAdapter($appAdapter); $schemeCollection->scheme('http')->host('*')->toAdapter(new HttpAdapter); return $schemeCollection; } } } namespace BEAR\Resource\Module { use Aura\Signal\HandlerFactory; use Aura\Signal\Manager; use Aura\Signal\ResultCollection; use Aura\Signal\ResultFactory; use Ray\Di\ProviderInterface; class SignalProvider implements ProviderInterface { public function get() { return new Manager(new HandlerFactory, new ResultFactory, new ResultCollection); } } } namespace BEAR\Sunday\Module\Resource { use Ray\Di\AbstractModule; class ResourceCacheModule extends AbstractModule { protected function configure() { $this ->bind('Guzzle\Cache\CacheAdapterInterface') ->annotatedWith('resource_cache') ->toProvider('BEAR\Sunday\Module\Cache\CacheProvider'); } } } namespace BEAR\Sunday\Module\Constant { use Ray\Di\AbstractModule; class NamedModule extends AbstractModule { public function __construct(array $names) { $names += [ 'sunday_dir' =>dirname(dirname(dirname(dirname(dirname(__DIR__))))) ]; $this->names = $names; parent::__construct(); } protected function configure() { foreach ($this->names as $annotatedWith => $instance) { $this->bind()->annotatedWith($annotatedWith)->toInstance($instance); } } } } namespace BEAR\Sunday\Extension { interface ExtensionInterface { } } namespace BEAR\Sunday\Extension\Application { use BEAR\Sunday\Extension\ExtensionInterface; interface AppInterface extends ExtensionInterface { } } namespace Demo\Helloworld { use BEAR\Resource\ResourceInterface; use BEAR\Sunday\Extension\Application\AppInterface; use Ray\Di\Di\Inject; final class App implements AppInterface { public $resource; public function __construct(ResourceInterface $resource) { $this->resource = $resource; } } } namespace BEAR\Resource { use Ray\Di\Di\ImplementedBy; interface ResourceInterface { public function newInstance($uri); public function object($ro); public function uri($uri); public function withQuery(array $query); public function addQuery(array $query); public function request(); public function linkSelf($linkKey); public function linkNew($linkKey); public function linkCrawl($linkKey); public function attachParamProvider($varName, ParamProviderInterface $provider); public function href($rel, array $query = []); public function setExceptionHandler(ExceptionHandlerInterface $exceptionHandler); } } namespace BEAR\Resource { use BEAR\Resource\Exception; use Guzzle\Cache\CacheAdapterInterface; use SplObjectStorage; use Ray\Di\Di\Scope; use Ray\Di\Di\Inject; use Ray\Di\Di\Named; class Resource implements ResourceInterface { private $factory; private $invoker; private $request; private $requests; private $cache; private $appName = ''; private $anchor; public function setAppName($appName) { $this->appName = $appName; } public function setCacheAdapter(CacheAdapterInterface $cache) { $this->cache = $cache; } public function setSchemeCollection(SchemeCollectionInterface $scheme) { $this->factory->setSchemeCollection($scheme); } public function __construct( Factory $factory, InvokerInterface $invoker, Request $request, Anchor $anchor ) { $this->factory = $factory; $this->invoker = $invoker; $this->newRequest = $request; $this->requests = new SplObjectStorage; $this->invoker->setResourceClient($this); $this->anchor = $anchor; } public function newInstance($uri) { $useCache = $this->cache instanceof CacheAdapterInterface; if ($useCache === true) { $key = $this->appName . 'res-' . str_replace('/', '-', $uri); $cached = $this->cache->fetch($key); if ($cached) { return $cached; } } $instance = $this->factory->newInstance($uri); if ($useCache === true) { $this->cache->save($key, $instance); } return $instance; } public function object($ro) { $this->request->ro = $ro; return $this; } public function uri($uri) { if ($uri instanceof Uri) { $this->request->ro = $this->newInstance($uri->uri); $this->withQuery($uri->query); return $this; } if (! $this->request) { throw new Exception\BadRequest('Request method (get/put/post/delete/options) required before uri()'); } if (filter_var($uri, FILTER_VALIDATE_URL) === false) { throw new Exception\Uri($uri); } if (strpos($uri, '?') !== false) { $parsed = parse_url($uri); $uri = $parsed['scheme'] . '://' . $parsed['host'] . $parsed['path']; if (isset($parsed['query'])) { parse_str($parsed['query'], $query); $this->withQuery($query); } } $this->request->ro = $this->newInstance($uri); $this->request->uri = $uri; return $this; } public function withQuery(array $query) { $this->request->query = $query; return $this; } public function addQuery(array $query) { $this->request->query = array_merge($this->request->query, $query); return $this; } public function linkSelf($linkKey) { $this->request->links[] = new LinkType($linkKey, LinkType::SELF_LINK); return $this; } public function linkNew($linkKey) { $this->request->links[] = new LinkType($linkKey, LinkType::NEW_LINK); return $this; } public function linkCrawl($linkKey) { $this->request->links[] = new LinkType($linkKey, LinkType::CRAWL_LINK); return $this; } public function request() { $this->request->ro->uri = $this->request->toUri(); if (isset($this->request->options['sync'])) { $this->requests->attach($this->request); $this->request = clone $this->newRequest; return $this; } if ($this->request->in !== 'eager') { return clone $this->request; } return $this->invoke(); } public function href($rel, array $query = []) { list($method, $uri) = $this->anchor->href($rel, $this->request, $query); $linkedResource = $this->{$method}->uri($uri)->eager->request(); return $linkedResource; } private function invoke() { if ($this->requests->count() === 0) { return $this->invoker->invoke($this->request); } $this->requests->attach($this->request); return $this->invoker->invokeSync($this->requests); } public function attachParamProvider($varName, ParamProviderInterface $provider) { $this->invoker->attachParamProvider($varName, $provider); return $this; } public function setExceptionHandler(ExceptionHandlerInterface $exceptionHandler) { $this->invoker->setExceptionHandler($exceptionHandler); } public function __get($name) { if (in_array($name, ['get', 'post', 'put', 'delete', 'head', 'options'])) { $this->request = clone $this->newRequest; $this->request->method = $name; return $this; } if (in_array($name, ['lazy', 'eager'])) { $this->request->in = $name; return $this; } if (in_array($name, ['sync'])) { $this->request->options[$name] = $name; return $this; } throw new Exception\BadRequest($name, 400); } public function __toString() { return $this->request->toUri(); } } } namespace BEAR\Resource { use Ray\Aop\MethodInvocation; use ReflectionParameter; interface ParamInterface { public function set(MethodInvocation $invocation, ReflectionParameter $parameter); public function getMethodInvocation(); public function getParameter(); public function inject($arg); } } namespace Guzzle\Cache { interface CacheAdapterInterface { public function contains($id, array $options = null); public function delete($id, array $options = null); public function fetch($id, array $options = null); public function save($id, $data, $lifeTime = false, array $options = null); } } namespace BEAR\Resource { use BEAR\Resource\Adapter\AdapterInterface; interface SchemeCollectionInterface { public function scheme($scheme); public function host($host); public function toAdapter(AdapterInterface $adapter); } } namespace BEAR\Resource { use Ray\Di\Di\ImplementedBy; interface FactoryInterface { public function newInstance($uri); } } namespace BEAR\Resource { use Ray\Di\Di\Inject; use Ray\Di\Di\Scope; use Ray\Di\Exception\NotReadable; class Factory implements FactoryInterface { private $scheme = []; public function __construct(SchemeCollectionInterface $scheme) { $this->scheme = $scheme; } public function setSchemeCollection(SchemeCollectionInterface $scheme) { $this->scheme = $scheme; } public function newInstance($uri) { $parsedUrl = parse_url($uri); if (!(isset($parsedUrl['scheme']) && isset($parsedUrl['scheme']))) { throw new Exception\Uri; } $scheme = $parsedUrl['scheme']; $host = $parsedUrl['host']; if (!isset($this->scheme[$scheme])) { throw new Exception\Scheme($uri); } if (!isset($this->scheme[$scheme][$host])) { if (!(isset($this->scheme[$scheme]['*']))) { throw new Exception\Scheme($uri); } $host = '*'; } try { $adapter = $this->scheme[$scheme][$host]; $resourceObject = $adapter->get($uri); } catch (NotReadable $e) { $resourceObject = $this->indexRequest($uri, $e); } $resourceObject->uri = $uri; return $resourceObject; } private function indexRequest($uri, NotReadable $e) { if (substr($uri, -1) !== '/') { throw new Exception\ResourceNotFound($uri, 0, $e); } $resourceObject = $this->newInstance($uri . 'index'); return $resourceObject; } } } namespace BEAR\Resource { use Ray\Di\Di\ImplementedBy; interface InvokerInterface { public function invoke(Request $request); public function invokeTraversal(\Traversable $requests); public function invokeSync(\SplObjectStorage $requests); public function setResourceClient(ResourceInterface $resource); public function attachParamProvider($varName, ParamProviderInterface $provider); public function setExceptionHandler(ExceptionHandlerInterface $exceptionHandler); } } namespace BEAR\Resource { use Ray\Di\Di\ImplementedBy; interface RequestInterface { public function __construct(InvokerInterface $invoker); public function withQuery(array $query); public function addQuery(array $query); public function __invoke(array $query = null); public function toUri(); public function toUriWithMethod(); public function hash(); } } namespace BEAR\Resource { use ArrayIterator; use Traversable; trait BodyArrayAccessTrait { public $body; public function offsetGet($offset) { return $this->body[$offset]; } public function offsetSet($offset, $value) { $this->body[$offset] = $value; } public function offsetExists($offset) { return isset($this->body[$offset]); } public function offsetUnset($offset) { unset($this->body[$offset]); } public function count() { return count($this->body); } public function ksort() { return ksort($this->body); } public function asort() { return asort($this->body); } public function getIterator() { $isTraversal = (is_array($this->body) || $this->body instanceof Traversable); return ($isTraversal ? new ArrayIterator($this->body) : new ArrayIterator([])); } } } namespace BEAR\Resource { use ArrayAccess; use ArrayIterator; use IteratorAggregate; use OutOfBoundsException; use Traversable; use Ray\Di\Di\Inject; use Ray\Di\Di\Scope; final class Request implements RequestInterface, ArrayAccess, IteratorAggregate { use BodyArrayAccessTrait; const SCHEME_OBJECT = 'object'; public $uri; public $ro; public $method = ''; public $query = []; public $options = []; public $in; public $links = []; private $result; public function __construct(InvokerInterface $invoker) { $this->invoker = $invoker; } public function set(ResourceObject $ro, $uri, $method, array $query) { $this->ro = $ro; $this->uri = $uri; $this->method = $method; $this->query = $query; } public function withQuery(array $query) { $this->query = $query; return $this; } public function addQuery(array $query) { $this->query = array_merge($this->query, $query); return $this; } public function __invoke(array $query = null) { if (!is_null($query)) { $this->query = array_merge($this->query, $query); } $result = $this->invoker->invoke($this); return $result; } public function toUri() { $uri = isset($this->ro->uri) && $this->ro->uri ? $this->ro->uri : $this->uri; $parsed = parse_url($uri); if ($this->query === []) { return $uri; } if (!isset($parsed['scheme'])) { return $uri; } $fullUri = $parsed['scheme'] . "://{$parsed['host']}{$parsed['path']}?" . http_build_query( $this->query, null, '&', PHP_QUERY_RFC3986 ); return $fullUri; } public function toUriWithMethod() { return "{$this->method} " . $this->toUri(); } public function __toString() { if (is_null($this->result)) { $this->result = $this->__invoke(); } return (string)$this->result; } public function offsetGet($offset) { if (is_null($this->result)) { $this->result = $this->__invoke(); } if (!isset($this->result->body[$offset])) { throw new OutOfBoundsException("[$offset] for object[" . get_class($this->result) . "]"); } return $this->result->body[$offset]; } public function offsetExists($offset) { if (is_null($this->result)) { $this->result = $this->__invoke(); } return isset($this->result->body[$offset]); } public function getIterator() { if (is_null($this->result)) { $this->result = $this->__invoke(); } $isArray = (is_array($this->result->body) || $this->result->body instanceof Traversable); $iterator = $isArray ? new ArrayIterator($this->result->body) : new ArrayIterator([]); return $iterator; } public function hash() { return md5(get_class($this->ro) . $this->method . serialize($this->query) . serialize($this->links)); } } } namespace BEAR\Resource { use BEAR\Resource\Exception; use Doctrine\Common\Annotations\AnnotationReader; use Guzzle\Parser\UriTemplate\UriTemplateInterface; use Ray\Di\Di\Scope; use BEAR\Resource\Annotation; use Ray\Di\Di\Inject; class Anchor { private $reader; private $request; public function __construct( UriTemplateInterface $uriTemplate, AnnotationReader $reader, Request $request ) { $this->uriTemplate = $uriTemplate; $this->reader = $reader; $this->request = $request; } public function href($rel, Request $request, array $query) { $classMethod = 'on' . ucfirst($request->method); $annotations = $this->reader->getMethodAnnotations(new \ReflectionMethod($request->ro, $classMethod)); foreach ($annotations as $annotation) { $isValidLinkAnnotation = $annotation instanceof Annotation\Link && isset($annotation->rel) && $annotation->rel === $rel; if ($isValidLinkAnnotation) { $query = array_merge($request->ro->body, $query); $uri = $this->uriTemplate->expand($annotation->href, $query); return [$annotation->method, $uri]; } } throw new Exception\Link("rel:{$rel} class:" . get_class($request->ro)); } } } namespace Ray\Di\Exception { interface ExceptionInterface { } } namespace Ray\Di\Exception { use LogicException; class Binding extends LogicException implements ExceptionInterface { } } namespace Ray\Di\Exception { use Ray\Di\AbstractModule; class NotBound extends Binding implements ExceptionInterface { public $module; public function setModule(AbstractModule $module) { $this->module = $module; return $this; } } } namespace Guzzle\Cache { abstract class AbstractCacheAdapter implements CacheAdapterInterface { protected $cache; public function getCacheObject() { return $this->cache; } } } namespace Guzzle\Cache { use Doctrine\Common\Cache\Cache; class DoctrineCacheAdapter extends AbstractCacheAdapter { public function __construct(Cache $cache) { $this->cache = $cache; } public function contains($id, array $options = null) { return $this->cache->contains($id); } public function delete($id, array $options = null) { return $this->cache->delete($id); } public function fetch($id, array $options = null) { return $this->cache->fetch($id); } public function save($id, $data, $lifeTime = false, array $options = null) { return $this->cache->save($id, $data, $lifeTime); } } } namespace Doctrine\Common\Cache { interface Cache { const STATS_HITS = 'hits'; const STATS_MISSES = 'misses'; const STATS_UPTIME = 'uptime'; const STATS_MEMORY_USAGE = 'memory_usage'; const STATS_MEMORY_AVAILABLE = 'memory_available'; const STATS_MEMORY_AVAILIABLE = 'memory_available'; function fetch($id); function contains($id); function save($id, $data, $lifeTime = 0); function delete($id); function getStats(); } } namespace Doctrine\Common\Cache { abstract class CacheProvider implements Cache { const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]'; private $namespace = ''; private $namespaceVersion; public function setNamespace($namespace) { $this->namespace = (string) $namespace; $this->namespaceVersion = null; } public function getNamespace() { return $this->namespace; } public function fetch($id) { return $this->doFetch($this->getNamespacedId($id)); } public function contains($id) { return $this->doContains($this->getNamespacedId($id)); } public function save($id, $data, $lifeTime = 0) { return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); } public function delete($id) { return $this->doDelete($this->getNamespacedId($id)); } public function getStats() { return $this->doGetStats(); } public function flushAll() { return $this->doFlush(); } public function deleteAll() { $namespaceCacheKey = $this->getNamespaceCacheKey(); $namespaceVersion = $this->getNamespaceVersion() + 1; $this->namespaceVersion = $namespaceVersion; return $this->doSave($namespaceCacheKey, $namespaceVersion); } private function getNamespacedId($id) { $namespaceVersion = $this->getNamespaceVersion(); return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); } private function getNamespaceCacheKey() { return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); } private function getNamespaceVersion() { if (null !== $this->namespaceVersion) { return $this->namespaceVersion; } $namespaceCacheKey = $this->getNamespaceCacheKey(); $namespaceVersion = $this->doFetch($namespaceCacheKey); if (false === $namespaceVersion) { $namespaceVersion = 1; $this->doSave($namespaceCacheKey, $namespaceVersion); } $this->namespaceVersion = $namespaceVersion; return $this->namespaceVersion; } abstract protected function doFetch($id); abstract protected function doContains($id); abstract protected function doSave($id, $data, $lifeTime = 0); abstract protected function doDelete($id); abstract protected function doFlush(); abstract protected function doGetStats(); } } namespace Doctrine\Common\Cache { class ApcCache extends CacheProvider { protected function doFetch($id) { return apc_fetch($id); } protected function doContains($id) { return apc_exists($id); } protected function doSave($id, $data, $lifeTime = 0) { return (bool) apc_store($id, $data, (int) $lifeTime); } protected function doDelete($id) { return apc_delete($id); } protected function doFlush() { return apc_clear_cache() && apc_clear_cache('user'); } protected function doGetStats() { $info = apc_cache_info(); $sma = apc_sma_info(); if (PHP_VERSION_ID >= 50500) { $info['num_hits'] = isset($info['num_hits']) ? $info['num_hits'] : $info['nhits']; $info['num_misses'] = isset($info['num_misses']) ? $info['num_misses'] : $info['nmisses']; $info['start_time'] = isset($info['start_time']) ? $info['start_time'] : $info['stime']; } return array( Cache::STATS_HITS => $info['num_hits'], Cache::STATS_MISSES => $info['num_misses'], Cache::STATS_UPTIME => $info['start_time'], Cache::STATS_MEMORY_USAGE => $info['mem_size'], Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], ); } } } namespace BEAR\Resource { use ArrayObject; use BEAR\Resource\Adapter\AdapterInterface; class SchemeCollection extends ArrayObject implements SchemeCollectionInterface { private $scheme; private $host; public function scheme($scheme) { $this->scheme = $scheme; return $this; } public function host($host) { $this->host = $host; return $this; } public function toAdapter(AdapterInterface $adapter) { $this[$this->scheme][$this->host] = $adapter; $this->scheme = $this->host = null; return $this; } } } namespace BEAR\Resource { interface ProviderInterface { public function get($uri); } } namespace BEAR\Resource\Adapter { use BEAR\Resource\ProviderInterface; interface AdapterInterface extends ProviderInterface { } } namespace BEAR\Resource\Adapter { use BEAR\Resource\Exception\AppNamespace; use Ray\Di\InjectorInterface; use Ray\Di\Di\Inject; use Ray\Di\Di\Scope; class App implements AdapterInterface { private $injector; private $namespace; private $path; public function __construct( InjectorInterface $injector, $namespace, $path ) { if (!is_string($namespace)) { throw new AppNamespace(gettype($namespace)); } $this->injector = $injector; $this->namespace = $namespace; $this->path = $path; } public function get($uri) { $parsedUrl = parse_url($uri); $path = str_replace('/', ' ', $parsedUrl['path']); $path = ucwords($path); $path = str_replace(' ', '\\', $path); $className = "{$this->namespace}\\{$this->path}{$path}"; $instance = $this->injector->getInstance($className); return $instance; } } } namespace BEAR\Resource\Adapter { use Guzzle\Service\Client as GuzzleClient; class Http implements AdapterInterface { public function get($uri) { $instance = new Http\Guzzle(new GuzzleClient($uri)); return $instance; } } } namespace BEAR\Resource { use Ray\Di\Definition; use Ray\Di\Di\Inject; use Ray\Di\Di\Named; use Ray\Di\Di\Scope; class Invoker implements InvokerInterface { private $linker; private $logger; protected $params; private $exceptionHandler; const OPTIONS = 'options'; const HEAD = 'head'; const ANNOTATION_PROVIDES = 'Provides'; public function setResourceClient(ResourceInterface $resource) { $this->linker->setResource($resource); } public function setResourceLogger(LoggerInterface $logger) { $this->logger = $logger; return $this; } public function __construct( LinkerInterface $linker, NamedParameter $params, LoggerInterface $logger = null, ExceptionHandlerInterface $exceptionHandler = null ) { $this->linker = $linker; $this->params = $params; $this->logger = $logger; $this->exceptionHandler = $exceptionHandler ?: new ExceptionHandler; } public function invoke(Request $request) { $onMethod = 'on' . ucfirst($request->method); if (method_exists($request->ro, $onMethod) !== true) { return $this->methodNotExists($request->ro, $request, $onMethod); } $args = $this->params->getArgs([$request->ro, $onMethod], $request->query); $result = null; try { $result = call_user_func_array([$request->ro, $onMethod], $args); } catch (Exception\Parameter $e) { $e = new Exception\ParameterInService('', 0, $e); $result = $this->exceptionHandler->handle($e, $request); } catch (\Exception $e) { $result = $this->exceptionHandler->handle($e, $request); } if (!$result instanceof ResourceObject) { $request->ro->body = $result; $result = $request->ro; } completed: if ($request->links) { $result = $this->linker->invoke($request); } if ($this->logger instanceof LoggerInterface) { $this->logger->log($request, $result); } return $result; } public function invokeTraversal(\Traversable $requests) { foreach ($requests as &$element) { if ($element instanceof Request || is_callable($element)) { $element = $element(); } } return $requests; } public function invokeSync(\SplObjectStorage $requests) { $requests->rewind(); $data = new \ArrayObject(); while ($requests->valid()) { $request = $requests->current(); if (method_exists($request->ro, 'onSync')) { call_user_func([$request->ro, 'onSync'], $request, $data); } $requests->next(); } $result = call_user_func([$request->ro, 'onFinalSync'], $request, $data); return $result; } protected function getOptions(ResourceObject $ro) { $ref = new \ReflectionClass($ro); $methods = $ref->getMethods(); $allow = []; foreach ($methods as $method) { $isRequestMethod = (substr($method->name, 0, 2) === 'on') && (substr($method->name, 0, 6) !== 'onLink'); if ($isRequestMethod) { $allow[] = strtolower(substr($method->name, 2)); } } $params = []; foreach ($allow as $method) { $refMethod = new \ReflectionMethod($ro, 'on' . $method); $parameters = $refMethod->getParameters(); $paramArray = []; foreach ($parameters as $parameter) { $name = $parameter->getName(); $param = $parameter->isOptional() ? "({$name})" : $name; $paramArray[] = $param; } $key = "param-{$method}"; $params[$key] = implode(',', $paramArray); } $result = ['allow' => $allow, 'params' => $params]; return $result; } private function methodNotExists(ResourceObject $ro, Request $request, $method) { if ($request->method === self::OPTIONS) { return $this->onOptions($ro); } if ($method === 'onHead' && method_exists($ro, 'onGet')) { return $this->onHead($request); } throw new Exception\MethodNotAllowed(get_class($request->ro) . "::$method()", 405); } private function onOptions(ResourceObject $ro) { $options = $this->getOptions($ro); $ro->headers['allow'] = $options['allow']; $ro->headers += $options['params']; $ro->body = null; return $ro; } private function onHead(Request $request) { if (method_exists($request->ro, 'onGet')) { $args = $this->params->getArgs([$request->ro, 'onGet'], $request->query); try { call_user_func_array([$request->ro, 'onGet'], $args); } catch (Exception\Parameter $e) { throw new Exception\ParameterInService('', 0, $e); } } $request->ro->body = ''; return $request->ro; } public function attachParamProvider($varName, ParamProviderInterface $provider) { $this->params->attachParamProvider($varName, $provider); } public function setExceptionHandler(ExceptionHandlerInterface $exceptionHandler) { $this->exceptionHandler = $exceptionHandler; } } } namespace BEAR\Resource { use IteratorAggregate; interface LoggerInterface extends IteratorAggregate { public function log(RequestInterface $request, ResourceObject $result); public function setWriter(LogWriterInterface $writer); public function write(); } } namespace BEAR\Resource { use Ray\Di\Di\ImplementedBy; interface LinkerInterface { public function invoke(Request $request); } } namespace BEAR\Resource { interface NamedParameterInterface { public function getArgs(array $callable, array $query); } } namespace BEAR\Resource { use BEAR\Resource\Exception; use Ray\Aop\ReflectiveMethodInvocation; use ReflectionParameter; use Ray\Di\Di\Inject; final class NamedParameter implements NamedParameterInterface { private $signalParameter; public function __construct(SignalParameterInterface $signalParameter = null) { $this->signalParameter = $signalParameter; } public function getArgs(array $callable, array $query) { $namedArgs = $query; $method = new \ReflectionMethod($callable[0], $callable[1]); $parameters = $method->getParameters(); $args = []; foreach ($parameters as $parameter) { if (isset($namedArgs[$parameter->name])) { $args[] = $namedArgs[$parameter->name]; continue; } if ($parameter->isDefaultValueAvailable() === true) { $args[] = $parameter->getDefaultValue(); continue; } if ($this->signalParameter) { $invocation = new ReflectiveMethodInvocation($callable, $query); $args[] = $this->signalParameter->getArg($parameter, $invocation); continue; } $msg = '$' . "{$parameter->name} in " . get_class($callable[0]) . '::' . $callable[1] . '()'; throw new Exception\Parameter($msg); } return $args; } public function attachParamProvider($varName, ParamProviderInterface $provider) { $this->signalParameter->attachParamProvider($varName, $provider); } } } namespace BEAR\Resource { interface ExceptionHandlerInterface { public function handle(\Exception $e, Request $request); } } namespace BEAR\Resource { use Any\Serializer\SerializeInterface; use Any\Serializer\Serializer; use ArrayIterator; use Countable; use Serializable; use Ray\Di\Di\Inject; use Ray\Di\Di\Scope; class Logger implements LoggerInterface, Countable, Serializable { const LOG_REQUEST = 0; const LOG_RESULT = 1; private $logs = []; private $writer; private $serializer; public function __construct(SerializeInterface $serializer = null) { $this->serializer = $serializer ? : new Serializer; } public function log(RequestInterface $request, ResourceObject $result) { $this->logs[] = [ self::LOG_REQUEST => $request, self::LOG_RESULT => $result ]; } public function setWriter(LogWriterInterface $writer) { $this->writer = $writer; } public function write() { if ($this->writer instanceof LogWriterInterface) { foreach ($this->logs as $log) { $this->writer->write($log[0], $log[1]); } $this->logs = []; return true; } return false; } public function getIterator() { return new ArrayIterator($this->logs); } public function count() { return count($this->logs); } public function serialize() { $this->logs = []; return serialize([$this->writer, $this->serializer]); } public function unserialize($data) { list($this->writer, $this->serializer) = unserialize($data); } } } namespace Any\Serializer { interface SerializeInterface { public function serialize($value); public function removeUnserializable($object); } } namespace BEAR\Resource { interface LogWriterInterface { public function write(RequestInterface $request, ResourceObject $result); } } namespace Ray\Di\Exception { class OptionalInjectionNotBound extends Binding implements ExceptionInterface { } } namespace Any\Serializer { class Serializer implements SerializeInterface { private $hash = []; public function serialize($value) { return serialize( $this->removeUnserializable($value) ); } public function removeUnserializable($value) { if (is_scalar($value)) { return $value; } if (is_array($value)) { $this->removeReferenceItemInArray($value); $this->serializeArray($value); return $value; } $hash = spl_object_hash($value); if (in_array($hash, $this->hash)) { $value = null; return $value; } $this->hash[] = $hash; $props = (new \ReflectionObject($value))->getProperties(); foreach ($props as &$prop) { $prop->setAccessible(true); $propVal = $prop->getValue($value); if (is_array($propVal)) { $this->removeUnrealizableInArray($propVal); $prop->setValue($value, $propVal); } if (is_object($propVal)) { $propVal = $this->removeUnserializable($propVal); $prop->setValue($value, $propVal); } if ($this->isUnserializable($propVal)) { $prop->setValue($value, null); } } return $value; } public function serializeArray(array &$array) { foreach ($array as &$item) { $this->removeUnserializable($item); } } private function removeUnrealizableInArray(array &$array) { $this->removeReferenceItemInArray($array); foreach ($array as &$value) { if (is_object($value)) { $value = $this->removeUnserializable($value); } if (is_array($value)) { $this->removeUnrealizableInArray($value); } if ($this->isUnserializable($value)) { $value = null; } } return $array; } private function removeReferenceItemInArray(array &$room) { $roomCopy = $room; $keys = array_keys($room); foreach ($keys as $key) { if (is_array($roomCopy[$key])) { $roomCopy[$key]['_test'] = true; if (isset($room[$key]['_test'])) { unset($room[$key]); } } } } private function isUnserializable($value) { return (is_callable($value) || is_resource($value) || $value instanceof \PDO); } } } namespace BEAR\Resource { use BEAR\Resource\Exception; use Doctrine\Common\Annotations\Reader; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\Cache; use Guzzle\Parser\UriTemplate\UriTemplate; use Guzzle\Parser\UriTemplate\UriTemplateInterface; use ReflectionMethod; use Ray\Di\Di\Inject; use Ray\Di\Di\Scope; final class Linker implements LinkerInterface { private $resource; private $uriTemplate; public function __construct( Reader $reader, Cache $cache = null, UriTemplateInterface $uriTemplate = null ) { $this->reader = $reader; $this->cache = $cache ? : new ArrayCache; $this->uriTemplate = $uriTemplate ? : new UriTemplate; } public function setResource(ResourceInterface $resource) { $this->resource = $resource; } public function invoke(Request $request) { $current = clone $request->ro; foreach ($request->links as $link) { $nextResource = $this->annotationLink($link, $current, $request); $current = $this->nextLink($link, $current, $nextResource); } return $current; } private function nextLink(LinkType $link, ResourceObject $ro, $nextResource) { $nextBody = $nextResource instanceof ResourceObject ? $nextResource->body : $nextResource; if ($link->type === LinkType::SELF_LINK) { $ro->body = $nextBody; return $ro; } if ($link->type === LinkType::NEW_LINK) { $ro->body[$link->key] = $nextBody; return $ro; } return $ro; } private function annotationLink(LinkType $link, ResourceObject $current, Request $request) { if (!(is_array($current->body))) { throw new Exception\LinkQuery('Only array is allowed for link in ' . get_class($current)); } $classMethod = 'on' . ucfirst($request->method); $annotations = $this->reader->getMethodAnnotations(new ReflectionMethod($current, $classMethod)); if ($link->type === LinkType::CRAWL_LINK) { return $this->annotationCrawl($annotations, $link, $current); } return $this->annotationRel($annotations, $link, $current)->body; } private function annotationRel(array $annotations, LinkType $link, ResourceObject $current) { foreach ($annotations as $annotation) { if ($annotation->rel !== $link->key) { continue; } $uri = $this->uriTemplate->expand($annotation->href, $current->body); try { $linkedResource = $this->resource->{$annotation->method}->uri($uri)->eager->request(); } catch (Exception\Parameter $e) { $msg = 'class:' . get_class($current) . " link:{$link->key} query:" . json_encode($current->body); throw new Exception\LinkQuery($msg, 0, $e); } return $linkedResource; } throw new Exception\LinkRel("[{$link->key}] in " . get_class($current) . ' is not available.'); } private function annotationCrawl(array $annotations, LinkType $link, ResourceObject $current) { $isList = $this->isList($current->body); $bodyList = $isList ? $current->body : [$current->body]; foreach ($bodyList as &$body) { $this->crawl($annotations, $link, $body); } $current->body = $isList ? $bodyList : $bodyList[0]; return $current; } private function crawl(array $annotations, LinkType $link, array &$body) { foreach ($annotations as $annotation) { if ($annotation->crawl !== $link->key) { continue; } $uri = $this->uriTemplate->expand($annotation->href, $body); $request = $this->resource->{$annotation->method}->uri($uri)->linkCrawl($link->key)->request(); $hash = $request->hash(); if ($this->cache->contains($hash)) { $body[$annotation->rel] = $this->cache->fetch($hash); continue; } $body[$annotation->rel] = $request()->body; $this->cache->save($hash, $body[$annotation->rel]); } } private function isList($value) { $value = array_values((array)$value); $isMultiColumnList = (count($value) > 1 && isset($value[0]) && isset($value[1]) && is_array($value[0]) && is_array($value[1]) && (array_keys($value[0]) === array_keys($value[1])) ); $isOneColumnList = (count($value) === 1) && is_array($value[0]); return ($isOneColumnList | $isMultiColumnList); } } } namespace Guzzle\Parser\UriTemplate { interface UriTemplateInterface { public function expand($template, array $variables); } } namespace Guzzle\Parser\UriTemplate { class UriTemplate implements UriTemplateInterface { const DEFAULT_PATTERN = '/\{([^\}]+)\}/'; private $template; private $variables; private $regex = self::DEFAULT_PATTERN; private static $operatorHash = array( '+' => true, '#' => true, '.' => true, '/' => true, ';' => true, '?' => true, '&' => true ); private static $delims = array( ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' ); private static $delimsPct = array( '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D' ); public function expand($template, array $variables) { if ($this->regex == self::DEFAULT_PATTERN && false === strpos($template, '{')) { return $template; } $this->template = $template; $this->variables = $variables; return preg_replace_callback($this->regex, array($this, 'expandMatch'), $this->template); } public function setRegex($regexPattern) { $this->regex = $regexPattern; } private function parseExpression($expression) { $operator = ''; if (isset(self::$operatorHash[$expression[0]])) { $operator = $expression[0]; $expression = substr($expression, 1); } $values = explode(',', $expression); foreach ($values as &$value) { $value = trim($value); $varspec = array(); $substrPos = strpos($value, ':'); if ($substrPos) { $varspec['value'] = substr($value, 0, $substrPos); $varspec['modifier'] = ':'; $varspec['position'] = (int) substr($value, $substrPos + 1); } elseif (substr($value, -1) == '*') { $varspec['modifier'] = '*'; $varspec['value'] = substr($value, 0, -1); } else { $varspec['value'] = (string) $value; $varspec['modifier'] = ''; } $value = $varspec; } return array( 'operator' => $operator, 'values' => $values ); } private function expandMatch(array $matches) { static $rfc1738to3986 = array( '+' => '%20', '%7e' => '~' ); $parsed = self::parseExpression($matches[1]); $replacements = array(); $prefix = $parsed['operator']; $joiner = $parsed['operator']; $useQueryString = false; if ($parsed['operator'] == '?') { $joiner = '&'; $useQueryString = true; } elseif ($parsed['operator'] == '&') { $useQueryString = true; } elseif ($parsed['operator'] == '#') { $joiner = ','; } elseif ($parsed['operator'] == ';') { $useQueryString = true; } elseif ($parsed['operator'] == '' || $parsed['operator'] == '+') { $joiner = ','; $prefix = ''; } foreach ($parsed['values'] as $value) { if (!array_key_exists($value['value'], $this->variables) || $this->variables[$value['value']] === null) { continue; } $variable = $this->variables[$value['value']]; $actuallyUseQueryString = $useQueryString; $expanded = ''; if (is_array($variable)) { $isAssoc = $this->isAssoc($variable); $kvp = array(); foreach ($variable as $key => $var) { if ($isAssoc) { $key = rawurlencode($key); $isNestedArray = is_array($var); } else { $isNestedArray = false; } if (!$isNestedArray) { $var = rawurlencode($var); if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { $var = $this->decodeReserved($var); } } if ($value['modifier'] == '*') { if ($isAssoc) { if ($isNestedArray) { $var = strtr(http_build_query(array($key => $var)), $rfc1738to3986); } else { $var = $key . '=' . $var; } } elseif ($key > 0 && $actuallyUseQueryString) { $var = $value['value'] . '=' . $var; } } $kvp[$key] = $var; } if (empty($variable)) { $actuallyUseQueryString = false; } elseif ($value['modifier'] == '*') { $expanded = implode($joiner, $kvp); if ($isAssoc) { $actuallyUseQueryString = false; } } else { if ($isAssoc) { foreach ($kvp as $k => &$v) { $v = $k . ',' . $v; } } $expanded = implode(',', $kvp); } } else { if ($value['modifier'] == ':') { $variable = substr($variable, 0, $value['position']); } $expanded = rawurlencode($variable); if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { $expanded = $this->decodeReserved($expanded); } } if ($actuallyUseQueryString) { if (!$expanded && $joiner != '&') { $expanded = $value['value']; } else { $expanded = $value['value'] . '=' . $expanded; } } $replacements[] = $expanded; } $ret = implode($joiner, $replacements); if ($ret && $prefix) { return $prefix . $ret; } return $ret; } private function isAssoc(array $array) { return (bool) count(array_filter(array_keys($array), 'is_string')); } private function decodeReserved($string) { return str_replace(self::$delimsPct, self::$delims, $string); } } } namespace Doctrine\Common\Cache { class ArrayCache extends CacheProvider { private $data = array(); protected function doFetch($id) { return (isset($this->data[$id])) ? $this->data[$id] : false; } protected function doContains($id) { return isset($this->data[$id]); } protected function doSave($id, $data, $lifeTime = 0) { $this->data[$id] = $data; return true; } protected function doDelete($id) { unset($this->data[$id]); return true; } protected function doFlush() { $this->data = array(); return true; } protected function doGetStats() { return null; } } } namespace BEAR\Resource { use ReflectionParameter; use Ray\Aop\MethodInvocation; interface SignalParameterInterface { public function getArg(ReflectionParameter $parameter, MethodInvocation $invocation); public function attachParamProvider($varName, ParamProviderInterface $provider); } } namespace BEAR\Resource { use Aura\Signal\Manager as Signal; use BEAR\Resource\Exception; use Ray\Aop\MethodInvocation; use ReflectionParameter; use Ray\Di\Di\Inject; class SignalParameter implements SignalParameterInterface { private $signal; private $param; public function __construct(Signal $signal, ParamInterface $param) { $this->signal = $signal; $this->param = $param; } public function getArg(ReflectionParameter $parameter, MethodInvocation $invocation) { try { $param = clone $this->param; $results = $this->sendSignal($parameter->name, $parameter, $param, $invocation, $parameter); if ($results->isStopped()) { return $param->getArg(); } $results = $this->sendSignal('*', $parameter, $param, $invocation, $parameter); if ($results->isStopped()) { return $param->getArg(); } $msg = '$' . "{$parameter->name} in " . get_class($invocation->getThis()) . '::' . $invocation->getMethod()->name; throw new Exception\Parameter($msg); } catch (\Exception $e) { throw new Exception\SignalParameter($e->getMessage(), 0, $e); } } private function sendSignal( $sigName, ReflectionParameter $parameter, ParamInterface $param, MethodInvocation $invocation, ReflectionParameter $parameter ) { $results = $this->signal->send( $this, $sigName, $param->set($invocation, $parameter) ); return $results; } public function attachParamProvider($varName, ParamProviderInterface $provider) { $this->signal->handler('*', $varName, $provider); } } } namespace Aura\Signal { class Manager { const STOP = 'Aura\Signal\Manager::STOP'; protected $handler_factory; protected $handlers = []; protected $result_collection; protected $result_factory; protected $results; protected $sorted = []; public function __construct( HandlerFactory $handler_factory, ResultFactory $result_factory, ResultCollection $result_collection, array $handlers = [] ) { $this->handler_factory = $handler_factory; $this->result_factory = $result_factory; $this->result_collection = $result_collection; foreach ($handlers as $handler) { list($sender, $signal, $callback) = $handler; if (isset($handler[3])) { $position = $handler[3]; } else { $position = 5000; } $this->handler($sender, $signal, $callback, $position); } $this->results = clone $this->result_collection; } public function handler($sender, $signal, $callback, $position = 5000) { $handler = $this->handler_factory->newInstance([ 'sender' => $sender, 'signal' => $signal, 'callback' => $callback ]); $this->handlers[$signal][(int) $position][] = $handler; $this->sorted[$signal] = false; } public function getHandlers($signal = null) { if (! $signal) { return $this->handlers; } if (! isset($this->handlers[$signal])) { return; } if (! $this->sorted[$signal]) { ksort($this->handlers[$signal]); } return $this->handlers[$signal]; } public function send($origin, $signal) { $this->results = clone $this->result_collection; $args = func_get_args(); array_shift($args); array_shift($args); $this->process($origin, $signal, $args); return $this->results; } protected function process($origin, $signal, array $args) { $list = $this->getHandlers($signal); if (! $list) { return; } foreach ($list as $position => $handlers) { foreach ($handlers as $handler) { $params = $handler->exec($origin, $signal, $args); if ($params) { $result = $this->result_factory->newInstance($params); if ($origin !== $this) { $this->process($this, 'handler_result', [$result]); } $this->results->append($result); if ($result->value === static::STOP) { return; } } } } } public function getResults() { return $this->results; } } } namespace Aura\Signal { class HandlerFactory { protected $params = [ 'sender' => null, 'signal' => null, 'callback' => null, ]; public function newInstance(array $params) { $params = array_merge($this->params, $params); return new Handler( $params['sender'], $params['signal'], $params['callback'] ); } } } namespace Aura\Signal { class ResultFactory { protected $params = [ 'origin' => null, 'sender' => null, 'signal' => null, 'value' => null, ]; public function newInstance(array $params) { $params = array_merge($this->params, $params); return new Result( $params['origin'], $params['sender'], $params['signal'], $params['value'] ); } } } namespace Aura\Signal { class ResultCollection extends \ArrayObject { public function __construct() { parent::__construct([]); } public function getLast() { $k = count($this); if ($k > 0) { return $this[$k - 1]; } } public function isStopped() { $last = $this->getLast(); if ($last) { return $last->value === Manager::STOP; } } } } namespace BEAR\Resource { class ExceptionHandler implements ExceptionHandlerInterface { public function handle(\Exception $e, Request $request) { throw $e; } } } namespace BEAR\Resource { use Exception; trait RenderTrait { protected $renderer; public function setRenderer(RenderInterface $renderer) { $this->renderer = $renderer; return $this; } public function __toString() { if (is_string($this->view)) { return $this->view; } if ($this->renderer instanceof RenderInterface) { try { $view = $this->renderer->render($this); } catch (Exception $e) { $view = ''; error_log('Exception caught in ' . __METHOD__); error_log((string)$e); } return $view; } if (is_scalar($this->body)) { return (string)$this->body; } error_log('No renderer bound for \BEAR\Resource\RenderInterface' . get_class($this) . ' in ' . __METHOD__); return ''; } } } namespace BEAR\Resource { use ArrayAccess; use Countable; use IteratorAggregate; use Ray\Di\Di\Inject; abstract class ResourceObject implements ArrayAccess, Countable, IteratorAggregate { use BodyArrayAccessTrait; use RenderTrait; public $uri = ''; public $code = 200; public $headers = []; public $view; public $links = []; } } namespace Demo\Helloworld\Resource\App { use BEAR\Resource\ResourceObject; class Hello extends ResourceObject { public function onGet($name) { $this['greeting'] = 'Hello ' . $name; $this['time'] = date('r'); return $this; } } } namespace BEAR\Resource { interface RenderInterface { public function render(ResourceObject $resourceObject); } } namespace Demo\Helloworld\Resource\Page { use BEAR\Resource\ResourceObject; class Hello extends ResourceObject { public function onGet($name = 'World') { $this->body = 'Hello ' . $name; return $this; } } } namespace Demo\Helloworld\Resource\Page { use BEAR\Resource\ResourceObject; class Minhello extends ResourceObject { public $body = 'Hello World !'; public function onGet() { return $this; } } }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment