Skip to content

Commit e31ad9e

Browse files
iggyvolzdg
authored andcommitted
added support for enums
1 parent ee25158 commit e31ad9e

20 files changed

+415
-16
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nette/php-generator",
3-
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.0 features.",
3+
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.",
44
"keywords": ["nette", "php", "code", "scaffolding"],
55
"homepage": "https://nette.org",
66
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],

ecs.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
$parameters->set('skip', [
1717
'fixtures*/*',
18+
'tests/PhpGenerator/Dumper.dump().enum.phpt', // enum
1819

1920
// constant NULL, FALSE
2021
PhpCsFixer\Fixer\Casing\LowercaseConstantsFixer::class => [

readme.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,43 @@ $trait = Nette\PhpGenerator\ClassType::trait('MyTrait');
227227
// in a similar way $class = Nette\PhpGenerator\ClassType::class('MyClass');
228228
```
229229

230+
Enums
231+
-----
232+
233+
You can easily create the enums that PHP 8.1 brings:
234+
235+
```php
236+
$enum = Nette\PhpGenerator\ClassType::enum('Suit');
237+
$enum->addCase('Clubs');
238+
$enum->addCase('Diamonds');
239+
$enum->addCase('Hearts');
240+
$enum->addCase('Spades');
241+
242+
echo $enum;
243+
```
244+
245+
Result:
246+
247+
```php
248+
enum Suit
249+
{
250+
case Clubs;
251+
case Diamonds;
252+
case Hearts;
253+
case Spades;
254+
}
255+
```
256+
257+
You can also define scalar equivalents for cases to create a backed enum:
258+
259+
```php
260+
$enum->addCase('Clubs', '♣');
261+
$enum->addCase('Diamonds', '♦');
262+
```
263+
264+
It is possible to add a comment or [attributes](#attributes) to each case using `addComment()` or `addAttribute()`.
265+
266+
230267
Literals
231268
--------
232269

src/PhpGenerator/ClassType.php

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515
/**
16-
* Class/Interface/Trait description.
16+
* Class/Interface/Trait/Enum description.
1717
*
1818
* @property Method[] $methods
1919
* @property Property[] $properties
@@ -27,7 +27,8 @@ final class ClassType
2727
public const
2828
TYPE_CLASS = 'class',
2929
TYPE_INTERFACE = 'interface',
30-
TYPE_TRAIT = 'trait';
30+
TYPE_TRAIT = 'trait',
31+
TYPE_ENUM = 'enum';
3132

3233
public const
3334
VISIBILITY_PUBLIC = 'public',
@@ -67,6 +68,9 @@ final class ClassType
6768
/** @var Method[] name => Method */
6869
private $methods = [];
6970

71+
/** @var EnumCase[] name => EnumCase */
72+
private $cases = [];
73+
7074

7175
public static function class(string $name = null, PhpNamespace $namespace = null): self
7276
{
@@ -86,6 +90,12 @@ public static function trait(string $name = null, PhpNamespace $namespace = null
8690
}
8791

8892

93+
public static function enum(string $name = null, PhpNamespace $namespace = null): self
94+
{
95+
return (new self($name, $namespace))->setType(self::TYPE_ENUM);
96+
}
97+
98+
8999
/**
90100
* @param string|object $class
91101
*/
@@ -191,11 +201,17 @@ public function isTrait(): bool
191201
}
192202

193203

204+
public function isEnum(): bool
205+
{
206+
return $this->type === self::TYPE_ENUM;
207+
}
208+
209+
194210
/** @return static */
195211
public function setType(string $type): self
196212
{
197-
if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) {
198-
throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.');
213+
if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) {
214+
throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.');
199215
}
200216
$this->type = $type;
201217
return $this;
@@ -351,7 +367,7 @@ public function removeTrait(string $name): self
351367

352368

353369
/**
354-
* @param Method|Property|Constant $member
370+
* @param Method|Property|Constant|EnumCase $member
355371
* @return static
356372
*/
357373
public function addMember($member): self
@@ -368,8 +384,11 @@ public function addMember($member): self
368384
} elseif ($member instanceof Constant) {
369385
$this->consts[$member->getName()] = $member;
370386

387+
} elseif ($member instanceof EnumCase) {
388+
$this->cases[$member->getName()] = $member;
389+
371390
} else {
372-
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.');
391+
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase.');
373392
}
374393

375394
return $this;
@@ -414,6 +433,44 @@ public function removeConstant(string $name): self
414433
}
415434

416435

436+
/**
437+
* Sets cases to enum
438+
* @param EnumCase[] $consts
439+
* @return static
440+
*/
441+
public function setCases(array $cases): self
442+
{
443+
(function (EnumCase ...$cases) {})(...$cases);
444+
$this->cases = [];
445+
foreach ($cases as $case) {
446+
$this->cases[$case->getName()] = $case;
447+
}
448+
return $this;
449+
}
450+
451+
452+
/** @return EnumCase[] */
453+
public function getCases(): array
454+
{
455+
return $this->cases;
456+
}
457+
458+
459+
/** Adds case to enum */
460+
public function addCase(string $name, $value = null): EnumCase
461+
{
462+
return $this->cases[$name] = (new EnumCase($name))->setValue($value);
463+
}
464+
465+
466+
/** @return static */
467+
public function removeCase(string $name): self
468+
{
469+
unset($this->cases[$name]);
470+
return $this;
471+
}
472+
473+
417474
/**
418475
* @param Property[] $props
419476
* @return static
@@ -540,6 +597,9 @@ public function validate(): void
540597
if ($this->abstract && $this->final) {
541598
throw new Nette\InvalidStateException('Class cannot be abstract and final.');
542599

600+
} elseif ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) {
601+
throw new Nette\InvalidStateException('Enum cannot be abstract or final or extends class or have properties.');
602+
543603
} elseif (!$this->name && ($this->abstract || $this->final)) {
544604
throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
545605
}
@@ -559,6 +619,7 @@ private function validateNames(array $names): void
559619
public function __clone()
560620
{
561621
$clone = function ($item) { return clone $item; };
622+
$this->cases = array_map($clone, $this->cases);
562623
$this->consts = array_map($clone, $this->consts);
563624
$this->properties = array_map($clone, $this->properties);
564625
$this->methods = array_map($clone, $this->methods);

src/PhpGenerator/Dumper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ private function dumpObject(&$var, array $parents, int $level): string
123123
if ($var instanceof \Serializable) {
124124
return 'unserialize(' . $this->dumpString(serialize($var)) . ')';
125125

126+
} elseif ($var instanceof \UnitEnum) {
127+
return '\\' . get_class($var) . '::' . $var->name;
128+
126129
} elseif ($var instanceof \Closure) {
127130
throw new Nette\InvalidArgumentException('Cannot dump closure.');
128131
}

src/PhpGenerator/EnumCase.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
14+
15+
/**
16+
* Enum case.
17+
*/
18+
final class EnumCase
19+
{
20+
use Nette\SmartObject;
21+
use Traits\NameAware;
22+
use Traits\CommentAware;
23+
use Traits\AttributeAware;
24+
25+
/** @var mixed */
26+
private $value;
27+
28+
29+
/** @return static */
30+
public function setValue($val): self
31+
{
32+
$this->value = $val;
33+
return $this;
34+
}
35+
36+
37+
public function getValue()
38+
{
39+
return $this->value;
40+
}
41+
}

src/PhpGenerator/Factory.php

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,17 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f
2727
$class = $from->isAnonymous()
2828
? new ClassType
2929
: new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
30-
$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
31-
$class->setFinal($from->isFinal() && $class->isClass());
32-
$class->setAbstract($from->isAbstract() && $class->isClass());
30+
31+
if (PHP_VERSION_ID >= 80100 && $from->isEnum()) {
32+
$class->setType($class::TYPE_ENUM);
33+
$from = new \ReflectionEnum($from->getName());
34+
$enumIface = $from->isBacked() ? \BackedEnum::class : \UnitEnum::class;
35+
} else {
36+
$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
37+
$class->setFinal($from->isFinal() && $class->isClass());
38+
$class->setAbstract($from->isAbstract() && $class->isClass());
39+
$enumIface = null;
40+
}
3341

3442
$ifaces = $from->getInterfaceNames();
3543
foreach ($ifaces as $iface) {
@@ -40,6 +48,7 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f
4048
if ($from->isInterface()) {
4149
$class->setExtends($ifaces);
4250
} else {
51+
$ifaces = array_diff($ifaces, [$enumIface]);
4352
$class->setImplements($ifaces);
4453
}
4554

@@ -49,20 +58,25 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f
4958
$class->setExtends($from->getParentClass()->name);
5059
$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
5160
}
52-
$props = $methods = $consts = [];
61+
62+
$props = [];
5363
foreach ($from->getProperties() as $prop) {
5464
if ($prop->isDefault()
5565
&& $prop->getDeclaringClass()->name === $from->name
5666
&& (PHP_VERSION_ID < 80000 || !$prop->isPromoted())
67+
&& !$class->isEnum()
5768
) {
5869
$props[] = $this->fromPropertyReflection($prop);
5970
}
6071
}
6172
$class->setProperties($props);
6273

63-
$bodies = [];
74+
$methods = $bodies = [];
6475
foreach ($from->getMethods() as $method) {
65-
if ($method->getDeclaringClass()->name === $from->name) {
76+
if (
77+
$method->getDeclaringClass()->name === $from->name
78+
&& (!$enumIface || !method_exists($enumIface, $method->name))
79+
) {
6680
$methods[] = $m = $this->fromMethodReflection($method);
6781
if ($withBodies) {
6882
$srcMethod = Nette\Utils\Reflection::getMethodDeclaringMethod($method);
@@ -76,12 +90,16 @@ public function fromClassReflection(\ReflectionClass $from, bool $withBodies = f
7690
}
7791
$class->setMethods($methods);
7892

93+
$consts = $cases = [];
7994
foreach ($from->getReflectionConstants() as $const) {
80-
if ($const->getDeclaringClass()->name === $from->name) {
95+
if ($class->isEnum() && $from->hasCase($const->name)) {
96+
$cases[] = $this->fromCaseReflection($const);
97+
} elseif ($const->getDeclaringClass()->name === $from->name) {
8198
$consts[] = $this->fromConstantReflection($const);
8299
}
83100
}
84101
$class->setConstants($consts);
102+
$class->setCases($cases);
85103

86104
return $class;
87105
}
@@ -185,6 +203,16 @@ public function fromConstantReflection(\ReflectionClassConstant $from): Constant
185203
}
186204

187205

206+
public function fromCaseReflection(\ReflectionClassConstant $from): EnumCase
207+
{
208+
$const = new EnumCase($from->name);
209+
$const->setValue($from->getValue()->value ?? null);
210+
$const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
211+
$const->setAttributes(self::getAttributes($from));
212+
return $const;
213+
}
214+
215+
188216
public function fromPropertyReflection(\ReflectionProperty $from): Property
189217
{
190218
$defaults = $from->getDeclaringClass()->getDefaultProperties();

src/PhpGenerator/PhpFile.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ public function addTrait(string $name): ClassType
5656
}
5757

5858

59+
public function addEnum(string $name): ClassType
60+
{
61+
return $this
62+
->addNamespace(Helpers::extractNamespace($name))
63+
->addEnum(Helpers::extractShortName($name));
64+
}
65+
66+
5967
/** @param string|PhpNamespace $namespace */
6068
public function addNamespace($namespace): PhpNamespace
6169
{

src/PhpGenerator/PhpNamespace.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ public function addTrait(string $name): ClassType
189189
}
190190

191191

192+
public function addEnum(string $name): ClassType
193+
{
194+
return $this->addClass($name)->setType(ClassType::TYPE_ENUM);
195+
}
196+
197+
192198
/** @return ClassType[] */
193199
public function getClasses(): array
194200
{

0 commit comments

Comments
 (0)