Skip to content

Commit

Permalink
Add support for Pure PHP Enum
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Jan 21, 2024
1 parent db8b20e commit 1d9a81f
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 39 deletions.
109 changes: 76 additions & 33 deletions src/Enum/JavascriptConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use BackedEnum;
use Closure;
use ReflectionEnum;
use UnitEnum;
use ValueError;

final class JavascriptConverter
Expand All @@ -21,6 +21,7 @@ private function __construct(
private readonly ?Closure $propertyNameCasing,
private readonly int $indentSize,
private readonly string $export,
private readonly int $unitEnumStartAt
) {
}

Expand All @@ -31,7 +32,8 @@ public static function new(): self
useImmutability: true,
propertyNameCasing: null,
indentSize: 2,
export: self::EXPORT_NONE
export: self::EXPORT_NONE,
unitEnumStartAt: 0,
);
}

Expand All @@ -43,6 +45,7 @@ public function propertyNameCase(Closure $casing = null): self
$casing,
$this->indentSize,
$this->export,
$this->unitEnumStartAt,
);
}

Expand All @@ -56,6 +59,7 @@ public function useImmutability(): self
$this->propertyNameCasing,
$this->indentSize,
$this->export,
$this->unitEnumStartAt,
),
};
}
Expand All @@ -70,6 +74,7 @@ public function ignoreImmutability(): self
$this->propertyNameCasing,
$this->indentSize,
$this->export,
$this->unitEnumStartAt,
),
};
}
Expand All @@ -84,6 +89,7 @@ public function useSymbol(): self
$this->propertyNameCasing,
$this->indentSize,
$this->export,
$this->unitEnumStartAt,
),
};
}
Expand All @@ -98,6 +104,7 @@ public function ignoreSymbol(): self
$this->propertyNameCasing,
$this->indentSize,
$this->export,
$this->unitEnumStartAt,
),
};
}
Expand All @@ -112,6 +119,7 @@ public function useExportDefault(): self
$this->propertyNameCasing,
$this->indentSize,
self::EXPORT_DEFAULT,
$this->unitEnumStartAt,
),
};
}
Expand All @@ -126,6 +134,7 @@ public function useExport(): self
$this->propertyNameCasing,
$this->indentSize,
self::EXPORT,
$this->unitEnumStartAt,
),
};
}
Expand All @@ -140,6 +149,22 @@ public function ignoreExport(): self
$this->propertyNameCasing,
$this->indentSize,
self::EXPORT_NONE,
$this->unitEnumStartAt,
),
};
}

public function startAt(int $startAt): self
{
return match (true) {
$startAt === $this->unitEnumStartAt => $this,
default => new self(
$this->useSymbol,
$this->useImmutability,
$this->propertyNameCasing,
$this->indentSize,
$this->export,
$startAt,
),
};
}
Expand All @@ -155,6 +180,7 @@ public function indentSize(int $indentSize): self
$this->propertyNameCasing,
$indentSize,
$this->export,
$this->unitEnumStartAt,
),
};
}
Expand All @@ -172,22 +198,15 @@ public function indentSize(int $indentSize): self
*/
public function convertToObject(string $enumClass, ?string $objectName = null): string
{
$this->filterBackedEnum($enumClass);

$space = '';
$eol = '';
if (0 < $this->indentSize) {
$space = str_repeat(' ', $this->indentSize);
$eol = "\n";
}

$output = array_reduce(
$enumClass::cases(),
fn (string $output, BackedEnum $enum): string => $output.$space.$this->formatPropertyName($enum).': '.$this->formatPropertyValue($enum).','.$eol,
''
);

$output = '{'.$eol.$output.'}';
$body = $this->getObjectBody($enumClass, $space, $eol);
$output = '{'.$eol.$body.'}';
if ($this->useImmutability) {
$output = "Object.freeze($output)";
}
Expand All @@ -203,6 +222,21 @@ public function convertToObject(string $enumClass, ?string $objectName = null):
return $this->export.$output.$eol;
}

/**
* @param class-string<UnitEnum> $enumClass
*/
private function getObjectBody(string $enumClass, string $space, string $eol): string
{
$this->filterBackedEnum($enumClass);

$output = [];
foreach ($enumClass::cases() as $offset => $enum) {
$output[] = $space.$this->formatPropertyName($enum).': '.$this->formatPropertyValue($enum, $offset).',';
}

return implode($eol, $output).$eol;
}

/**
* Converts the Enum into a Javascript class.
*
Expand All @@ -211,33 +245,43 @@ public function convertToObject(string $enumClass, ?string $objectName = null):
* <li>If the class name is a non-empty string, it will be used as is as the class name</li>
* </ul>
*
* @param class-string<BackedEnum> $enumClass
* @param class-string<UnitEnum> $enumClass
*/
public function convertToClass(string $enumClass, string $className = ''): string
{
$this->filterBackedEnum($enumClass);

$space = '';
$eol = '';
if (0 < $this->indentSize) {
$space = str_repeat(' ', $this->indentSize);
$eol = "\n";
}

/** @var string $className */
$className = $this->sanitizeName($className, $enumClass);
$output = array_reduce(
$enumClass::cases(),
fn (string $output, BackedEnum $enum): string => $output.$space."static {$this->formatPropertyName($enum)} = new $className({$this->formatPropertyValue($enum)})$eol",
''
);

$output = 'class '.$className.' {'.$eol.$output.$eol.$space.'constructor(name) {'.$eol.$space.$space.'this.name = name'.$eol.$space.'}'.$eol.'}';
$body = $this->getClassBody($enumClass, $className, $space, $eol);
$output = 'class '.$className.' {'.$eol.$body.$eol.$space.'constructor(name) {'.$eol.$space.$space.'this.name = name'.$eol.$space.'}'.$eol.'}';

return $this->export.$output.$eol;
}

/**
* @param class-string<BackedEnum> $enumClass
* @param class-string<UnitEnum> $enumClass
*/
private function getClassBody(string $enumClass, string $className, string $space, string $eol): string
{
$this->filterBackedEnum($enumClass);

$output = [];
foreach ($enumClass::cases() as $offset => $enum) {
$output[] = $space."static {$this->formatPropertyName($enum)} = new $className({$this->formatPropertyValue($enum, $offset)})";
}

return implode($eol, $output).$eol;

}

/**
* @param class-string<UnitEnum> $enumClass
*
* @throws ValueError If the given string does not represent a Backed Enum class
*/
Expand All @@ -246,11 +290,6 @@ private function filterBackedEnum(string $enumClass): void
if (!enum_exists($enumClass)) {
throw new ValueError($enumClass.' is not a valid PHP Enum.');
}

$reflection = new ReflectionEnum($enumClass);
if (!$reflection->isBacked()) {
throw new ValueError($enumClass.' is not a PHP backed enum.');
}
}

private function sanitizeName(?string $className, string $enumClass): ?string
Expand All @@ -264,22 +303,26 @@ private function sanitizeName(?string $className, string $enumClass): ?string
return (string) array_pop($parts);
}

private function formatPropertyName(BackedEnum $enum): string
private function formatPropertyName(UnitEnum $enum): string
{
return match ($this->propertyNameCasing) {
null => $enum->name,
default => ($this->propertyNameCasing)($enum->name),
};
}

private function formatPropertyValue(BackedEnum $enum): string|int
private function formatPropertyValue(UnitEnum $enum, int $offset = 0): string|int
{
$value = $enum->value;
$isBackedEnum = $enum instanceof BackedEnum;
$value = $isBackedEnum ? $enum->value : $offset + $this->unitEnumStartAt;
$value = is_string($value) ? '"'.$value.'"' : $value;

return match ($this->useSymbol) {
true => 'Symbol('.$value.')',
default => $value,
return match ($isBackedEnum) {
true => match ($this->useSymbol) {
true => 'Symbol('.$value.')',
default => $value,
},
false => 'Symbol('.$value.')',
};
}
}
4 changes: 1 addition & 3 deletions src/Enum/JavascriptConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ public function it_will_fails_with_a_negative_indent_size(): void
#[Test]
public function it_will_fails_converting_a_non_backed_enum(): void
{
$this->expectException(ValueError::class);

JavascriptConverter::new()->convertToObject(HttpMethod::class); /* @phpstan-ignore-line */
$actual = JavascriptConverter::new()->convertToObject(HttpMethod::class); /* @phpstan-ignore-line */
}

#[Test]
Expand Down
10 changes: 7 additions & 3 deletions src/Enum/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ enum HttpMethod: string

### Converting the Enum into a Javascript structure

The `JavascriptConverter` enables converting your PHP Backed Enum into an equivalent structure in Javascript.
The `JavascriptConverter` enables converting your PHP Enum into an equivalent structure in Javascript.
Because there are two (2) ways to create an Enum like structure in Javascript, the class provides
two (2) methods, `convertToObject` and `convertToClass` to allow the conversion. In both cases,
the conversion is configurable via wither methods to control the formatting and the
two (2) methods to allow the conversion.
In both cases, the conversion is configurable via wither methods to control the formatting and the
Javascript structure properties. For instance, given I have the following enum:

```php
Expand Down Expand Up @@ -290,6 +290,10 @@ const StatusCode = Object.freeze({
export default StatusCode;
```

The converter also supports converting pure PHP Enum, the value will be of type `Symbol` and
will be incremeted starting from `0`. you can configure the start value using the `startAt`
method. This means that for Pure Enum the `ignoreSymbol` method as no effect on the output.

The converter will not store the resulting string into a Javascriot file as this part is
left to the discretion of the implementor. There are several ways to do so:

Expand Down

0 comments on commit 1d9a81f

Please sign in to comment.