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

nette / php-generator / 8833786024

25 Apr 2024 01:47PM UTC coverage: 93.784% (+0.02%) from 93.769%
8833786024

push

github

dg
added PhpFile::removeNamespace()

4 of 4 new or added lines in 1 file covered. (100.0%)

24 existing lines in 6 files now uncovered.

1539 of 1641 relevant lines covered (93.78%)

0.94 hits per line

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

98.51
/src/PhpGenerator/PhpNamespace.php
1
<?php
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
declare(strict_types=1);
9

10
namespace Nette\PhpGenerator;
11

12
use Nette;
13
use Nette\InvalidStateException;
14

15

16
/**
17
 * Namespaced part of a PHP file.
18
 *
19
 * Generates:
20
 * - namespace statement
21
 * - variable amount of use statements
22
 * - one or more class declarations
23
 */
24
final class PhpNamespace
25
{
26
        public const
27
                NameNormal = 'n',
28
                NameFunction = 'f',
29
                NameConstant = 'c';
30

31
        /** @deprecated use PhpNamespace::NameNormal */
32
        public const NAME_NORMAL = self::NameNormal;
33

34
        /** @deprecated use PhpNamespace::NameFunction */
35
        public const NAME_FUNCTION = self::NameFunction;
36

37
        /** @deprecated use PhpNamespace::NameConstant */
38
        public const NAME_CONSTANT = self::NameConstant;
39

40
        private string $name;
41

42
        private bool $bracketedSyntax = false;
43

44
        /** @var string[][] */
45
        private array $aliases = [
46
                self::NameNormal => [],
47
                self::NameFunction => [],
48
                self::NameConstant => [],
49
        ];
50

51
        /** @var (ClassType|InterfaceType|TraitType|EnumType)[] */
52
        private array $classes = [];
53

54
        /** @var GlobalFunction[] */
55
        private array $functions = [];
56

57

58
        public function __construct(string $name)
1✔
59
        {
60
                if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
1✔
61
                        throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
1✔
62
                }
63

64
                $this->name = $name;
1✔
65
        }
1✔
66

67

68
        public function getName(): string
69
        {
70
                return $this->name;
1✔
71
        }
72

73

74
        /**
75
         * @internal
76
         */
77
        public function setBracketedSyntax(bool $state = true): static
1✔
78
        {
79
                $this->bracketedSyntax = $state;
1✔
80
                return $this;
1✔
81
        }
82

83

84
        public function hasBracketedSyntax(): bool
85
        {
86
                return $this->bracketedSyntax;
1✔
87
        }
88

89

90
        /**
91
         * Adds a use statement to the namespace for classes, functions or constants.
92
         * @throws InvalidStateException
93
         */
94
        public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): static
1✔
95
        {
96
                if (
97
                        !Helpers::isNamespaceIdentifier($name, allowLeadingSlash: true)
1✔
98
                        || (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)]))
1✔
99
                ) {
100
                        throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name.");
1✔
101

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

106
                $name = ltrim($name, '\\');
1✔
107
                $aliases = array_change_key_case($this->aliases[$of]);
1✔
108
                $used = [self::NameNormal => $this->classes, self::NameFunction => $this->functions, self::NameConstant => []][$of];
1✔
109

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

129
                $this->aliases[$of][$alias] = $name;
1✔
130
                return $this;
1✔
131
        }
132

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 to the namespace for functions.
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 to the namespace for constants.
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
        /** @return string[] */
163
        public function getUses(string $of = self::NameNormal): array
1✔
164
        {
165
                uasort($this->aliases[$of], fn(string $a, string $b): int => strtr($a, '\\', ' ') <=> strtr($b, '\\', ' '));
1✔
166
                return array_filter(
1✔
167
                        $this->aliases[$of],
1✔
168
                        fn($name, $alias) => strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name),
1✔
169
                        ARRAY_FILTER_USE_BOTH,
1✔
170
                );
171
        }
172

173

174
        /**
175
         * Resolves name to full name.
176
         */
177
        public function resolveName(string $name, string $of = self::NameNormal): string
1✔
178
        {
179
                if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
1✔
180
                        return $name;
1✔
181
                } elseif ($name[0] === '\\') {
1✔
182
                        return substr($name, 1);
1✔
183
                }
184

185
                $aliases = array_change_key_case($this->aliases[$of]);
1✔
186
                if ($of !== self::NameNormal) {
1✔
187
                        return $aliases[strtolower($name)]
1✔
188
                                ?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
1✔
189
                }
190

191
                $parts = explode('\\', $name, 2);
1✔
192
                return ($res = $aliases[strtolower($parts[0])] ?? null)
1✔
193
                        ? $res . (isset($parts[1]) ? '\\' . $parts[1] : '')
1✔
194
                        : $this->name . ($this->name ? '\\' : '') . $name;
1✔
195
        }
196

197

198
        /**
199
         * Simplifies type hint using aliases.
200
         */
201
        public function simplifyType(string $type, string $of = self::NameNormal): string
1✔
202
        {
203
                return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', fn($m) => $this->simplifyName($m[0], $of), $type);
1✔
204
        }
205

206

207
        /**
208
         * Simplifies class, function or constant full name to alias.
209
         */
210
        public function simplifyName(string $name, string $of = self::NameNormal): string
1✔
211
        {
212
                if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
1✔
213
                        return $name;
1✔
214
                }
215

216
                $name = ltrim($name, '\\');
1✔
217

218
                if ($of !== self::NameNormal) {
1✔
219
                        foreach ($this->aliases[$of] as $alias => $original) {
1✔
220
                                if (strcasecmp($original, $name) === 0) {
1✔
221
                                        return $alias;
1✔
222
                                }
223
                        }
224

225
                        return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
1✔
226
                }
227

228
                $shortest = null;
1✔
229
                $relative = self::startsWith($name, $this->name . '\\')
1✔
230
                        ? substr($name, strlen($this->name) + 1)
1✔
231
                        : null;
1✔
232

233
                foreach ($this->aliases[$of] as $alias => $original) {
1✔
234
                        if ($relative && self::startsWith($relative . '\\', $alias . '\\')) {
1✔
235
                                $relative = null;
1✔
236
                        }
237

238
                        if (self::startsWith($name . '\\', $original . '\\')) {
1✔
239
                                $short = $alias . substr($name, strlen($original));
1✔
240
                                if (!isset($shortest) || strlen($shortest) > strlen($short)) {
1✔
241
                                        $shortest = $short;
1✔
242
                                }
243
                        }
244
                }
245

246
                if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) {
1✔
247
                        return $shortest;
1✔
248
                }
249

250
                return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name;
1✔
251
        }
252

253

254
        /**
255
         * Adds a class to the namespace. If it already exists, throws an exception.
256
         */
257
        public function add(ClassType|InterfaceType|TraitType|EnumType $class): static
1✔
258
        {
259
                $name = $class->getName();
1✔
260
                if ($name === null) {
1✔
261
                        throw new Nette\InvalidArgumentException('Class does not have a name.');
1✔
262
                }
263

264
                $lower = strtolower($name);
1✔
265
                if (isset($this->classes[$lower]) && $this->classes[$lower] !== $class) {
1✔
266
                        throw new Nette\InvalidStateException("Cannot add '$name', because it already exists.");
1✔
267
                } elseif ($orig = array_change_key_case($this->aliases[self::NameNormal])[$lower] ?? null) {
1✔
268
                        throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
1✔
269
                }
270

271
                $this->classes[$lower] = $class;
1✔
272
                return $this;
1✔
273
        }
274

275

276
        /**
277
         * Adds a class to the namespace. If it already exists, throws an exception.
278
         */
279
        public function addClass(string $name): ClassType
1✔
280
        {
281
                $this->add($class = new ClassType($name, $this));
1✔
282
                return $class;
1✔
283
        }
284

285

286
        /**
287
         * Adds an inteface to the namespace. If it already exists, throws an exception.
288
         */
289
        public function addInterface(string $name): InterfaceType
1✔
290
        {
291
                $this->add($iface = new InterfaceType($name, $this));
1✔
292
                return $iface;
1✔
293
        }
294

295

296
        /**
297
         * Adds a trait to the namespace. If it already exists, throws an exception.
298
         */
299
        public function addTrait(string $name): TraitType
1✔
300
        {
301
                $this->add($trait = new TraitType($name, $this));
1✔
302
                return $trait;
1✔
303
        }
304

305

306
        /**
307
         * Adds an enum to the namespace. If it already exists, throws an exception.
308
         */
309
        public function addEnum(string $name): EnumType
1✔
310
        {
311
                $this->add($enum = new EnumType($name, $this));
1✔
312
                return $enum;
1✔
313
        }
314

315

316
        public function removeClass(string $name): static
1✔
317
        {
318
                unset($this->classes[strtolower($name)]);
1✔
319
                return $this;
1✔
320
        }
321

322

323
        /**
324
         * Adds a function to the namespace. If it already exists, throws an exception.
325
         */
326
        public function addFunction(string $name): GlobalFunction
1✔
327
        {
328
                $lower = strtolower($name);
1✔
329
                if (isset($this->functions[$lower])) {
1✔
330
                        throw new Nette\InvalidStateException("Cannot add '$name', because it already exists.");
1✔
331
                } elseif ($orig = array_change_key_case($this->aliases[self::NameFunction])[$lower] ?? null) {
1✔
332
                        throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
1✔
333
                }
334

335
                return $this->functions[$lower] = new GlobalFunction($name);
1✔
336
        }
337

338

339
        public function removeFunction(string $name): static
1✔
340
        {
341
                unset($this->functions[strtolower($name)]);
1✔
342
                return $this;
1✔
343
        }
344

345

346
        /** @return (ClassType|InterfaceType|TraitType|EnumType)[] */
347
        public function getClasses(): array
348
        {
349
                $res = [];
1✔
350
                foreach ($this->classes as $class) {
1✔
351
                        $res[$class->getName()] = $class;
1✔
352
                }
353

354
                return $res;
1✔
355
        }
356

357

358
        /** @return GlobalFunction[] */
359
        public function getFunctions(): array
360
        {
361
                $res = [];
1✔
362
                foreach ($this->functions as $fn) {
1✔
363
                        $res[$fn->getName()] = $fn;
1✔
364
                }
365

366
                return $res;
1✔
367
        }
368

369

370
        private static function startsWith(string $a, string $b): bool
1✔
371
        {
372
                return strncasecmp($a, $b, strlen($b)) === 0;
1✔
373
        }
374

375

376
        public function __toString(): string
377
        {
378
                return (new Printer)->printNamespace($this);
1✔
379
        }
380
}
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

© 2025 Coveralls, Inc