This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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.
The library provides both a builder API for constructing PHP structures and extraction capabilities for loading existing PHP code.
# Run all tests
composer run tester
# OR
vendor/bin/tester tests -s
# Run specific test file
vendor/bin/tester tests/PhpGenerator/ClassType.phpt -s
# Run tests in specific directory
vendor/bin/tester tests/PhpGenerator/ -s# Run PHPStan analysis
composer run phpstan
# OR
vendor/bin/phpstan analyseThe project follows Nette Coding Standard with configuration in ncs.php. Uses tabs for indentation and braces on next line for functions/methods.
PhpFile (PhpFile.php)
- Top-level container representing a complete PHP file
- Manages namespaces, strict_types declaration, and file-level comments
- Entry point:
PhpFile::add()for adding namespaces/classes/functions
PhpNamespace (PhpNamespace.php)
- Represents a namespace with use statements and contained classes/functions
- Handles type resolution and name simplification based on use statements
- Methods:
addClass(),addInterface(),addTrait(),addEnum(),addFunction()
ClassLike (ClassLike.php)
- Abstract base for class-like structures (classes, interfaces, traits, enums)
- Provides common functionality for attributes and comments
- Extended by ClassType, InterfaceType, TraitType, EnumType
ClassType (ClassType.php)
- Represents class definitions with full feature support
- Composes traits for properties, methods, constants, and trait usage
- Supports final, abstract, readonly modifiers, extends, and implements
Method (Method.php) & GlobalFunction (GlobalFunction.php)
- Method represents class/interface/trait methods
- GlobalFunction represents standalone functions
- Both use FunctionLike trait for parameters, return types, and body
Property (Property.php)
- Represents class properties with type hints, visibility, hooks
- Supports PHP 8.4 property hooks (get/set) via PropertyHook class
- Supports asymmetric visibility (different get/set visibility)
Printer (Printer.php) & PsrPrinter (PsrPrinter.php)
- Printer: Generates code following Nette Coding Standard (tabs, braces on next line)
- PsrPrinter: Generates PSR-2/PSR-12/PER compliant code
- Configurable via properties:
$wrapLength,$indentation,$linesBetweenMethods, etc. - Handles type resolution when printing within namespace context
Factory (Factory.php) & Extractor (Extractor.php)
- Factory: Creates PhpGenerator objects from existing classes/functions
- Extractor: Low-level parser integration (requires nikic/php-parser)
- Enable loading existing code:
ClassType::from(),PhpFile::fromCode()
Dumper (Dumper.php)
- Converts PHP values to code representation
- Used internally for default values, but also available for standalone use
- Better output than
var_export()
Shared functionality is implemented via traits for composition:
- FunctionLike: Parameters, return types, body management for functions/methods
- PropertyLike: Core property functionality (type, value, visibility)
- AttributeAware: Attribute support for all elements
- CommentAware: Doc comment management
- VisibilityAware: Visibility modifiers (public/protected/private)
- ConstantsAware: Class constant management
- MethodsAware: Method collection management
- PropertiesAware: Property collection management
- TraitsAware: Trait usage management
This trait-based architecture allows ClassType to compose all necessary features while keeping concerns separated.
Type (Type.php)
- Constants for native types (String, Int, Array, etc.)
- Helper methods for union/intersection/nullable types
- Used throughout for type hints and return types
Literal (Literal.php)
- Represents raw PHP code that should not be escaped
- Used for default values, constants, expressions
- Supports placeholders for value injection (see Placeholders section below)
Literal::new()helper for creating object instantiation literals
Tests use Nette Tester with .phpt extension:
- Located in
tests/PhpGenerator/ - Mirror source file organization
- Use
test()function for test cases (defined inbootstrap.php) - Use
testException()for exception testing - Helper functions:
same(),sameFile()for assertions
Example test pattern:
test('description of what is being tested', function () {
$class = new ClassType('Demo');
// ... test code
Assert::same($expected, (string) $class);
});The library supports special placeholders for inserting values into method/function bodies:
-
?- Simple placeholder for single values (strings, numbers, arrays)$function->addBody('return substr(?, ?);', [$str, $num]); // Generates: return substr('any string', 3);
-
...?- Variadic placeholder (unpacks arrays as separate arguments)$function->setBody('myfunc(...?);', [[1, 2, 3]]); // Generates: myfunc(1, 2, 3);
-
...?:- Named parameters placeholder for PHP 8+$function->setBody('myfunc(...?:);', [['foo' => 1, 'bar' => true]]); // Generates: myfunc(foo: 1, bar: true);
-
\?- Escaped placeholder (literal question mark)$function->addBody('return $a \? 10 : ?;', [$num]); // Generates: return $a ? 10 : 3;
Both Printer and PsrPrinter can be customized by extending and overriding public properties:
class MyPrinter extends Nette\PhpGenerator\Printer
{
public int $wrapLength = 120; // line length for wrapping
public string $indentation = "\t"; // indentation character
public int $linesBetweenProperties = 0; // blank lines between properties
public int $linesBetweenMethods = 2; // blank lines between methods
public int $linesBetweenUseTypes = 0; // blank lines between use statement groups
public bool $bracesOnNextLine = true; // opening brace position for functions/methods
public bool $singleParameterOnOneLine = false; // single parameter formatting
public bool $omitEmptyNamespaces = true; // omit empty namespaces
public string $returnTypeColon = ': '; // separator before return type
}Note: Printer uses Nette Coding Standard (tabs, braces on next line), while PsrPrinter follows PSR-2/PSR-12/PER (spaces, braces on same line).
Property hooks allow defining get/set operations directly on properties:
$prop = $class->addProperty('firstName')->setType('string');
$prop->addHook('set', 'strtolower($value)') // Arrow function style
->addParameter('value')->setType('string');
$prop->addHook('get') // Block style
->setBody('return ucfirst($this->firstName);');Property hooks can be marked as abstract or final using setAbstract() / setFinal().
Properties can have different visibility for reading vs writing:
// Using setVisibility() with two parameters
$class->addProperty('name')
->setVisibility('public', 'private'); // public get, private set
// Using modifier methods with 'get' or 'set' mode
$class->addProperty('id')
->setProtected('set'); // protected set, public get (default)Generates: public private(set) string $name;
The ClassManipulator class provides advanced class manipulation:
inheritMethod($name)- Copies method from parent/interface for overridinginheritProperty($name)- Copies property from parent classimplement($interface)- Automatically implements all methods/properties from interface/abstract class
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
$manipulator->implement(SomeInterface::class);
// Now $class contains stub implementations of all interface methodsWhile Closure represents anonymous functions, you can generate arrow functions using the printer:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b'); // Note: arrow function body without 'return'
$closure->addParameter('a');
$closure->addParameter('b');
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
// Generates: fn($a, $b) => $a + $bMethods, properties, and constants can be cloned under a different name:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);All classes use fluent interface - methods return $this/static for chaining:
$class->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class);When a class is part of a namespace, types are automatically resolved:
- Fully qualified names → simplified based on use statements
- Can be disabled:
$printer->setTypeResolving(false) - Use
$namespace->simplifyType()for manual resolution
- Create PhpFile
- Add namespace(s)
- Add classes/interfaces/traits/enums/functions to namespace
- Add members (properties/methods/constants) to classes
- Convert to string or use Printer explicitly
Classes validate themselves before printing via validate() method. Throws Nette\InvalidStateException for invalid states (e.g., abstract and final simultaneously).
All major classes implement __clone() to deep-clone contained objects (methods, properties, etc.).
- Check if feature needs new class or extends existing (e.g., PropertyHook for property hooks)
- Add tests first in
tests/PhpGenerator/ - Update Printer to generate correct syntax
- Update Factory/Extractor if feature should be loadable from existing code
- Consider PsrPrinter compatibility
- Add support to builder classes (e.g., new method/property)
- Update Printer with syntax generation
- Update Extractor to parse the feature (via php-parser)
- Add comprehensive tests including edge cases
- Update README.md with examples
- Test files may have corresponding
.expectfiles with expected output - Use
sameFile()helper to compare against expectation files - This keeps test files clean and makes output changes visible in diffs
- Requires
nikic/php-parserto load method/function bodies withwithBodies: true/withBody: true - Without php-parser, bodies are empty but signatures are complete
- Single-line comments outside method bodies are ignored when loading (library has no API for them)
- Use
nikic/php-parserdirectly if you need to manipulate global code or individual statements
- No global code allowed - PhpFile can only contain namespaces, classes, functions
- Cannot add arbitrary code like
echo 'hello'outside functions/classes - Use
setStrictTypes()fordeclare(strict_types=1)declaration
- Adding duplicate members (same name) throws
Nette\InvalidStateException - Use
addMember($member, overwrite: true)to replace existing members - Invalid class states (e.g., abstract + final) detected by
validate()method
removeProperty($name),removeConstant($name),removeMethod($name),removeParameter($name)- Available on respective container classes
- PhpGenerator 4.1+ supports PHP 8.0 to 8.4
- PhpGenerator 4.2 (current) compatible with PHP 8.1 to 8.5
- Check composer.json for exact version requirements