1: <?php declare(strict_types=1);
2:
3: namespace PhpParser\Node;
4:
5: use PhpParser\NodeAbstract;
6:
7: class Name extends NodeAbstract
8: {
9: /** @var string[] Parts of the name */
10: public $parts;
11:
12: private static $specialClassNames = [
13: 'self' => true,
14: 'parent' => true,
15: 'static' => true,
16: ];
17:
18: /**
19: * Constructs a name node.
20: *
21: * @param string|string[]|self $name Name as string, part array or Name instance (copy ctor)
22: * @param array $attributes Additional attributes
23: */
24: public function __construct($name, array $attributes = []) {
25: $this->attributes = $attributes;
26: $this->parts = self::prepareName($name);
27: }
28:
29: public function getSubNodeNames() : array {
30: return ['parts'];
31: }
32:
33: /**
34: * Gets the first part of the name, i.e. everything before the first namespace separator.
35: *
36: * @return string First part of the name
37: */
38: public function getFirst() : string {
39: return $this->parts[0];
40: }
41:
42: /**
43: * Gets the last part of the name, i.e. everything after the last namespace separator.
44: *
45: * @return string Last part of the name
46: */
47: public function getLast() : string {
48: return $this->parts[count($this->parts) - 1];
49: }
50:
51: /**
52: * Checks whether the name is unqualified. (E.g. Name)
53: *
54: * @return bool Whether the name is unqualified
55: */
56: public function isUnqualified() : bool {
57: return 1 === count($this->parts);
58: }
59:
60: /**
61: * Checks whether the name is qualified. (E.g. Name\Name)
62: *
63: * @return bool Whether the name is qualified
64: */
65: public function isQualified() : bool {
66: return 1 < count($this->parts);
67: }
68:
69: /**
70: * Checks whether the name is fully qualified. (E.g. \Name)
71: *
72: * @return bool Whether the name is fully qualified
73: */
74: public function isFullyQualified() : bool {
75: return false;
76: }
77:
78: /**
79: * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
80: *
81: * @return bool Whether the name is relative
82: */
83: public function isRelative() : bool {
84: return false;
85: }
86:
87: /**
88: * Returns a string representation of the name itself, without taking the name type into
89: * account (e.g., not including a leading backslash for fully qualified names).
90: *
91: * @return string String representation
92: */
93: public function toString() : string {
94: return implode('\\', $this->parts);
95: }
96:
97: /**
98: * Returns a string representation of the name as it would occur in code (e.g., including
99: * leading backslash for fully qualified names.
100: *
101: * @return string String representation
102: */
103: public function toCodeString() : string {
104: return $this->toString();
105: }
106:
107: /**
108: * Returns lowercased string representation of the name, without taking the name type into
109: * account (e.g., no leading backslash for fully qualified names).
110: *
111: * @return string Lowercased string representation
112: */
113: public function toLowerString() : string {
114: return strtolower(implode('\\', $this->parts));
115: }
116:
117: /**
118: * Checks whether the identifier is a special class name (self, parent or static).
119: *
120: * @return bool Whether identifier is a special class name
121: */
122: public function isSpecialClassName() : bool {
123: return count($this->parts) === 1
124: && isset(self::$specialClassNames[strtolower($this->parts[0])]);
125: }
126:
127: /**
128: * Returns a string representation of the name by imploding the namespace parts with the
129: * namespace separator.
130: *
131: * @return string String representation
132: */
133: public function __toString() : string {
134: return implode('\\', $this->parts);
135: }
136:
137: /**
138: * Gets a slice of a name (similar to array_slice).
139: *
140: * This method returns a new instance of the same type as the original and with the same
141: * attributes.
142: *
143: * If the slice is empty, null is returned. The null value will be correctly handled in
144: * concatenations using concat().
145: *
146: * Offset and length have the same meaning as in array_slice().
147: *
148: * @param int $offset Offset to start the slice at (may be negative)
149: * @param int|null $length Length of the slice (may be negative)
150: *
151: * @return static|null Sliced name
152: */
153: public function slice(int $offset, int $length = null) {
154: $numParts = count($this->parts);
155:
156: $realOffset = $offset < 0 ? $offset + $numParts : $offset;
157: if ($realOffset < 0 || $realOffset > $numParts) {
158: throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset));
159: }
160:
161: if (null === $length) {
162: $realLength = $numParts - $realOffset;
163: } else {
164: $realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
165: if ($realLength < 0 || $realLength > $numParts - $realOffset) {
166: throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
167: }
168: }
169:
170: if ($realLength === 0) {
171: // Empty slice is represented as null
172: return null;
173: }
174:
175: return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes);
176: }
177:
178: /**
179: * Concatenate two names, yielding a new Name instance.
180: *
181: * The type of the generated instance depends on which class this method is called on, for
182: * example Name\FullyQualified::concat() will yield a Name\FullyQualified instance.
183: *
184: * If one of the arguments is null, a new instance of the other name will be returned. If both
185: * arguments are null, null will be returned. As such, writing
186: * Name::concat($namespace, $shortName)
187: * where $namespace is a Name node or null will work as expected.
188: *
189: * @param string|string[]|self|null $name1 The first name
190: * @param string|string[]|self|null $name2 The second name
191: * @param array $attributes Attributes to assign to concatenated name
192: *
193: * @return static|null Concatenated name
194: */
195: public static function concat($name1, $name2, array $attributes = []) {
196: if (null === $name1 && null === $name2) {
197: return null;
198: } elseif (null === $name1) {
199: return new static(self::prepareName($name2), $attributes);
200: } elseif (null === $name2) {
201: return new static(self::prepareName($name1), $attributes);
202: } else {
203: return new static(
204: array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
205: );
206: }
207: }
208:
209: /**
210: * Prepares a (string, array or Name node) name for use in name changing methods by converting
211: * it to an array.
212: *
213: * @param string|string[]|self $name Name to prepare
214: *
215: * @return string[] Prepared name
216: */
217: private static function prepareName($name) : array {
218: if (\is_string($name)) {
219: if ('' === $name) {
220: throw new \InvalidArgumentException('Name cannot be empty');
221: }
222:
223: return explode('\\', $name);
224: } elseif (\is_array($name)) {
225: if (empty($name)) {
226: throw new \InvalidArgumentException('Name cannot be empty');
227: }
228:
229: return $name;
230: } elseif ($name instanceof self) {
231: return $name->parts;
232: }
233:
234: throw new \InvalidArgumentException(
235: 'Expected string, array of parts or Name instance'
236: );
237: }
238:
239: public function getType() : string {
240: return 'Name';
241: }
242: }
243: