|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Nette PHP Generator is a library for programmatically generating PHP code - classes, functions, namespaces, and complete PHP files. It supports all modern PHP features including property hooks (PHP 8.4), enums, attributes, asymmetric visibility, and more. |
| 8 | + |
| 9 | +The library provides both a builder API for constructing PHP structures and extraction capabilities for loading existing PHP code. |
| 10 | + |
| 11 | +## Essential Commands |
| 12 | + |
| 13 | +### Testing |
| 14 | +```bash |
| 15 | +# Run all tests |
| 16 | +composer run tester |
| 17 | +# OR |
| 18 | +vendor/bin/tester tests -s -C |
| 19 | + |
| 20 | +# Run specific test file |
| 21 | +vendor/bin/tester tests/PhpGenerator/ClassType.phpt -s -C |
| 22 | + |
| 23 | +# Run tests in specific directory |
| 24 | +vendor/bin/tester tests/PhpGenerator/ -s -C |
| 25 | +``` |
| 26 | + |
| 27 | +### Static Analysis |
| 28 | +```bash |
| 29 | +# Run PHPStan analysis |
| 30 | +composer run phpstan |
| 31 | +# OR |
| 32 | +vendor/bin/phpstan analyse |
| 33 | +``` |
| 34 | + |
| 35 | +### Code Style |
| 36 | +The project follows Nette Coding Standard with configuration in `ncs.php`. Uses tabs for indentation and braces on next line for functions/methods. |
| 37 | + |
| 38 | +## Architecture |
| 39 | + |
| 40 | +### Core Components |
| 41 | + |
| 42 | +**PhpFile** (`PhpFile.php`) |
| 43 | +- Top-level container representing a complete PHP file |
| 44 | +- Manages namespaces, strict_types declaration, and file-level comments |
| 45 | +- Entry point: `PhpFile::add()` for adding namespaces/classes/functions |
| 46 | + |
| 47 | +**PhpNamespace** (`PhpNamespace.php`) |
| 48 | +- Represents a namespace with use statements and contained classes/functions |
| 49 | +- Handles type resolution and name simplification based on use statements |
| 50 | +- Methods: `addClass()`, `addInterface()`, `addTrait()`, `addEnum()`, `addFunction()` |
| 51 | + |
| 52 | +**ClassLike** (`ClassLike.php`) |
| 53 | +- Abstract base for class-like structures (classes, interfaces, traits, enums) |
| 54 | +- Provides common functionality for attributes and comments |
| 55 | +- Extended by ClassType, InterfaceType, TraitType, EnumType |
| 56 | + |
| 57 | +**ClassType** (`ClassType.php`) |
| 58 | +- Represents class definitions with full feature support |
| 59 | +- Composes traits for properties, methods, constants, and trait usage |
| 60 | +- Supports final, abstract, readonly modifiers, extends, and implements |
| 61 | + |
| 62 | +**Method** (`Method.php`) & **GlobalFunction** (`GlobalFunction.php`) |
| 63 | +- Method represents class/interface/trait methods |
| 64 | +- GlobalFunction represents standalone functions |
| 65 | +- Both use FunctionLike trait for parameters, return types, and body |
| 66 | + |
| 67 | +**Property** (`Property.php`) |
| 68 | +- Represents class properties with type hints, visibility, hooks |
| 69 | +- Supports PHP 8.4 property hooks (get/set) via PropertyHook class |
| 70 | +- Supports asymmetric visibility (different get/set visibility) |
| 71 | + |
| 72 | +**Printer** (`Printer.php`) & **PsrPrinter** (`PsrPrinter.php`) |
| 73 | +- Printer: Generates code following Nette Coding Standard (tabs, braces on next line) |
| 74 | +- PsrPrinter: Generates PSR-2/PSR-12/PER compliant code |
| 75 | +- Configurable via properties: `$wrapLength`, `$indentation`, `$linesBetweenMethods`, etc. |
| 76 | +- Handles type resolution when printing within namespace context |
| 77 | + |
| 78 | +**Factory** (`Factory.php`) & **Extractor** (`Extractor.php`) |
| 79 | +- Factory: Creates PhpGenerator objects from existing classes/functions |
| 80 | +- Extractor: Low-level parser integration (requires nikic/php-parser) |
| 81 | +- Enable loading existing code: `ClassType::from()`, `PhpFile::fromCode()` |
| 82 | + |
| 83 | +**Dumper** (`Dumper.php`) |
| 84 | +- Converts PHP values to code representation |
| 85 | +- Used internally for default values, but also available for standalone use |
| 86 | +- Better output than `var_export()` |
| 87 | + |
| 88 | +### Trait Organization (`Traits/`) |
| 89 | + |
| 90 | +Shared functionality is implemented via traits for composition: |
| 91 | + |
| 92 | +- **FunctionLike**: Parameters, return types, body management for functions/methods |
| 93 | +- **PropertyLike**: Core property functionality (type, value, visibility) |
| 94 | +- **AttributeAware**: Attribute support for all elements |
| 95 | +- **CommentAware**: Doc comment management |
| 96 | +- **VisibilityAware**: Visibility modifiers (public/protected/private) |
| 97 | +- **ConstantsAware**: Class constant management |
| 98 | +- **MethodsAware**: Method collection management |
| 99 | +- **PropertiesAware**: Property collection management |
| 100 | +- **TraitsAware**: Trait usage management |
| 101 | + |
| 102 | +This trait-based architecture allows ClassType to compose all necessary features while keeping concerns separated. |
| 103 | + |
| 104 | +### Type System |
| 105 | + |
| 106 | +**Type** (`Type.php`) |
| 107 | +- Constants for native types (String, Int, Array, etc.) |
| 108 | +- Helper methods for union/intersection/nullable types |
| 109 | +- Used throughout for type hints and return types |
| 110 | + |
| 111 | +**Literal** (`Literal.php`) |
| 112 | +- Represents raw PHP code that should not be escaped |
| 113 | +- Used for default values, constants, expressions |
| 114 | +- Supports placeholders for value injection (see Placeholders section below) |
| 115 | +- `Literal::new()` helper for creating object instantiation literals |
| 116 | + |
| 117 | +### Test Structure |
| 118 | + |
| 119 | +Tests use Nette Tester with `.phpt` extension: |
| 120 | +- Located in `tests/PhpGenerator/` |
| 121 | +- Mirror source file organization |
| 122 | +- Use `test()` function for test cases (defined in `bootstrap.php`) |
| 123 | +- Use `testException()` for exception testing |
| 124 | +- Helper functions: `same()`, `sameFile()` for assertions |
| 125 | + |
| 126 | +Example test pattern: |
| 127 | +```php |
| 128 | +test('description of what is being tested', function () { |
| 129 | + $class = new ClassType('Demo'); |
| 130 | + // ... test code |
| 131 | + Assert::same($expected, (string) $class); |
| 132 | +}); |
| 133 | +``` |
| 134 | + |
| 135 | +## Important Features |
| 136 | + |
| 137 | +### Placeholders in Function Bodies |
| 138 | + |
| 139 | +The library supports special placeholders for inserting values into method/function bodies: |
| 140 | + |
| 141 | +- **`?`** - Simple placeholder for single values (strings, numbers, arrays) |
| 142 | + ```php |
| 143 | + $function->addBody('return substr(?, ?);', [$str, $num]); |
| 144 | + // Generates: return substr('any string', 3); |
| 145 | + ``` |
| 146 | + |
| 147 | +- **`...?`** - Variadic placeholder (unpacks arrays as separate arguments) |
| 148 | + ```php |
| 149 | + $function->setBody('myfunc(...?);', [[1, 2, 3]]); |
| 150 | + // Generates: myfunc(1, 2, 3); |
| 151 | + ``` |
| 152 | + |
| 153 | +- **`...?:`** - Named parameters placeholder for PHP 8+ |
| 154 | + ```php |
| 155 | + $function->setBody('myfunc(...?:);', [['foo' => 1, 'bar' => true]]); |
| 156 | + // Generates: myfunc(foo: 1, bar: true); |
| 157 | + ``` |
| 158 | + |
| 159 | +- **`\?`** - Escaped placeholder (literal question mark) |
| 160 | + ```php |
| 161 | + $function->addBody('return $a \? 10 : ?;', [$num]); |
| 162 | + // Generates: return $a ? 10 : 3; |
| 163 | + ``` |
| 164 | + |
| 165 | +### Printer Configuration |
| 166 | + |
| 167 | +Both `Printer` and `PsrPrinter` can be customized by extending and overriding public properties: |
| 168 | + |
| 169 | +```php |
| 170 | +class MyPrinter extends Nette\PhpGenerator\Printer |
| 171 | +{ |
| 172 | + public int $wrapLength = 120; // line length for wrapping |
| 173 | + public string $indentation = "\t"; // indentation character |
| 174 | + public int $linesBetweenProperties = 0; // blank lines between properties |
| 175 | + public int $linesBetweenMethods = 2; // blank lines between methods |
| 176 | + public int $linesBetweenUseTypes = 0; // blank lines between use statement groups |
| 177 | + public bool $bracesOnNextLine = true; // opening brace position for functions/methods |
| 178 | + public bool $singleParameterOnOneLine = false; // single parameter formatting |
| 179 | + public bool $omitEmptyNamespaces = true; // omit empty namespaces |
| 180 | + public string $returnTypeColon = ': '; // separator before return type |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +**Note:** `Printer` uses Nette Coding Standard (tabs, braces on next line), while `PsrPrinter` follows PSR-2/PSR-12/PER (spaces, braces on same line). |
| 185 | + |
| 186 | +### Property Hooks (PHP 8.4) |
| 187 | + |
| 188 | +Property hooks allow defining get/set operations directly on properties: |
| 189 | + |
| 190 | +```php |
| 191 | +$prop = $class->addProperty('firstName')->setType('string'); |
| 192 | +$prop->addHook('set', 'strtolower($value)') // Arrow function style |
| 193 | + ->addParameter('value')->setType('string'); |
| 194 | +$prop->addHook('get') // Block style |
| 195 | + ->setBody('return ucfirst($this->firstName);'); |
| 196 | +``` |
| 197 | + |
| 198 | +Property hooks can be marked as `abstract` or `final` using `setAbstract()` / `setFinal()`. |
| 199 | + |
| 200 | +### Asymmetric Visibility (PHP 8.4) |
| 201 | + |
| 202 | +Properties can have different visibility for reading vs writing: |
| 203 | + |
| 204 | +```php |
| 205 | +// Using setVisibility() with two parameters |
| 206 | +$class->addProperty('name') |
| 207 | + ->setVisibility('public', 'private'); // public get, private set |
| 208 | + |
| 209 | +// Using modifier methods with 'get' or 'set' mode |
| 210 | +$class->addProperty('id') |
| 211 | + ->setProtected('set'); // protected set, public get (default) |
| 212 | +``` |
| 213 | + |
| 214 | +Generates: `public private(set) string $name;` |
| 215 | + |
| 216 | +### ClassManipulator |
| 217 | + |
| 218 | +The `ClassManipulator` class provides advanced class manipulation: |
| 219 | + |
| 220 | +- **`inheritMethod($name)`** - Copies method from parent/interface for overriding |
| 221 | +- **`inheritProperty($name)`** - Copies property from parent class |
| 222 | +- **`implement($interface)`** - Automatically implements all methods/properties from interface/abstract class |
| 223 | + |
| 224 | +```php |
| 225 | +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); |
| 226 | +$manipulator->implement(SomeInterface::class); |
| 227 | +// Now $class contains stub implementations of all interface methods |
| 228 | +``` |
| 229 | + |
| 230 | +### Arrow Functions |
| 231 | + |
| 232 | +While `Closure` represents anonymous functions, you can generate arrow functions using the printer: |
| 233 | + |
| 234 | +```php |
| 235 | +$closure = new Nette\PhpGenerator\Closure; |
| 236 | +$closure->setBody('$a + $b'); // Note: arrow function body without 'return' |
| 237 | +$closure->addParameter('a'); |
| 238 | +$closure->addParameter('b'); |
| 239 | + |
| 240 | +echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure); |
| 241 | +// Generates: fn($a, $b) => $a + $b |
| 242 | +``` |
| 243 | + |
| 244 | +### Cloning Members |
| 245 | + |
| 246 | +Methods, properties, and constants can be cloned under a different name: |
| 247 | + |
| 248 | +```php |
| 249 | +$methodCount = $class->getMethod('count'); |
| 250 | +$methodRecount = $methodCount->cloneWithName('recount'); |
| 251 | +$class->addMember($methodRecount); |
| 252 | +``` |
| 253 | + |
| 254 | +## Key Patterns |
| 255 | + |
| 256 | +### Builder Pattern |
| 257 | +All classes use fluent interface - methods return `$this`/`static` for chaining: |
| 258 | +```php |
| 259 | +$class->setFinal() |
| 260 | + ->setExtends(ParentClass::class) |
| 261 | + ->addImplement(Countable::class); |
| 262 | +``` |
| 263 | + |
| 264 | +### Type Resolution |
| 265 | +When a class is part of a namespace, types are automatically resolved: |
| 266 | +- Fully qualified names → simplified based on use statements |
| 267 | +- Can be disabled: `$printer->setTypeResolving(false)` |
| 268 | +- Use `$namespace->simplifyType()` for manual resolution |
| 269 | + |
| 270 | +### Code Generation Flow |
| 271 | +1. Create PhpFile |
| 272 | +2. Add namespace(s) |
| 273 | +3. Add classes/interfaces/traits/enums/functions to namespace |
| 274 | +4. Add members (properties/methods/constants) to classes |
| 275 | +5. Convert to string or use Printer explicitly |
| 276 | + |
| 277 | +### Validation |
| 278 | +Classes validate themselves before printing via `validate()` method. Throws `Nette\InvalidStateException` for invalid states (e.g., abstract and final simultaneously). |
| 279 | + |
| 280 | +### Cloning |
| 281 | +All major classes implement `__clone()` to deep-clone contained objects (methods, properties, etc.). |
| 282 | + |
| 283 | +## Common Tasks |
| 284 | + |
| 285 | +### Adding New Features |
| 286 | +1. Check if feature needs new class or extends existing (e.g., PropertyHook for property hooks) |
| 287 | +2. Add tests first in `tests/PhpGenerator/` |
| 288 | +3. Update Printer to generate correct syntax |
| 289 | +4. Update Factory/Extractor if feature should be loadable from existing code |
| 290 | +5. Consider PsrPrinter compatibility |
| 291 | + |
| 292 | +### Updating for New PHP Versions |
| 293 | +1. Add support to builder classes (e.g., new method/property) |
| 294 | +2. Update Printer with syntax generation |
| 295 | +3. Update Extractor to parse the feature (via php-parser) |
| 296 | +4. Add comprehensive tests including edge cases |
| 297 | +5. Update README.md with examples |
| 298 | + |
| 299 | +### Test Expectations |
| 300 | +- Test files may have corresponding `.expect` files with expected output |
| 301 | +- Use `sameFile()` helper to compare against expectation files |
| 302 | +- This keeps test files clean and makes output changes visible in diffs |
| 303 | + |
| 304 | +## Important Notes & Limitations |
| 305 | + |
| 306 | +### Loading from Existing Code |
| 307 | +- **Requires `nikic/php-parser`** to load method/function bodies with `withBodies: true` / `withBody: true` |
| 308 | +- Without php-parser, bodies are empty but signatures are complete |
| 309 | +- Single-line comments outside method bodies are ignored when loading (library has no API for them) |
| 310 | +- Use `nikic/php-parser` directly if you need to manipulate global code or individual statements |
| 311 | + |
| 312 | +### PhpFile Restrictions |
| 313 | +- **No global code allowed** - PhpFile can only contain namespaces, classes, functions |
| 314 | +- Cannot add arbitrary code like `echo 'hello'` outside functions/classes |
| 315 | +- Use `setStrictTypes()` for `declare(strict_types=1)` declaration |
| 316 | + |
| 317 | +### Exception Handling |
| 318 | +- Adding duplicate members (same name) throws `Nette\InvalidStateException` |
| 319 | +- Use `addMember($member, overwrite: true)` to replace existing members |
| 320 | +- Invalid class states (e.g., abstract + final) detected by `validate()` method |
| 321 | + |
| 322 | +### Removal Methods |
| 323 | +- `removeProperty($name)`, `removeConstant($name)`, `removeMethod($name)`, `removeParameter($name)` |
| 324 | +- Available on respective container classes |
| 325 | + |
| 326 | +### Compatibility |
| 327 | +- **PhpGenerator 4.1+** supports PHP 8.0 to 8.4 |
| 328 | +- **PhpGenerator 4.2** (current) compatible with PHP 8.1 to 8.5 |
| 329 | +- Check composer.json for exact version requirements |
0 commit comments