• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / php-generator / 22836119803

09 Mar 2026 02:40AM UTC coverage: 94.264% (+0.2%) from 94.029%
22836119803

push

github

dg
added CLAUDE.md

1890 of 2005 relevant lines covered (94.26%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.55
/src/PhpGenerator/PhpNamespace.php
1
<?php declare(strict_types=1);
1✔
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\PhpGenerator;
9

10
use Nette;
11
use Nette\InvalidStateException;
12
use function strlen;
13

14

15
/**
16
 * Represents a PHP namespace with use statements and contained class-like types and functions.
17
 */
18
final class PhpNamespace
19
{
20
        public const
21
                NameNormal = 'n',
22
                NameFunction = 'f',
23
                NameConstant = 'c';
24

25
        #[\Deprecated('use PhpNamespace::NameNormal')]
26
        public const NAME_NORMAL = self::NameNormal;
27

28
        #[\Deprecated('use PhpNamespace::NameFunction')]
29
        public const NAME_FUNCTION = self::NameFunction;
30

31
        #[\Deprecated('use PhpNamespace::NameConstant')]
32
        public const NAME_CONSTANT = self::NameConstant;
33

34
        private string $name;
35

36
        private bool $bracketedSyntax = false;
37

38
        /** @var array<string, array<string, string>> */
39
        private array $aliases = [
40
                self::NameNormal => [],
41
                self::NameFunction => [],
42
                self::NameConstant => [],
43
        ];
44

45
        /** @var array<string, ClassType|InterfaceType|TraitType|EnumType> */
46
        private array $classes = [];
47

48
        /** @var array<string, GlobalFunction> */
49
        private array $functions = [];
50

51

52
        public function __construct(string $name)
1✔
53
        {
54
                if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
1✔
55
                        throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
1✔
56
                }
57

58
                $this->name = $name;
1✔
59
        }
1✔
60

61

62
        public function getName(): string
63
        {
64
                return $this->name;
1✔
65
        }
66

67

68
        /**
69
         * @internal
70
         */
71
        public function setBracketedSyntax(bool $state = true): static
1✔
72
        {
73
                $this->bracketedSyntax = $state;
1✔
74
                return $this;
1✔
75
        }
76

77

78
        public function hasBracketedSyntax(): bool
79
        {
80
                return $this->bracketedSyntax;
1✔
81
        }
82

83

84
        /**
85
         * Adds a use statement for a class, function, or constant.
86
         * Auto-generates an alias if the short name conflicts with existing names.
87
         * @param  self::Name*  $of
88
         * @throws InvalidStateException  if the alias is already used for a different name
89
         */
90
        public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): static
1✔
91
        {
92
                if (
93
                        !Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true)
1✔
94
                        || (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)]))
1✔
95
                ) {
96
                        throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name.");
1✔
97

98
                } elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::Keywords[strtolower($alias)]))) {
1✔
99
                        throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias.");
1✔
100
                }
101

102
                $name = ltrim($name, '\\');
1✔
103
                $aliases = array_change_key_case($this->aliases[$of]);
1✔
104
                $used = [self::NameNormal => $this->classes, self::NameFunction => $this->functions, self::NameConstant => []][$of];
1✔
105

106
                if ($alias === null) {
1✔
107
                        $base = Helpers::extractShortName($name);
1✔
108
                        $counter = null;
1✔
109
                        do {
110
                                $alias = $base . $counter;
1✔
111
                                $lower = strtolower($alias);
1✔
112
                                $counter++;
1✔
113
                        } while ((isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) || isset($used[$lower]));
1✔
114
                } else {
115
                        $lower = strtolower($alias);
1✔
116
                        if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) {
1✔
117
                                throw new InvalidStateException(
×
118
                                        "Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'.",
×
119
                                );
120
                        } elseif (isset($used[$lower])) {
1✔
121
                                throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'.");
1✔
122
                        }
123
                }
124

125
                $this->aliases[$of][$alias] = $name;
1✔
126
                return $this;
1✔
127
        }
128

129

130
        /**
131
         * Removes a use statement.
132
         * @param  self::Name*  $of
133
         */
134
        public function removeUse(string $name, string $of = self::NameNormal): void
1✔
135
        {
136
                foreach ($this->aliases[$of] as $alias => $item) {
1✔
137
                        if (strcasecmp($item, $name) === 0) {
1✔
138
                                unset($this->aliases[$of][$alias]);
1✔
139
                        }
140
                }
141
        }
1✔
142

143

144
        /**
145
         * Adds a use statement for a function.
146
         */
147
        public function addUseFunction(string $name, ?string $alias = null): static
1✔
148
        {
149
                return $this->addUse($name, $alias, self::NameFunction);
1✔
150
        }
151

152

153
        /**
154
         * Adds a use statement for a constant.
155
         */
156
        public function addUseConstant(string $name, ?string $alias = null): static
1✔
157
        {
158
                return $this->addUse($name, $alias, self::NameConstant);
1✔
159
        }
160

161

162
        /**
163
         * Returns use statements, sorted alphabetically and excluding redundant aliases within the same namespace.
164
         * @param  self::Name*  $of
165
         * @return array<string, string>  alias => fully qualified name
166
         */
167
        public function getUses(string $of = self::NameNormal): array
1✔
168
        {
169
                uasort($this->aliases[$of], fn(string $a, string $b): int => strtr($a, '\\', ' ') <=> strtr($b, '\\', ' '));
1✔
170
                return array_filter(
1✔
171
                        $this->aliases[$of],
1✔
172
                        fn($name, $alias) => (bool) strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name),
1✔
173
                        ARRAY_FILTER_USE_BOTH,
1✔
174
                );
175
        }
176

177

178
        /**
179
         * Resolves a relative or aliased name to its fully qualified form.
180
         * @param  self::Name*  $of
181
         */
182
        public function resolveName(string $name, string $of = self::NameNormal): string
1✔
183
        {
184
                if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
1✔
185
                        return $name;
1✔
186
                } elseif ($name[0] === '\\') {
1✔
187
                        return substr($name, 1);
1✔
188
                }
189

190
                $aliases = array_change_key_case($this->aliases[$of]);
1✔
191
                if ($of !== self::NameNormal) {
1✔
192
                        return $aliases[strtolower($name)]
1✔
193
                                ?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
1✔
194
                }
195

196
                $parts = explode('\\', $name, 2);
1✔
197
                return ($res = $aliases[strtolower($parts[0])] ?? null)
1✔
198
                        ? $res . (isset($parts[1]) ? '\\' . $parts[1] : '')
1✔
199
                        : $this->name . ($this->name ? '\\' : '') . $name;
1✔
200
        }
201

202

203
        /**
204
         * Simplifies all class/function/constant names in a type string using current use statements.
205
         * @param  self::Name*  $of
206
         */
207
        public function simplifyType(string $type, string $of = self::NameNormal): string
1✔
208
        {
209
                return preg_replace_callback('~[\w\x7f-\xff\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type);
1✔
210
        }
211

212

213
        /**
214
         * Simplifies a fully qualified name to the shortest possible form using current use statements.
215
         * @param  self::Name*  $of
216
         */
217
        public function simplifyName(string $name, string $of = self::NameNormal): string
1✔
218
        {
219
                if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
1✔
220
                        return $name;
1✔
221
                }
222

223
                $name = ltrim($name, '\\');
1✔
224

225
                if ($of !== self::NameNormal) {
1✔
226
                        foreach ($this->aliases[$of] as $alias => $original) {
1✔
227
                                if (strcasecmp($original, $name) === 0) {
1✔
228
                                        return $alias;
1✔
229
                                }
230
                        }
231

232
                        return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
1✔
233
                }
234

235
                $shortest = null;
1✔
236
                $relative = self::startsWith($name, $this->name . '\\')
1✔
237
                        ? substr($name, strlen($this->name) + 1)
1✔
238
                        : null;
1✔
239

240
                foreach ($this->aliases[$of] as $alias => $original) {
1✔
241
                        if ($relative && self::startsWith($relative . '\\', $alias . '\\')) {
1✔
242
                                $relative = null;
1✔
243
                        }
244

245
                        if (self::startsWith($name . '\\', $original . '\\')) {
1✔
246
                                $short = $alias . substr($name, strlen($original));
1✔
247
                                if (!isset($shortest) || strlen($shortest) > strlen($short)) {
1✔
248
                                        $shortest = $short;
1✔
249
                                }
250
                        }
251
                }
252

253
                if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) {
1✔
254
                        return $shortest;
1✔
255
                }
256

257
                return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name;
1✔
258
        }
259

260

261
        /**
262
         * Adds a class-like type or function to the namespace.
263
         * @throws Nette\InvalidStateException if an item with the same name already exists
264
         */
265
        public function add(ClassType|InterfaceType|TraitType|EnumType|GlobalFunction $item): static
1✔
266
        {
267
                $name = $item->getName() ?? throw new Nette\InvalidArgumentException('Class does not have a name.');
1✔
268
                $lower = strtolower($name);
1✔
269
                [$list, $type] = $item instanceof GlobalFunction ? [$this->functions, self::NameFunction] : [$this->classes, self::NameNormal];
1✔
270
                if (isset($list[$lower]) && $list[$lower] !== $item) {
1✔
271
                        throw new Nette\InvalidStateException("Cannot add '$name', because it already exists.");
1✔
272
                } elseif ($orig = array_change_key_case($this->aliases[$type])[$lower] ?? null) {
1✔
273
                        throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
1✔
274
                }
275

276
                if ($item instanceof GlobalFunction) {
1✔
277
                        $this->functions[$lower] = $item;
1✔
278
                } else {
279
                        $this->classes[$lower] = $item;
1✔
280
                        $item->setNamespace($this);
1✔
281
                }
282

283
                return $this;
1✔
284
        }
285

286

287
        /**
288
         * Adds a class to the namespace.
289
         * @throws Nette\InvalidStateException if the class already exists
290
         */
291
        public function addClass(string $name): ClassType
1✔
292
        {
293
                $this->add($class = (new ClassType($name))->setNamespace($this));
1✔
294
                return $class;
1✔
295
        }
296

297

298
        /**
299
         * Adds an interface to the namespace.
300
         * @throws Nette\InvalidStateException if the interface already exists
301
         */
302
        public function addInterface(string $name): InterfaceType
1✔
303
        {
304
                $this->add($iface = (new InterfaceType($name))->setNamespace($this));
1✔
305
                return $iface;
1✔
306
        }
307

308

309
        /**
310
         * Adds a trait to the namespace.
311
         * @throws Nette\InvalidStateException if the trait already exists
312
         */
313
        public function addTrait(string $name): TraitType
1✔
314
        {
315
                $this->add($trait = (new TraitType($name))->setNamespace($this));
1✔
316
                return $trait;
1✔
317
        }
318

319

320
        /**
321
         * Adds an enum to the namespace.
322
         * @throws Nette\InvalidStateException if the enum already exists
323
         */
324
        public function addEnum(string $name): EnumType
1✔
325
        {
326
                $this->add($enum = (new EnumType($name))->setNamespace($this));
1✔
327
                return $enum;
1✔
328
        }
329

330

331
        /**
332
         * Returns a class-like type by name.
333
         * @throws Nette\InvalidArgumentException if the class is not found
334
         */
335
        public function getClass(string $name): ClassType|InterfaceType|TraitType|EnumType
1✔
336
        {
337
                return $this->classes[strtolower($name)] ?? throw new Nette\InvalidArgumentException("Class '$name' not found.");
1✔
338
        }
339

340

341
        /** @return array<string, ClassType|InterfaceType|TraitType|EnumType> */
342
        public function getClasses(): array
343
        {
344
                $res = [];
1✔
345
                foreach ($this->classes as $class) {
1✔
346
                        $name = $class->getName();
1✔
347
                        assert($name !== null);
348
                        $res[$name] = $class;
1✔
349
                }
350

351
                return $res;
1✔
352
        }
353

354

355
        /**
356
         * Removes a class-like type from the namespace.
357
         */
358
        public function removeClass(string $name): static
1✔
359
        {
360
                unset($this->classes[strtolower($name)]);
1✔
361
                return $this;
1✔
362
        }
363

364

365
        /**
366
         * Adds a function to the namespace.
367
         * @throws Nette\InvalidStateException if the function already exists
368
         */
369
        public function addFunction(string $name): GlobalFunction
1✔
370
        {
371
                $this->add($function = new GlobalFunction($name));
1✔
372
                return $function;
1✔
373
        }
374

375

376
        /**
377
         * Returns a function by name.
378
         * @throws Nette\InvalidArgumentException if the function is not found
379
         */
380
        public function getFunction(string $name): GlobalFunction
1✔
381
        {
382
                return $this->functions[strtolower($name)] ?? throw new Nette\InvalidArgumentException("Function '$name' not found.");
1✔
383
        }
384

385

386
        /** @return array<string, GlobalFunction> */
387
        public function getFunctions(): array
388
        {
389
                $res = [];
1✔
390
                foreach ($this->functions as $fn) {
1✔
391
                        $res[$fn->getName()] = $fn;
1✔
392
                }
393

394
                return $res;
1✔
395
        }
396

397

398
        /**
399
         * Removes a function from the namespace.
400
         */
401
        public function removeFunction(string $name): static
1✔
402
        {
403
                unset($this->functions[strtolower($name)]);
1✔
404
                return $this;
1✔
405
        }
406

407

408
        private static function startsWith(string $a, string $b): bool
1✔
409
        {
410
                return strncasecmp($a, $b, strlen($b)) === 0;
1✔
411
        }
412

413

414
        public function __toString(): string
415
        {
416
                return (new Printer)->printNamespace($this);
1✔
417
        }
418
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc