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