Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
59 / 59
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
BasicGenerationHelper
100.00% covered (success)
100.00%
59 / 59
100.00% covered (success)
100.00%
9 / 9
21
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 return
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addVisibility
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 createDocComment
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 createArguments
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 createArray
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 convertToAstNode
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 isValidDefault
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 generateFile
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace quintenmbusiness\PhpAstCodeGenerationHelper\GeneratorHelpers;
6
7use PhpParser\Builder\Namespace_;
8use PhpParser\Node\Arg;
9use PhpParser\Node\Expr;
10use PhpParser\Builder\Method;
11use PhpParser\BuilderFactory;
12use PhpParser\Builder\Property;
13use PhpParser\Node\Expr\Array_;
14use PhpParser\Node\Expr\ArrayItem;
15use PhpParser\Node\Expr\Cast\Bool_;
16use PhpParser\Node\Expr\ConstFetch;
17use PhpParser\Node\Expr\Variable;
18use PhpParser\Node\Name;
19use PhpParser\Node\Scalar\String_;
20use PhpParser\Node\Scalar\LNumber;
21use PhpParser\Node\Stmt\Return_;
22use PhpParser\Builder\Class_;
23use PhpParser\Builder\Interface_;
24use PhpParser\PrettyPrinter\Standard;
25
26class BasicGenerationHelper
27{
28    /**
29     * @var BuilderFactory
30     */
31    public BuilderFactory $factory;
32
33    /**
34     * @param BuilderFactory $factory
35     */
36    public function __construct(BuilderFactory $factory)
37    {
38        $this->factory = $factory;
39    }
40
41    /**
42     * Creates a return statement using a passed Expr
43     *
44     * @param  Expr    $expr
45     * @return Return_
46     */
47    public function return(Expr $expr): Return_
48    {
49        return new Return_($expr);
50    }
51
52    /**
53     * @param  Method|Property $stmt
54     * @param  string          $visibility
55     * @return Method|Property
56     */
57    public function addVisibility(Method|Property $stmt, string $visibility = 'public'): Method|Property
58    {
59        match ($visibility) {
60            'public' => $stmt->makePublic(),
61            'protected' => $stmt->makeProtected(),
62            'private' => $stmt->makePrivate(),
63            'static' => $stmt->makeStatic(),
64            'abstract' => $stmt->makeAbstract(),
65            'final' => $stmt->makeFinal(),
66            default => $stmt,
67        };
68
69        return $stmt;
70    }
71
72    /**
73     * Adds a PHPDoc comment to the given Method or Property.
74     *
75     * @param  Method|Property                   $method
76     * @param  array<int, array<string, string>> $params       Array of parameters, where each element has 'type' and 'name'.
77     * @param  null|string                       $returnType   The return type to document.
78     * @param  null|string                       $throws       The exception type to document in @throws.
79     * @param  null|string                       $deprecated   Marks the method/property as deprecated with an optional message.
80     * @param  array<string, string>             $additional   Additional tags, where the key is the tag (e.g., 'author') and the value is the tag's content.
81     * @return Method|Property
82     */
83    public function createDocComment(
84        Method|Property $method,
85        array $params = [],
86        ?string $returnType = null,
87        ?string $throws = null,
88        ?string $deprecated = null,
89        array $additional = []
90    ): Method|Property {
91        $comment = "\n" . '/**';
92
93        // Add @param tags
94        foreach ($params as $param) {
95            $comment .= "\n" . ' * @param ' . $param['type'] . ' $' . $param['name'];
96        }
97
98        // Add @return tag
99        if (!empty($returnType)) {
100            $comment .= "\n * \n" . ' * @return ' . $returnType;
101        }
102
103        // Add @throws tag
104        if (!empty($throws)) {
105            $comment .= "\n" . ' * @throws ' . $throws;
106        }
107
108        // Add @deprecated tag
109        if (!empty($deprecated)) {
110            $comment .= "\n" . ' * @deprecated ' . $deprecated;
111        }
112
113        // Add any additional tags
114        foreach ($additional as $tag => $content) {
115            $comment .= "\n" . ' * @' . $tag . ' ' . $content;
116        }
117
118        $comment .= "\n" . ' */';
119
120        $method->setDocComment($comment);
121
122        return $method;
123    }
124
125    /**
126     * Creates an array of arguments for a method call.
127     *
128     * @param array<int|string, mixed> $args
129     * @return array<int, Arg>
130     */
131    public function createArguments(array $args): array
132    {
133        $arguments = [];
134        foreach ($args as $value) {
135            $arguments[] = new Arg($value instanceof Expr ? $value : $this->convertToAstNode($value));
136        }
137        return $arguments;
138    }
139
140    /**
141     * Creates an array node from a PHP array.
142     *
143     * @param array<int|string, mixed> $items
144     * @return Expr\Array_
145     */
146    public function createArray(array $items): Array_
147    {
148        $arrayItems = [];
149        foreach ($items as $key => $value) {
150            $keyNode = is_int($key) ? null : $this->convertToAstNode($key);
151            $arrayItems[] = new ArrayItem($this->convertToAstNode($value), $keyNode);
152        }
153
154        return new Array_($arrayItems);
155    }
156
157    /**
158     * Converts a PHP value to an AST node.
159     *
160     * @param mixed $value
161     * @return Array_|LNumber|String_|ConstFetch|Variable
162     * @throws \InvalidArgumentException
163     */
164    public function convertToAstNode(mixed $value): Array_|LNumber|String_|ConstFetch|Variable
165    {
166        return match (true) {
167            is_int($value) => new LNumber($value),
168            is_string($value) => new String_($value),
169            is_array($value) => $this->createArray($value),
170            is_bool($value) => new ConstFetch(new Name($value ? 'true' : 'false')),
171            $value instanceof Variable => $value,
172            $value === null => new ConstFetch(new Name('null')), // Handle null as ConstFetch
173            default => throw new \InvalidArgumentException('Unsupported value type for AST conversion: ' . gettype($value)),
174        };
175    }
176
177    /**
178     * @param string|null $type
179     * @param mixed $default
180     * @return bool
181     */
182    public function isValidDefault(?string $type, mixed $default): bool
183    {
184        return match ($type) {
185            'int' => is_int($default),
186            'string' => is_string($default),
187            'bool' => is_bool($default),
188            'float' => is_float($default),
189            'array' => is_array($default),
190            default => false,
191        };
192    }
193
194    /**
195     * Generates a PHP file for a given class, interface, or namespace.
196     *
197     * @param Class_|Interface_|Namespace_ $definition The class, interface, or namespace to generate.
198     * @param string|null $namespace The namespace of the class/interface (if $definition is not already a Namespace_).
199     * @param string $outputPath The file path where the generated file should be saved.
200     * @return void
201     */
202    public function generateFile(Class_|Interface_|Namespace_ $definition, ?string $namespace, string $outputPath): void
203    {
204        $prettyPrinter = new Standard();
205
206        // Convert builder to node if necessary
207        $node = $definition instanceof Namespace_
208            ? $definition->getNode()
209            : $this->factory->namespace($namespace)->addStmt($definition)->getNode();
210
211        // Generate the PHP code
212        $code = $prettyPrinter->prettyPrintFile([$node]);
213
214        // Ensure the directory exists
215        $directory = dirname($outputPath);
216        if (!is_dir($directory)) {
217            mkdir($directory, 0755, true);
218        }
219
220        // Save the generated code to the file
221        file_put_contents($outputPath, $code);
222    }
223}