Created
February 5, 2014 16:21
-
-
Save koriym/8827311 to your computer and use it in GitHub Desktop.
minified BEAR.Sunday Demo.Helloworld app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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