cerbero90 /
enum
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace Cerbero\Enum\Services; |
||
| 6 | |||
| 7 | use Cerbero\Enum\Data\MethodAnnotation; |
||
| 8 | use InvalidArgumentException; |
||
| 9 | |||
| 10 | use function Cerbero\Enum\className; |
||
| 11 | |||
| 12 | /** |
||
| 13 | * The enums annotator. |
||
| 14 | * |
||
| 15 | * @template TEnum of \UnitEnum |
||
| 16 | */ |
||
| 17 | class Annotator |
||
| 18 | { |
||
| 19 | /** |
||
| 20 | * The regular expression to extract the use statements. |
||
| 21 | * |
||
| 22 | * @var string |
||
| 23 | */ |
||
| 24 | public const RE_USE_STATEMENTS = '~^use(?:[\s\S]+(?=^use))?.+~im'; |
||
| 25 | |||
| 26 | /** |
||
| 27 | * The regular expression to extract the enum declaring line with potential attributes. |
||
| 28 | * |
||
| 29 | * @var string |
||
| 30 | */ |
||
| 31 | public const RE_ENUM = '~(^(?:#\[[\s\S]+)?^enum.+)~im'; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * The regular expression to extract the method annotations. |
||
| 35 | * |
||
| 36 | * @var string |
||
| 37 | */ |
||
| 38 | public const RE_METHOD_ANNOTATIONS = '~^ \* @method(?:[\s\S]+(?=@method))?.+~im'; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * The enum inspector. |
||
| 42 | * |
||
| 43 | * @var Inspector<TEnum> |
||
| 44 | */ |
||
| 45 | protected Inspector $inspector; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * Instantiate the class. |
||
| 49 | * |
||
| 50 | * @param class-string<TEnum> $enum |
||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
| 51 | * @throws InvalidArgumentException |
||
| 52 | */ |
||
| 53 | 6 | public function __construct(protected string $enum) |
|
| 54 | { |
||
| 55 | 6 | $this->inspector = new Inspector($enum); |
|
| 56 | } |
||
| 57 | |||
| 58 | /** |
||
| 59 | * Annotate the given enum. |
||
| 60 | */ |
||
| 61 | 5 | public function annotate(bool $overwrite = false): bool |
|
| 62 | { |
||
| 63 | 5 | if (empty($annotations = $this->inspector->methodAnnotations(! $overwrite))) { |
|
| 64 | 1 | return true; |
|
| 65 | } |
||
| 66 | |||
| 67 | 4 | $docBlock = $this->inspector->docBlock(); |
|
| 68 | 4 | $filename = $this->inspector->filename(); |
|
| 69 | 4 | $oldContent = (string) file_get_contents($filename); |
|
| 70 | 4 | $methodAnnotations = $this->formatMethodAnnotations($annotations); |
|
| 71 | 4 | $useStatements = $this->formatUseStatements($this->inspector->useStatements(! $overwrite)); |
|
| 72 | 4 | $newContent = (string) preg_replace(static::RE_USE_STATEMENTS, $useStatements, $oldContent, 1); |
|
| 73 | |||
| 74 | 4 | $newContent = match (true) { |
|
| 75 | 4 | empty($docBlock) => $this->addDocBlock($methodAnnotations, $newContent), |
|
| 76 | 3 | str_contains($docBlock, '@method') => $this->replaceAnnotations($methodAnnotations, $newContent), |
|
| 77 | 1 | default => $this->addAnnotations($methodAnnotations, $newContent, $docBlock), |
|
| 78 | 4 | }; |
|
| 79 | |||
| 80 | 4 | return file_put_contents($filename, $newContent) !== false; |
|
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * Retrieve the formatted method annotations. |
||
| 85 | * |
||
| 86 | * @param array<string, MethodAnnotation> $annotations |
||
| 87 | */ |
||
| 88 | 4 | protected function formatMethodAnnotations(array $annotations): string |
|
| 89 | { |
||
| 90 | 4 | $mapped = array_map(fn(MethodAnnotation $annotation) => " * {$annotation}", $annotations); |
|
| 91 | |||
| 92 | 4 | return implode(PHP_EOL, $mapped); |
|
| 93 | } |
||
| 94 | |||
| 95 | /** |
||
| 96 | * Retrieve the formatted use statements. |
||
| 97 | * |
||
| 98 | * @param array<string, class-string> $statements |
||
|
0 ignored issues
–
show
|
|||
| 99 | */ |
||
| 100 | 4 | protected function formatUseStatements(array $statements): string |
|
| 101 | { |
||
| 102 | 4 | array_walk($statements, function (string &$namespace, string $alias) { |
|
| 103 | 4 | $namespace = "use {$namespace}" . (className($namespace) == $alias ? ';' : " as {$alias};"); |
|
| 104 | 4 | }); |
|
| 105 | |||
| 106 | 4 | return implode(PHP_EOL, $statements); |
|
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Add a docBlock with the given method annotations. |
||
| 111 | */ |
||
| 112 | 1 | protected function addDocBlock(string $methodAnnotations, string $content): string |
|
| 113 | { |
||
| 114 | 1 | $replacement = implode(PHP_EOL, ['/**', $methodAnnotations, ' */', '$1']); |
|
| 115 | |||
| 116 | 1 | return (string) preg_replace(static::RE_ENUM, $replacement, $content, 1); |
|
| 117 | } |
||
| 118 | |||
| 119 | /** |
||
| 120 | * Replace existing method annotations with the given method annotations. |
||
| 121 | */ |
||
| 122 | 2 | protected function replaceAnnotations(string $methodAnnotations, string $content): string |
|
| 123 | { |
||
| 124 | 2 | return (string) preg_replace(static::RE_METHOD_ANNOTATIONS, $methodAnnotations, $content, 1); |
|
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * Add the given method annotations to the provided docBlock. |
||
| 129 | */ |
||
| 130 | 1 | protected function addAnnotations(string $methodAnnotations, string $content, string $docBlock): string |
|
| 131 | { |
||
| 132 | 1 | $newDocBlock = str_replace(' */', implode(PHP_EOL, [' *', $methodAnnotations, ' */']), $docBlock); |
|
| 133 | |||
| 134 | 1 | return str_replace($docBlock, $newDocBlock, $content); |
|
| 135 | } |
||
| 136 | } |
||
| 137 |