/** * Resolve class alias * @param Reflector $reflection * @param string $className * @return string */ public static function resolve(Reflector $reflection, $className) { if ($reflection instanceof ReflectionProperty || $reflection instanceof ReflectionMethod) { $reflection = $reflection->getDeclaringClass(); } $docs = (new DocComment())->forClass($reflection); $use = $docs['use']; $ns = $docs['namespace']; $aliases = $docs['useAliases']; // Resolve to itself with keywords if ($className === 'self' || $className === 'static') { $fqn = $ns . '\\' . $docs['className']; return $fqn; } // This is for same namespaced class as current class $aliases[$ns . '\\' . $className] = $className; if (in_array($className, $use)) { return $className; } foreach ($use as $useClause) { $patternClass = preg_quote($className); $pattern = "~\\\\{$patternClass}\$~"; if (preg_match($pattern, $useClause)) { return $useClause; } } foreach ($aliases as $useClause => $alias) { if ($className == $alias) { NameNormalizer::normalize($useClause, false); return $useClause; } } return $className; }
public function testNameNormalizerWithTrailingSlash() { $test = ['\\' => '\\', 'Maslosoft\\Addendum\\Annotations' => '\\Maslosoft\\Addendum\\Annotations', 'Maslosoft\\AddendumTest\\Annotations' => '\\Maslosoft\\AddendumTest\\Annotations', '\\Maslosoft\\AddendumTest\\Annotations\\SignaledNs' => '\\Maslosoft\\AddendumTest\\Annotations\\SignaledNs']; foreach ($test as $actual => $expected) { NameNormalizer::normalize($actual, true); $this->assertSame($actual, $expected); } }
/** * TODO Move to addendum project */ public function testIfWillNormalizeClassName() { $config = ['Some\\Namespace\\' => '\\Some\\Namespace', '\\Some\\Namespace' => '\\Some\\Namespace', '\\Some\\\\Namespace' => '\\Some\\Namespace', 'GlobalNamespace' => '\\GlobalNamespace', '\\GlobalNamespace' => '\\GlobalNamespace', '\\GlobalNamespace\\' => '\\GlobalNamespace']; foreach ($config as $src => $dest) { $title = sprintf('Namespace `%s` should be `%s`', $src, $dest); $this->specify($title, function () use($src, $dest) { NameNormalizer::normalize($src); $this->assertSame($dest, $src); }); } }
public function init() { $data = ParamsExpander::expand($this, ['class']); $class = ''; if (isset($data['class'])) { $class = $data['class']; } // Log only, as it is designed as soft-fail if (empty($class) || !ClassChecker::exists($class)) { (new Signal())->getLogger()->warning(sprintf('Class not found for SignalFor annotation on model `%s`', $this->getMeta()->type()->name)); return; } NameNormalizer::normalize($class); $this->getEntity()->signalFor[] = $class; }
/** * Call for signals from slot * @param object $slot * @param string $interface Interface or class name which must be implemented, instanceof or sub class of to get into slot */ public function gather($slot, $interface = null) { $name = get_class($slot); NameNormalizer::normalize($name); if (!empty($interface)) { NameNormalizer::normalize($interface); } if (empty(self::$config)) { $this->init(); } if (!isset(self::$config[self::Slots][$name])) { self::$config[self::Slots][$name] = []; $this->loggerInstance->debug('No signals found for slot `{name}`, skipping', ['name' => $name]); } $result = []; foreach ((array) self::$config[self::Slots][$name] as $fqn => $emit) { if (false === $emit) { continue; } if (!PreFilter::filter($this, $fqn, $slot)) { continue; } // Check if class exists and log if doesn't if (!ClassChecker::exists($fqn)) { $this->loggerInstance->debug(sprintf("Class `%s` not found while gathering slot `%s`", $fqn, get_class($slot))); continue; } if (null === $interface) { $injected = new $fqn(); if (!PostFilter::filter($this, $injected, $slot)) { continue; } $result[] = $injected; continue; } // Check if it's same as interface if ($fqn === $interface) { $injected = new $fqn(); if (!PostFilter::filter($this, $injected, $slot)) { continue; } $result[] = $injected; continue; } $info = new ReflectionClass($fqn); // Check if class is instance of base class if ($info->isSubclassOf($interface)) { $injected = new $fqn(); if (!PostFilter::filter($this, $injected, $slot)) { continue; } $result[] = $injected; continue; } $interfaceInfo = new ReflectionClass($interface); // Check if class implements interface if ($interfaceInfo->isInterface() && $info->implementsInterface($interface)) { $injected = new $fqn(); if (!PostFilter::filter($this, $injected, $slot)) { continue; } $result[] = $injected; continue; } } return $result; }
/** * @param string $file */ public function processFile($file, $contents) { $file = realpath($file); $this->paths[] = $file; // Remove initial `\` from namespace try { $annotated = AnnotationUtility::rawAnnotate($file); } catch (ParseException $e) { $this->log($e, $file); return; } catch (UnexpectedValueException $e) { $this->log($e, $file); return; } $namespace = preg_replace('~^\\\\+~', '', $annotated['namespace']); $className = $annotated['className']; // Use fully qualified name, class must autoload $fqn = $namespace . '\\' . $className; NameNormalizer::normalize($fqn); try { $info = new ReflectionClass($fqn); } catch (ReflectionException $e) { $this->log($e, $file); return; } $isAnnotated = $info->implementsInterface(AnnotatedInterface::class); $hasSignals = $this->hasSignals($contents); $isAbstract = $info->isAbstract() || $info->isInterface(); // Old classes must now implement interface // Brake BC! if ($hasSignals && !$isAnnotated && !$isAbstract) { throw new UnexpectedValueException(sprintf('Class %s must implement %s to use signals', $fqn, AnnotatedInterface::class)); } // Skip not annotated class if (!$isAnnotated) { return; } // Skip abstract classes if ($isAbstract) { return; } $meta = @SignalsMeta::create($fqn); /* @var $typeMeta DocumentTypeMeta */ $typeMeta = $meta->type(); // Signals foreach ($typeMeta->signalFor as $slot) { $this->data[Signal::Slots][$slot][$fqn] = true; } // Slots // For constructor injection foreach ($typeMeta->slotFor as $slot) { $key = implode('@', [$fqn, '__construct', '()']); $this->data[Signal::Signals][$slot][$fqn][$key] = true; } // For method injection foreach ($meta->methods() as $methodName => $method) { /* @var $method DocumentMethodMeta */ foreach ($method->slotFor as $slot) { $key = implode('@', [$fqn, $methodName, '()']); $this->data[Signal::Signals][$slot][$fqn][$key] = sprintf('%s()', $methodName); } } // For property injection foreach ($meta->fields() as $fieldName => $field) { /* @var $field DocumentPropertyMeta */ foreach ($field->slotFor as $slot) { $key = implode('@', [$fqn, $fieldName]); $this->data[Signal::Signals][$slot][$fqn][$key] = sprintf('%s', $fieldName); } } }
/** * Add annotations namespace. * Every added namespace will be included in annotation name resolving for current instance. * * @param string $ns * @renturn Addendum */ public function addNamespace($ns) { NameNormalizer::normalize($ns, false); if (!in_array($ns, $this->namespaces)) { $before = count($this->namespaces); $this->namespaces[] = $ns; $this->namespaces = array_unique($this->namespaces); $after = count($this->namespaces); if ($after !== $before) { $this->nameKeys = array_flip($this->namespaces); Cache\NsCache::$addeNs = true; } $this->di->store($this, [], true); // Reconfigure flyweight instances if present if (!empty(self::$addendums[$this->instanceId])) { self::$addendums[$this->instanceId]->di->configure(self::$addendums[$this->instanceId]); } } return $this; }
/** * Normalize class name and namespace to proper fully qualified name * @param string $ns * @param string $class * @return string */ private function normalizeFqn($ns, $class) { $fqn = "\\{$ns}\\{$class}"; NameNormalizer::normalize($fqn); return $fqn; }