Chris@13: true, Chris@13: 'parent' => true, Chris@13: 'static' => true, Chris@13: ]; Chris@13: Chris@0: /** Chris@0: * Constructs a name node. Chris@0: * Chris@13: * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor) Chris@13: * @param array $attributes Additional attributes Chris@0: */ Chris@13: public function __construct($name, array $attributes = []) { Chris@0: parent::__construct($attributes); Chris@0: $this->parts = self::prepareName($name); Chris@0: } Chris@0: Chris@13: public function getSubNodeNames() : array { Chris@13: return ['parts']; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the first part of the name, i.e. everything before the first namespace separator. Chris@0: * Chris@0: * @return string First part of the name Chris@0: */ Chris@13: public function getFirst() : string { Chris@0: return $this->parts[0]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the last part of the name, i.e. everything after the last namespace separator. Chris@0: * Chris@0: * @return string Last part of the name Chris@0: */ Chris@13: public function getLast() : string { Chris@0: return $this->parts[count($this->parts) - 1]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether the name is unqualified. (E.g. Name) Chris@0: * Chris@0: * @return bool Whether the name is unqualified Chris@0: */ Chris@13: public function isUnqualified() : bool { Chris@13: return 1 === count($this->parts); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether the name is qualified. (E.g. Name\Name) Chris@0: * Chris@0: * @return bool Whether the name is qualified Chris@0: */ Chris@13: public function isQualified() : bool { Chris@0: return 1 < count($this->parts); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether the name is fully qualified. (E.g. \Name) Chris@0: * Chris@0: * @return bool Whether the name is fully qualified Chris@0: */ Chris@13: public function isFullyQualified() : bool { Chris@0: return false; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name) Chris@0: * Chris@0: * @return bool Whether the name is relative Chris@0: */ Chris@13: public function isRelative() : bool { Chris@0: return false; Chris@0: } Chris@0: Chris@0: /** Chris@13: * Returns a string representation of the name itself, without taking taking the name type into Chris@13: * account (e.g., not including a leading backslash for fully qualified names). Chris@13: * Chris@13: * @return string String representation Chris@13: */ Chris@13: public function toString() : string { Chris@13: return implode('\\', $this->parts); Chris@13: } Chris@13: Chris@13: /** Chris@13: * Returns a string representation of the name as it would occur in code (e.g., including Chris@13: * leading backslash for fully qualified names. Chris@13: * Chris@13: * @return string String representation Chris@13: */ Chris@13: public function toCodeString() : string { Chris@13: return $this->toString(); Chris@13: } Chris@13: Chris@13: /** Chris@13: * Returns lowercased string representation of the name, without taking the name type into Chris@13: * account (e.g., no leading backslash for fully qualified names). Chris@13: * Chris@13: * @return string Lowercased string representation Chris@13: */ Chris@13: public function toLowerString() : string { Chris@13: return strtolower(implode('\\', $this->parts)); Chris@13: } Chris@13: Chris@13: /** Chris@13: * Checks whether the identifier is a special class name (self, parent or static). Chris@13: * Chris@13: * @return bool Whether identifier is a special class name Chris@13: */ Chris@13: public function isSpecialClassName() : bool { Chris@13: return count($this->parts) === 1 Chris@13: && isset(self::$specialClassNames[strtolower($this->parts[0])]); Chris@13: } Chris@13: Chris@13: /** Chris@0: * Returns a string representation of the name by imploding the namespace parts with the Chris@0: * namespace separator. Chris@0: * Chris@0: * @return string String representation Chris@0: */ Chris@13: public function __toString() : string { Chris@0: return implode('\\', $this->parts); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a slice of a name (similar to array_slice). Chris@0: * Chris@0: * This method returns a new instance of the same type as the original and with the same Chris@0: * attributes. Chris@0: * Chris@0: * If the slice is empty, null is returned. The null value will be correctly handled in Chris@0: * concatenations using concat(). Chris@0: * Chris@0: * Offset and length have the same meaning as in array_slice(). Chris@0: * Chris@0: * @param int $offset Offset to start the slice at (may be negative) Chris@0: * @param int|null $length Length of the slice (may be negative) Chris@0: * Chris@0: * @return static|null Sliced name Chris@0: */ Chris@13: public function slice(int $offset, int $length = null) { Chris@0: $numParts = count($this->parts); Chris@0: Chris@0: $realOffset = $offset < 0 ? $offset + $numParts : $offset; Chris@0: if ($realOffset < 0 || $realOffset > $numParts) { Chris@0: throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset)); Chris@0: } Chris@0: Chris@0: if (null === $length) { Chris@0: $realLength = $numParts - $realOffset; Chris@0: } else { Chris@0: $realLength = $length < 0 ? $length + $numParts - $realOffset : $length; Chris@0: if ($realLength < 0 || $realLength > $numParts) { Chris@0: throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length)); Chris@0: } Chris@0: } Chris@0: Chris@0: if ($realLength === 0) { Chris@0: // Empty slice is represented as null Chris@0: return null; Chris@0: } Chris@0: Chris@0: return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Concatenate two names, yielding a new Name instance. Chris@0: * Chris@0: * The type of the generated instance depends on which class this method is called on, for Chris@0: * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance. Chris@0: * Chris@0: * If one of the arguments is null, a new instance of the other name will be returned. If both Chris@0: * arguments are null, null will be returned. As such, writing Chris@0: * Name::concat($namespace, $shortName) Chris@0: * where $namespace is a Name node or null will work as expected. Chris@0: * Chris@13: * @param string|string[]|self|null $name1 The first name Chris@13: * @param string|string[]|self|null $name2 The second name Chris@13: * @param array $attributes Attributes to assign to concatenated name Chris@0: * Chris@0: * @return static|null Concatenated name Chris@0: */ Chris@0: public static function concat($name1, $name2, array $attributes = []) { Chris@0: if (null === $name1 && null === $name2) { Chris@0: return null; Chris@0: } elseif (null === $name1) { Chris@0: return new static(self::prepareName($name2), $attributes); Chris@13: } elseif (null === $name2) { Chris@0: return new static(self::prepareName($name1), $attributes); Chris@0: } else { Chris@0: return new static( Chris@0: array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Prepares a (string, array or Name node) name for use in name changing methods by converting Chris@0: * it to an array. Chris@0: * Chris@13: * @param string|string[]|self $name Name to prepare Chris@0: * Chris@13: * @return string[] Prepared name Chris@0: */ Chris@13: private static function prepareName($name) : array { Chris@0: if (\is_string($name)) { Chris@13: if ('' === $name) { Chris@13: throw new \InvalidArgumentException('Name cannot be empty'); Chris@13: } Chris@13: Chris@0: return explode('\\', $name); Chris@0: } elseif (\is_array($name)) { Chris@13: if (empty($name)) { Chris@13: throw new \InvalidArgumentException('Name cannot be empty'); Chris@13: } Chris@13: Chris@0: return $name; Chris@0: } elseif ($name instanceof self) { Chris@0: return $name->parts; Chris@0: } Chris@0: Chris@0: throw new \InvalidArgumentException( Chris@0: 'Expected string, array of parts or Name instance' Chris@0: ); Chris@0: } Chris@13: Chris@13: public function getType() : string { Chris@13: return 'Name'; Chris@13: } Chris@0: }