diff --git a/.gitattributes b/.gitattributes index 9670e954e..ed8103553 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,10 @@ -.gitattributes export-ignore -.gitignore export-ignore -.github export-ignore -ncs.* export-ignore -phpstan.neon export-ignore -tests/ export-ignore +.gitattributes export-ignore +.github/ export-ignore +.gitignore export-ignore +CLAUDE.md export-ignore +ncs.* export-ignore +phpstan*.neon export-ignore +tests/ export-ignore -*.sh eol=lf -*.php* diff=php linguist-language=PHP +*.php* diff=php +*.sh text eol=lf diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 56fe2d503..859398b0c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.1', '8.2', '8.3', '8.4', '8.5'] + php: ['8.2', '8.3', '8.4', '8.5'] fail-fast: false @@ -20,7 +20,7 @@ jobs: coverage: none - run: composer install --no-progress --prefer-dist - - run: vendor/bin/tester tests -s -C + - run: composer tester - if: failure() uses: actions/upload-artifact@v4 with: @@ -35,11 +35,11 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable - - run: vendor/bin/tester tests -s -C + - run: composer tester code_coverage: @@ -53,7 +53,7 @@ jobs: coverage: none - run: composer install --no-progress --prefer-dist - - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src + - run: composer tester -- -p phpdbg --coverage ./coverage.xml --coverage-src ./src - run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index de4a392c3..d49bcd46e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor /composer.lock +tests/lock diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..91103df20 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,903 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**Nette DI** is a compiled Dependency Injection Container for PHP - a core component of the Nette Framework. This is a library/framework component, not an application. + +**Key characteristics:** +- Compiled container generates optimized PHP code for maximum performance +- Full autowiring support with type-based dependency resolution +- NEON configuration format for human-friendly service definitions +- Supports PHP 8.1 - 8.5 +- ~5,900 lines of production code + +## Essential Commands + +### Running Tests + +Tests use Nette Tester (not PHPUnit) with `.phpt` file format: + +```bash +# Run all tests +vendor/bin/tester tests -s -C + +# Run specific directory +vendor/bin/tester tests/DI/ -s -C + +# Run specific test file +vendor/bin/tester tests/DI/Compiler.configurator.phpt -s -C +``` + +**Flags explained:** +- `-s` - show output from tests +- `-C` - use system-wide php.ini + +### Static Analysis + +```bash +# Run PHPStan (level 5) +composer run phpstan +``` + +### Code Quality + +```bash +# Quick validation +composer run tester +composer run phpstan +``` + +## Test Infrastructure + +### Test File Structure + +All tests use `.phpt` format with embedded test cases: + +```php +getByType(ServiceClass::class)); +}); + +testException('description', function () { + // code that should throw +}, ExpectedException::class, 'Expected message'); +``` + +### Test Helpers (from bootstrap.php) + +**`createContainer($source, $config, $params = [])`** +- Compiles and instantiates container from config +- `$source` can be `Compiler` or `ContainerBuilder` +- `$config` is NEON string or file path +- Returns compiled container instance +- Generated code saved to `tests/tmp/{pid}/code.php` for debugging + +**`getTempDir()`** +- Returns per-process temporary directory: `tests/tmp/{pid}/` +- Automatically cleaned via garbage collection + +**`Notes::add($message)` / `Notes::fetch()`** +- Test notification system for debugging +- Add messages during execution, fetch for assertions + +### Common Test Patterns + +**Testing service registration:** +```php +$container = createContainer(new Compiler, ' + services: + - MyService +'); +``` + +**Testing with fixtures:** +```php +$loader = new Loader; +$config = $loader->load(__DIR__ . '/files/config.neon'); +$container = createContainer(new Compiler, $config); +``` + +**Testing generated code:** +```php +$builder = new ContainerBuilder; +$builder->addDefinition('foo')->setType(MyClass::class); +$code = (new PhpGenerator($builder))->generate('Container1'); +// inspect $code +``` + +## Architecture Overview + +### Compilation Flow + +The container compilation happens in distinct phases: + +1. **Load** - Configuration files loaded and merged (`Config\Loader`) +2. **Extensions** - Compiler extensions process configuration and register services (`CompilerExtension`) +3. **Resolve** - Dependencies resolved, types validated (`Resolver`) +4. **Generate** - PHP code generated for container class (`PhpGenerator`) +5. **Runtime** - Compiled container instantiated and used (`Container`) + +### Core Components + +**`Container.php`** (11KB) +- Runtime container holding service instances +- Provides services via `getService()`, `getByType()`, `getByName()` +- Manages autowiring metadata, tags, aliases +- Lazy loading and circular dependency detection + +**`Compiler.php`** (8.6KB) +- Orchestrates compilation process +- Manages compiler extensions +- Loads and processes configuration +- Generates container code + +**`ContainerBuilder.php`** (9.8KB) +- Builds service definition graph during compilation +- Central registry for all service definitions +- Handles autowiring setup +- Validates service configurations + +**`Resolver.php`** (21KB - largest file) +- Core dependency resolution logic +- Resolves `Reference`, `Statement`, and type references +- Detects circular dependencies +- Handles complex autowiring scenarios + +**`PhpGenerator.php`** (5.6KB) +- Generates optimized PHP code for container +- Uses `nette/php-generator` for code emission +- Creates type-safe service factory methods +- Produces highly optimized, readable code + +### Service Definitions (`src/DI/Definitions/`) + +Multiple definition types for different service patterns: + +- **`ServiceDefinition`** - Standard service with constructor/setup +- **`FactoryDefinition`** - Auto-generated factory from interface +- **`AccessorDefinition`** - Service accessor (getter) +- **`LocatorDefinition`** - Dynamic service locator +- **`ImportedDefinition`** - External service reference +- **`Reference`** - Reference to another service (`@serviceName`) +- **`Statement`** - Callable/function call (`trim(...)`) + +### Configuration System (`src/DI/Config/`) + +**`NeonAdapter.php`** - Primary configuration format +- Processes NEON syntax into service definitions +- Handles special syntax: + - `@serviceName` - service references + - `%paramName%` - parameter expansion + - `trim(...)` - first-class callable statements + - `!` suffix - prevent merging +- Uses visitor pattern with `NodeTraverser` + +**`Loader.php`** - Configuration file loading +- Merges multiple config files +- Environment-specific overrides +- Parameter inheritance + +### Extension System (`src/DI/Extensions/`) + +Built-in extensions providing core functionality: + +- **`ServicesExtension`** - Registers services from `services:` section +- **`ParametersExtension`** - Handles container parameters +- **`SearchExtension`** - Auto-registration by file patterns +- **`InjectExtension`** - Property/method injection (`#[Inject]`) +- **`DecoratorExtension`** - Service decoration patterns +- **`DIExtension`** - DI-specific configuration +- **`ExtensionsExtension`** - Extension management + +Create custom extensions by extending `CompilerExtension`. + +### Tracy Integration (`src/Bridges/DITracy/`) + +Debug panel showing: +- All registered services with types +- Service tags and wiring info +- Container parameters +- Compilation time +- Service instantiation status + +## Key Design Patterns + +1. **Compiled Container Pattern** - Container pre-generated as PHP code, not interpreted at runtime +2. **Builder Pattern** - `ContainerBuilder` constructs service graph before code generation +3. **Visitor Pattern** - NEON adapter uses traverser with visitors to process configuration tree +4. **Extension Point Pattern** - `CompilerExtension` allows pluggable compilation customization +5. **Lazy Loading** - Services instantiated on-demand, not upfront +6. **Code Generation** - Runtime container is optimized PHP code with zero interpretation overhead + +## NEON Configuration Syntax + +The primary configuration format uses special syntax understood by `NeonAdapter`: + +**Service references:** +```neon +services: + logger: FileLogger + mailer: + factory: Mailer + setup: + - setLogger(@logger) # Reference by name + - setConnection(@Nette\Database\Connection) # Reference by type +``` + +**First-class callables (since 3.2.0):** +```neon +services: + - MyService(trim(...)) # Callable passed as argument + - Factory::create(...) # Factory method callable + - UserService(@user::logout(...)) # Equivalent to [@user, 'logout'] +``` + +**Parameters:** +```neon +parameters: + logFile: /var/log/app.log + mailer: + host: smtp.example.com + user: admin + +services: + - FileLogger(%logFile%) # Parameter expansion + - Mailer(%mailer.host%, %mailer.user%) # Nested parameter access +``` + +**Expression language - create objects and call functions:** +```neon +services: + - DateTime() # Create object + - Collator::create(%locale%) # Call static method + database: DatabaseFactory::create() + router: @routerFactory::create() # Call method on service +``` + +**Method chaining (use `::` instead of `->`):** +```neon +parameters: + currentDate: DateTime()::format('Y-m-d') + # PHP: (new DateTime())->format('Y-m-d') + + host: @http.request::getUrl()::getHost() + # PHP: $this->getService('http.request')->getUrl()->getHost() +``` + +**Special functions:** +```neon +services: + - Foo( + id: int(::getenv('ProjectId')) # Lossless type casting + productionMode: not(%debugMode%) # Boolean negation + bars: typed(Bar) # Array of all Bar services + loggers: tagged(logger) # Array of services with 'logger' tag + ) +``` + +**Constants:** +```neon +services: + - DirectoryIterator(%tempDir%, FilesystemIterator::SKIP_DOTS) + phpVersion: ::constant(PHP_VERSION) +``` + +**Prevent merging with `!` suffix:** +```neon +services: + database!: CustomConnection # Won't be merged with parent config +items!: # Replace array instead of merging + - newItem +``` + +## Autowiring Behavior + +Autowiring automatically passes services to constructors and methods based on type hints. Understanding its nuances is critical when working with this codebase. + +### Basic Autowiring Rules + +- **Exactly one service** of each type must exist in the container +- Multiple services of same type cause autowiring to fail with exception +- Services can be excluded from autowiring using `autowired: false` + +### Disabling Autowiring + +```neon +services: + mainDb: PDO(%dsn%, %user%, %password%) + + tempDb: + create: PDO('sqlite::memory:') + autowired: false # Excluded from autowiring + + articles: ArticleRepository # Gets mainDb injected +``` + +**Important:** In Nette, `autowired: false` means "don't pass this service to others" (different from Symfony where it means "don't autowire constructor args"). + +### Autowiring Preference + +When multiple services of same type exist, mark one as preferred: + +```neon +services: + mainDb: + create: PDO(%dsn%, %user%, %password%) + autowired: PDO # Becomes preferred for PDO type + + tempDb: + create: PDO('sqlite::memory:') + + articles: ArticleRepository # Gets mainDb +``` + +### Narrowing Autowiring + +Limit which types a service can be autowired for: + +```neon +services: + parent: ParentClass + child: + create: ChildClass + autowired: ChildClass # Only autowired for ChildClass type, not ParentClass + # Can also use 'self' as alias for current class + + parentDep: ParentDependent # Gets parent service + childDep: ChildDependent # Gets child service +``` + +Multiple types can be specified: + +```neon +autowired: [BarClass, FooInterface] +``` + +**How narrowing works:** Service is only autowired when the required type matches or is a subtype of the narrowed type. + +### Collection of Services + +Autowiring can pass arrays of services: + +```php +class ShipManager +{ + /** + * @param Shipper[] $shippers + */ + public function __construct(array $shippers) + {} +} +``` + +The container automatically passes all `Shipper` services (excluding those with `autowired: false`). + +Alternative using `typed()` function: + +```neon +services: + - ShipManager(typed(Shipper)) +``` + +### Scalar Arguments + +Autowiring only works for objects and arrays of objects. Scalar values (strings, numbers, booleans) must be specified in configuration or wrapped in a settings object. + + +## Service Definition Patterns + +### Service Creation Methods + +**Simple class instantiation:** +```neon +services: + database: PDO('sqlite::memory:') +``` + +**Multi-line with additional configuration:** +```neon +services: + database: + create: PDO('sqlite::memory:') # or 'factory:' (both work) + setup: ... + tags: ... +``` + +**Static method factories:** +```neon +services: + database: DatabaseFactory::create() + router: @routerFactory::create() # Call method on another service +``` + +**With explicit type (when return type not declared):** +```neon +services: + database: + create: DatabaseFactory::create() + type: PDO +``` + +### Arguments + +**Named arguments (preferred for clarity):** +```neon +services: + database: PDO( + username: root + password: secret + dsn: 'mysql:host=127.0.0.1;dbname=test' + ) +``` + +**Omit arguments to use defaults or autowiring:** +```neon +services: + foo: Foo(_, %appDir%) # First arg autowired, second is parameter +``` + +### Setup Section + +Call methods after service creation: + +```neon +services: + database: + create: PDO(%dsn%, %user%, %password%) + setup: + - setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) + - $value = 123 # Set property + - '$onClick[]' = [@bar, clickHandler] # Add to array + - My\Helpers::initializeFoo(@self) # Pass service to static method + - @anotherService::setFoo(@self) # Call method on other service +``` + +### Lazy Services (PHP 8.4+) + +Enable globally or per-service: + +```neon +di: + lazy: true # Global setting + +services: + foo: + create: Foo + lazy: false # Override for specific service +``` + +Lazy services return proxy objects; actual instantiation happens on first method/property access. Only works for user-defined classes. + +### Tags + +Organize and query services by tags: + +```neon +services: + foo: + create: Foo + tags: + - cached + logger: monolog.logger.event # Tag with value +``` + +Retrieve tagged services: + +```php +$names = $container->findByTag('logger'); +// ['foo' => 'monolog.logger.event', ...] +``` + +Or in configuration: + +```neon +services: + - LoggersDependent(tagged(logger)) +``` + +### Service Modifications + +Modify services registered by extensions: + +```neon +services: + application.application: + create: MyApplication + alteration: true # Indicates we're modifying existing service + setup: + - '$onStartup[]' = [@resource, init] +``` + +Remove original configuration: + +```neon +services: + application.application: + alteration: true + reset: + - arguments + - setup + - tags +``` + +Remove service entirely: + +```neon +services: + cache.journal: false +``` + + +## Configuration Sections + +### Decorator + +Apply setup to all services of a specific type: + +```neon +decorator: + App\Presentation\BasePresenter: + setup: + - setProjectId(10) + - $absoluteUrls = true + + InjectableInterface: + tags: [mytag: 1] + inject: true +``` + +Useful for: +- Calling methods on all presenters +- Setting tags on interfaces +- Enabling inject mode for specific types + +### Search (Auto-registration) + +Automatically register services by file/class patterns: + +```neon +search: + - in: %appDir%/Forms + files: + - *Factory.php + classes: + - *Factory + + - in: %appDir%/Model + extends: + - App\*Form + implements: + - App\*FormInterface + exclude: + files: ... + classes: ... + tags: [autoregistered] +``` + +**Filtering options:** +- `files:` - Filter by filename pattern +- `classes:` - Filter by class name pattern +- `extends:` - Select classes extending specified classes +- `implements:` - Select classes implementing interfaces +- `exclude:` - Exclusion rules (same keys as above) +- `tags:` - Tags to assign to all registered services + +### DI Section + +Technical container configuration: + +```neon +di: + debugger: true # Show DIC in Tracy Bar + excluded: [...] # Parameter types never autowired + lazy: false # Enable lazy services globally (PHP 8.4+) + parentClass: ... # Base class for DI container + + export: + parameters: false # Don't export parameters to metadata + tags: # Export only specific tags + - event.subscriber + types: # Export only specific types for autowiring + - Nette\Database\Connection +``` + +**Metadata optimization:** Reduce generated container size by limiting exported metadata to only what's actually used. + +### Including Files + +```neon +includes: + - parameters.php # Can include PHP files returning arrays + - services.neon + - presenters.neon +``` + +**Merging behavior:** +- Later files override earlier ones +- Arrays are merged (unless `!` suffix used) +- File containing `includes` has higher priority than included files + + +## Extension Development Lifecycle + +Extensions customize the compilation process by implementing up to 4 methods called sequentially: + +### 1. getConfigSchema() + +Define and validate extension configuration: + +```php +class BlogExtension extends Nette\DI\CompilerExtension +{ + public function getConfigSchema(): Nette\Schema\Schema + { + return Expect::structure([ + 'postsPerPage' => Expect::int(), + 'allowComments' => Expect::bool()->default(true), + ]); + } +} +``` + +Access config via `$this->config` (stdClass object). + +### 2. loadConfiguration() + +Register services to container: + +```php +public function loadConfiguration() +{ + $builder = $this->getContainerBuilder(); + $builder->addDefinition($this->prefix('articles')) + ->setFactory(App\Model\HomepageArticles::class, ['@connection']) + ->addSetup('setLogger', ['@logger']); +} +``` + +**Important:** Use `$this->prefix('name')` to avoid service name conflicts. + +**Loading from NEON:** + +```php +public function loadConfiguration() +{ + $this->compiler->loadDefinitionsFromConfig( + $this->loadFromFile(__DIR__ . '/blog.neon')['services'], + ); +} +``` + +In NEON file, use `@extension` to reference current extension's services. + +### 3. beforeCompile() + +Modify existing services or establish relationships: + +```php +public function beforeCompile() +{ + $builder = $this->getContainerBuilder(); + + foreach ($builder->findByTag('logaware') as $serviceName => $tagValue) { + $builder->getDefinition($serviceName)->addSetup('setLogger'); + } +} +``` + +Called after all `loadConfiguration()` methods complete. Service graph is fully defined. + +### 4. afterCompile() + +Modify generated container class: + +```php +public function afterCompile(Nette\PhpGenerator\ClassType $class) +{ + $method = $class->getMethod('__construct'); + // Modify generated PHP code +} +``` + +Container class already generated as `ClassType` object. Can modify before writing to cache. + +### $initialization + +Add code to run after container instantiation: + +```php +public function loadConfiguration() +{ + // Auto-start session + if ($this->config->session->autoStart) { + $this->initialization->addBody('$this->getService("session")->start()'); + } + + // Instantiate services tagged with 'run' + foreach ($this->getContainerBuilder()->findByTag('run') as $name => $foo) { + $this->initialization->addBody('$this->getService(?);', [$name]); + } +} +``` + + +## Generated Factories and Accessors + +Nette DI can generate factory and accessor implementations from interfaces. + +### Generated Factories + +**Define interface:** + +```php +interface ArticleFactory +{ + function create(): Article; +} +``` + +**Register in config:** + +```neon +services: + - ArticleFactory +``` + +Nette generates the implementation. Dependencies autowired into `Article` constructor. + +**Parameterized factories:** + +```php +interface ArticleFactory +{ + function create(int $authorId): Article; +} + +class Article +{ + public function __construct( + private Nette\Database\Connection $db, + private int $authorId, // Matched by name from factory method + ) {} +} +``` + +**Advanced configuration:** + +```neon +services: + articleFactory: + implement: ArticleFactory + arguments: + authorId: 123 # Fixed value passed to constructor + setup: + - setAuthorId($authorId) # Or via setter +``` + +### Accessors + +Provide lazy-loading for dependencies: + +```php +interface PDOAccessor +{ + function get(): PDO; +} +``` + +```neon +services: + - PDOAccessor + - PDO(%dsn%, %user%, %password%) +``` + +Accessor returns same instance on repeated calls. Database connection only created on first `get()` call. + +If multiple services of same type exist, specify which one: `- PDOAccessor(@db1)` + +### Multifactory/Accessor + +Combine multiple factories and accessors in one interface: + +```php +interface MultiFactory +{ + function createArticle(): Article; + function getDb(): PDO; +} +``` + +**Definition with list (3.2.0+):** + +```neon +services: + - MultiFactory( + article: Article + db: PDO(%dsn%, %user%, %password%) + ) +``` + +**Or with references:** + +```neon +services: + article: Article + - PDO(%dsn%, %user%, %password%) + - MultiFactory( + article: @article + db: @\PDO + ) +``` + + +## Development Workflow + +### Adding New Features + +When adding features to the DI container: + +1. **Determine scope** - Does it need new definition type, compiler extension, or core change? +2. **Update definitions** - Add to `ContainerBuilder` if new service type +3. **Implement resolution** - Update `Resolver` if special dependency handling needed +4. **Generate code** - Modify `PhpGenerator` to emit correct PHP code +5. **Add tests** - Create `.phpt` test files demonstrating usage +6. **Update docs** - Changes to configuration syntax need documentation + +### Debugging Tips + +**Inspect generated container:** +- Generated code is cached in temp directory +- Use `ContainerLoader` with `autoRebuild: true` during development +- Check `tests/tmp/{pid}/code.php` during test runs + +**Use Tracy panel:** +- Shows all registered services and their state +- Reveals autowiring metadata +- Displays compilation time + +**Test helpers:** +- `Notes::add()` for debug messages in tests +- Examine `tests/DI/expected/` for expected code output +- Use `Tester\FileMock::create()` for inline NEON configs + +### Common Gotchas + +- **Strict types required** - All files must have `declare(strict_types=1)` +- **NEON syntax sensitivity** - Indentation matters, references need `@` prefix +- **Circular dependencies** - Resolver detects but requires careful definition ordering +- **Generated code cache** - Use `autoRebuild: true` to avoid stale container during development +- **Test isolation** - Each test gets unique container class name via counter + +## Code Style + +Follows Nette Coding Standard (based on PSR-12): + +- Strict types declaration in all files +- PascalCase for classes, camelCase for methods/properties +- Type hints for all parameters, properties, return values +- Two empty lines between methods (per Nette convention) +- Exceptions grouped in `exceptions.php` files +- Natural language exception messages (e.g., "The file does not exist.") + +## Important Files + +**Entry points for understanding:** +- `readme.md` - Excellent overview with working examples +- `src/DI/Container.php` - Runtime behavior +- `src/DI/Compiler.php` - Compilation orchestration +- `src/DI/Resolver.php` - Dependency resolution logic +- `tests/DI/*.phpt` - Real usage patterns + +**Configuration examples:** +- `tests/DI/files/*.neon` - Test fixtures showing NEON syntax +- `tests/DI/expected/*.php` - Expected generated container code diff --git a/composer.json b/composer.json index baeb44700..c5e2c141d 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,13 @@ } ], "require": { - "php": "8.1 - 8.5", + "php": "8.2 - 8.5", "ext-tokenizer": "*", "ext-ctype": "*", - "nette/neon": "^3.3", + "nette/neon": "^3.4", "nette/php-generator": "^4.1.6", "nette/robot-loader": "^4.0", - "nette/schema": "^1.2.5", + "nette/schema": "^1.3", "nette/utils": "^4.0" }, "require-dev": { @@ -38,11 +38,26 @@ "minimum-stability": "dev", "scripts": { "phpstan": "phpstan analyse", - "tester": "tester tests -s" + "tester": "tester tests -s -C" }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" + }, + "nette": { + "di-extensions": { + "decorator": "Nette\\DI\\Extensions\\DecoratorExtension", + "di": { + "class": "Nette\\DI\\Extensions\\DIExtension", + "args": ["%debugMode%"] + }, + "extensions": "Nette\\DI\\Extensions\\ExtensionsExtension", + "inject": "Nette\\DI\\Extensions\\InjectExtension", + "search": { + "class": "Nette\\DI\\Extensions\\SearchExtension", + "args": ["%tempDir%/cache/nette.search"] + } + } } } } diff --git a/readme.md b/readme.md index 91fe39ae5..69927709d 100644 --- a/readme.md +++ b/readme.md @@ -39,7 +39,7 @@ The recommended way to install is via Composer: composer require nette/di ``` -It requires PHP version 8.1 and supports PHP up to 8.5. +It requires PHP version 8.2 and supports PHP up to 8.5. diff --git a/src/Bridges/DITracy/ContainerPanel.php b/src/Bridges/DITracy/ContainerPanel.php index 57f4431e9..766ce89ca 100644 --- a/src/Bridges/DITracy/ContainerPanel.php +++ b/src/Bridges/DITracy/ContainerPanel.php @@ -22,14 +22,16 @@ class ContainerPanel implements Tracy\IBarPanel { public static ?float $compilationTime = null; private Nette\DI\Container $container; + private Tracy\BlueScreen $blueScreen; private ?float $elapsedTime; - public function __construct(Container $container) + public function __construct(Container $container, Tracy\BlueScreen $blueScreen) { $this->container = $container; + $this->blueScreen = $blueScreen; $this->elapsedTime = self::$compilationTime - ? microtime(true) - self::$compilationTime + ? microtime(as_float: true) - self::$compilationTime : null; } @@ -77,6 +79,7 @@ public function getPanel(): string $parameters = $rc->getMethod('getStaticParameters')->getDeclaringClass()->getName() === Container::class ? null : $container->getParameters(); + $keysToHide = $this->blueScreen->keysToHide; require __DIR__ . '/dist/panel.phtml'; }); } diff --git a/src/Bridges/DITracy/dist/panel.phtml b/src/Bridges/DITracy/dist/panel.phtml index 8525b5794..fc7b616a3 100644 --- a/src/Bridges/DITracy/dist/panel.phtml +++ b/src/Bridges/DITracy/dist/panel.phtml @@ -58,7 +58,7 @@ declare(strict_types=1);
= Tracy\Helpers::escapeHtml(get_class($instances[$name])) ?>
diff --git a/src/Bridges/DITracy/panel.latte b/src/Bridges/DITracy/panel.latte
index 87d51026e..1b6090799 100644
--- a/src/Bridges/DITracy/panel.latte
+++ b/src/Bridges/DITracy/panel.latte
@@ -51,7 +51,7 @@
{get_class($instances[$name])}
{elseif is_string($type)}
diff --git a/src/DI/Autowiring.php b/src/DI/Autowiring.php
index 3be57cb3a..97a223d7d 100644
--- a/src/DI/Autowiring.php
+++ b/src/DI/Autowiring.php
@@ -17,8 +17,6 @@
*/
class Autowiring
{
- private ContainerBuilder $builder;
-
/** @var array[] type => services, used by getByType() */
private array $highPriority = [];
@@ -29,9 +27,9 @@ class Autowiring
private array $excludedClasses = [];
- public function __construct(ContainerBuilder $builder)
- {
- $this->builder = $builder;
+ public function __construct(
+ private readonly ContainerBuilder $builder,
+ ) {
}
diff --git a/src/DI/Compiler.php b/src/DI/Compiler.php
index 6f675174d..cc3094a96 100644
--- a/src/DI/Compiler.php
+++ b/src/DI/Compiler.php
@@ -26,7 +26,6 @@ class Compiler
/** @var CompilerExtension[] */
private array $extensions = [];
- private ContainerBuilder $builder;
private array $config = [];
/** @var array [section => array[]] */
@@ -36,9 +35,9 @@ class Compiler
private string $className = 'Container';
- public function __construct(?ContainerBuilder $builder = null)
- {
- $this->builder = $builder ?: new ContainerBuilder;
+ public function __construct(
+ private readonly ?ContainerBuilder $builder = new ContainerBuilder,
+ ) {
$this->dependencies = new DependencyChecker;
$this->addExtension(self::Services, new Extensions\ServicesExtension);
$this->addExtension(self::Parameters, new Extensions\ParametersExtension($this->configs));
@@ -113,7 +112,7 @@ public function addConfig(array $config): static
public function loadConfig(string $file, ?Config\Loader $loader = null): static
{
$sources = $this->sources . "// source: $file\n";
- $loader = $loader ?: new Config\Loader;
+ $loader ??= $this->createLoader();
foreach ($loader->load($file, merge: false) as $data) {
$this->addConfig($data);
}
@@ -313,4 +312,13 @@ protected function createPhpGenerator(): PhpGenerator
{
return new PhpGenerator($this->builder);
}
+
+
+ /** @internal */
+ public function createLoader(array $params = []): Config\Loader
+ {
+ return (new Config\Loader)
+ ->addAdapter('php', new Config\Adapters\PhpAdapter($this->builder))
+ ->setParameters($params);
+ }
}
diff --git a/src/DI/Config/Adapter.php b/src/DI/Config/Adapter.php
index 25e9cecd8..a9b7004b6 100644
--- a/src/DI/Config/Adapter.php
+++ b/src/DI/Config/Adapter.php
@@ -20,6 +20,3 @@ interface Adapter
*/
function load(string $file): array;
}
-
-
-class_exists(IAdapter::class);
diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php
index 5b26583b9..64f42c4c5 100644
--- a/src/DI/Config/Adapters/NeonAdapter.php
+++ b/src/DI/Config/Adapters/NeonAdapter.php
@@ -25,6 +25,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter
{
private const PreventMergingSuffix = '!';
private string $file;
+ private \WeakMap $parents;
/**
@@ -33,7 +34,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter
public function load(string $file): array
{
$input = Nette\Utils\FileSystem::read($file);
- if (substr($input, 0, 3) === "\u{FEFF}") { // BOM
+ if (str_starts_with($input, "\u{FEFF}")) { // BOM
$input = substr($input, 3);
}
@@ -41,61 +42,22 @@ public function load(string $file): array
$decoder = new Neon\Decoder;
$node = $decoder->parseToNode($input);
$traverser = new Neon\Traverser;
+ $node = $traverser->traverse($node, $this->deprecatedQuestionMarkVisitor(...));
$node = $traverser->traverse($node, $this->firstClassCallableVisitor(...));
$node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...));
$node = $traverser->traverse($node, $this->convertAtSignVisitor(...));
- $node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...));
$node = $traverser->traverse($node, $this->resolveConstantsVisitor(...));
- return $this->process((array) $node->toValue());
+ $node = $traverser->traverse($node, $this->preventMergingVisitor(...));
+ $this->connectParentsVisitor($traverser, $node);
+ $node = $traverser->traverse($node, leave: $this->entityToExpressionVisitor(...));
+ return (array) $node->toValue();
}
- /** @throws Nette\InvalidStateException */
+ /** @deprecated */
public function process(array $arr): array
{
- $res = [];
- foreach ($arr as $key => $val) {
- if (is_string($key) && str_ends_with($key, self::PreventMergingSuffix)) {
- if (!is_array($val) && $val !== null) {
- throw new Nette\DI\InvalidConfigurationException(sprintf(
- "Replacing operator is available only for arrays, item '%s' is not array (used in '%s')",
- $key,
- $this->file,
- ));
- }
-
- $key = substr($key, 0, -1);
- $val[DI\Config\Helpers::PREVENT_MERGING] = true;
- }
-
- if (is_array($val)) {
- $val = $this->process($val);
-
- } elseif ($val instanceof Neon\Entity) {
- if ($val->value === Neon\Neon::Chain) {
- $tmp = null;
- foreach ($this->process($val->attributes) as $st) {
- $tmp = new Statement(
- $tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')],
- $st->arguments,
- );
- }
-
- $val = $tmp;
- } else {
- $tmp = $this->process([$val->value]);
- if (is_string($tmp[0]) && str_contains($tmp[0], '?')) {
- throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')");
- }
-
- $val = new Statement($tmp[0], $this->process($val->attributes));
- }
- }
-
- $res[$key] = $val;
- }
-
- return $res;
+ return $arr;
}
@@ -112,7 +74,7 @@ function (&$val): void {
}
},
);
- return "# generated by Nette\n\n" . Neon\Neon::encode($data, Neon\Neon::BLOCK);
+ return "# generated by Nette\n\n" . Neon\Neon::encode($data, blockMode: true);
}
@@ -165,6 +127,71 @@ private function firstClassCallableVisitor(Node $node): void
}
+ private function preventMergingVisitor(Node $node): void
+ {
+ if ($node instanceof Node\ArrayItemNode
+ && $node->key instanceof Node\LiteralNode
+ && is_string($node->key->value)
+ && str_ends_with($node->key->value, self::PreventMergingSuffix)
+ ) {
+ if ($node->value instanceof Node\LiteralNode && $node->value->value === null) {
+ $node->value = new Node\InlineArrayNode('[');
+ } elseif (!$node->value instanceof Node\ArrayNode) {
+ throw new Nette\DI\InvalidConfigurationException(sprintf(
+ "Replacing operator is available only for arrays, item '%s' is not array (used in '%s')",
+ $node->key->value,
+ $this->file,
+ ));
+ }
+
+ $node->key->value = substr($node->key->value, 0, -1);
+ $node->value->items[] = $item = new Node\ArrayItemNode;
+ $item->key = new Node\LiteralNode(DI\Config\Helpers::PREVENT_MERGING);
+ $item->value = new Node\LiteralNode(true);
+ }
+ }
+
+
+ private function deprecatedQuestionMarkVisitor(Node $node): void
+ {
+ if ($node instanceof Node\EntityNode
+ && ($node->value instanceof Node\LiteralNode || $node->value instanceof Node\StringNode)
+ && is_string($node->value->value)
+ && str_contains($node->value->value, '?')
+ ) {
+ throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')");
+ }
+ }
+
+
+ private function entityToExpressionVisitor(Node $node): Node
+ {
+ if ($node instanceof Node\EntityChainNode) {
+ return new Node\LiteralNode($this->buildExpression($node->chain));
+
+ } elseif (
+ $node instanceof Node\EntityNode
+ && !$this->parents[$node] instanceof Node\EntityChainNode
+ ) {
+ return new Node\LiteralNode($this->buildExpression([$node]));
+
+ } else {
+ return $node;
+ }
+ }
+
+
+ private function buildExpression(array $chain): Statement
+ {
+ $node = array_pop($chain);
+ $entity = $node->toValue();
+ return new Statement(
+ $chain ? [$this->buildExpression($chain), ltrim($entity->value, ':')] : $entity->value,
+ $entity->attributes,
+ );
+ }
+
+
private function removeUnderscoreVisitor(Node $node): void
{
if (!$node instanceof Node\EntityNode) {
@@ -182,11 +209,6 @@ private function removeUnderscoreVisitor(Node $node): void
if ($attr->value instanceof Node\LiteralNode && $attr->value->value === '_') {
unset($node->attributes[$i]);
$index = true;
-
- } elseif ($attr->value instanceof Node\LiteralNode && $attr->value->value === '...') {
- trigger_error("Replace ... with _ in configuration file '$this->file'.", E_USER_DEPRECATED);
- unset($node->attributes[$i]);
- $index = true;
}
}
}
@@ -211,17 +233,6 @@ private function convertAtSignVisitor(Node $node): void
}
- private function deprecatedParametersVisitor(Node $node): void
- {
- if (($node instanceof Node\StringNode || $node instanceof Node\LiteralNode)
- && is_string($node->value)
- && str_contains($node->value, '%parameters%')
- ) {
- trigger_error('%parameters% is deprecated, use @container::getParameters() (in ' . $this->file . ')', E_USER_DEPRECATED);
- }
- }
-
-
private function resolveConstantsVisitor(Node $node): void
{
$items = match (true) {
@@ -241,4 +252,21 @@ private function resolveConstantsVisitor(Node $node): void
}
}
}
+
+
+ private function connectParentsVisitor(Neon\Traverser $traverser, Node $node): void
+ {
+ $this->parents = new \WeakMap;
+ $stack = [];
+ $traverser->traverse(
+ $node,
+ enter: function (Node $node) use (&$stack) {
+ $this->parents[$node] = end($stack);
+ $stack[] = $node;
+ },
+ leave: function () use (&$stack) {
+ array_pop($stack);
+ },
+ );
+ }
}
diff --git a/src/DI/Config/Adapters/PhpAdapter.php b/src/DI/Config/Adapters/PhpAdapter.php
index 5471307b2..bfcf6ded1 100644
--- a/src/DI/Config/Adapters/PhpAdapter.php
+++ b/src/DI/Config/Adapters/PhpAdapter.php
@@ -17,12 +17,39 @@
*/
final class PhpAdapter implements Nette\DI\Config\Adapter
{
+ public function __construct(
+ private ?Nette\DI\ContainerBuilder $builder = null,
+ ) {
+ }
+
+
/**
* Reads configuration from PHP file.
*/
public function load(string $file): array
{
- return require $file;
+ $data = require $file;
+ return $data instanceof \Closure
+ ? $this->processClosure($data, $file)
+ : $data;
+ }
+
+
+ private function processClosure(\Closure $callback, string $file): array
+ {
+ $params = (new \ReflectionFunction($callback))->getParameters();
+ if (count($params) !== 1) {
+ throw new Nette\InvalidStateException(sprintf("Callback in configuration file '%s' must have exactly one parameter.", $file));
+ }
+ $type = $params[0]->getType();
+ if (!$type instanceof \ReflectionNamedType || $type->getName() !== Nette\DI\ContainerBuilder::class) {
+ throw new Nette\InvalidStateException(sprintf("Callback in configuration file '%s' must have parameter of type %s.", $file, Nette\DI\ContainerBuilder::class));
+ }
+ if (!$this->builder) {
+ throw new Nette\InvalidStateException(sprintf("Configuration file '%s' requires ContainerBuilder to be set.", $file));
+ }
+ $callback($this->builder);
+ return [];
}
diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php
index 617ec0dbf..51e00ad9c 100644
--- a/src/DI/ContainerBuilder.php
+++ b/src/DI/ContainerBuilder.php
@@ -23,10 +23,10 @@ class ContainerBuilder
ThisService = 'self',
ThisContainer = 'container';
- /** @deprecated use ContainerBuilder::ThisService */
+ #[\Deprecated('use ContainerBuilder::ThisService')]
public const THIS_SERVICE = self::ThisService;
- /** @deprecated use ContainerBuilder::ThisContainer */
+ #[\Deprecated('use ContainerBuilder::ThisContainer')]
public const THIS_CONTAINER = self::ThisContainer;
public array $parameters = [];
@@ -83,7 +83,7 @@ public function addDefinition(?string $name, ?Definition $definition = null): De
}
}
- $definition = $definition ?: new Definitions\ServiceDefinition;
+ $definition ??= new Definitions\ServiceDefinition;
$definition->setName($name);
$definition->setNotifier(function (): void {
$this->needsResolve = true;
diff --git a/src/DI/ContainerLoader.php b/src/DI/ContainerLoader.php
index 3b9acf4f9..901874d10 100644
--- a/src/DI/ContainerLoader.php
+++ b/src/DI/ContainerLoader.php
@@ -105,7 +105,7 @@ protected function generate(string $class, callable $generator): array
{
$compiler = new Compiler;
$compiler->setClassName($class);
- $code = $generator(...[&$compiler]) ?: $compiler->compile();
+ $code = $generator(...[&$compiler]) ?? $compiler->compile();
return [
"exportDependencies()),
diff --git a/src/DI/Definitions/AccessorDefinition.php b/src/DI/Definitions/AccessorDefinition.php
index 7900de605..ba49ab49c 100644
--- a/src/DI/Definitions/AccessorDefinition.php
+++ b/src/DI/Definitions/AccessorDefinition.php
@@ -108,7 +108,7 @@ public function complete(Nette\DI\Resolver $resolver): void
}
- public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
@@ -124,6 +124,6 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe
->setBody('return $this->container->getService(?);', [$this->reference->getValue()])
->setReturnType((string) Type::fromReflection($rm));
- $method->setBody('return new class ($this) ' . $class . ';');
+ return 'return new class ($this) ' . $class . ';';
}
}
diff --git a/src/DI/Definitions/Definition.php b/src/DI/Definitions/Definition.php
index 6183f0a6f..91df97f77 100644
--- a/src/DI/Definitions/Definition.php
+++ b/src/DI/Definitions/Definition.php
@@ -148,7 +148,7 @@ abstract public function resolveType(Nette\DI\Resolver $resolver): void;
abstract public function complete(Nette\DI\Resolver $resolver): void;
- abstract public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void;
+ abstract public function generateCode(Nette\DI\PhpGenerator $generator): string;
final public function setNotifier(?\Closure $notifier): void
diff --git a/src/DI/Definitions/FactoryDefinition.php b/src/DI/Definitions/FactoryDefinition.php
index 9518ab91b..2372c0325 100644
--- a/src/DI/Definitions/FactoryDefinition.php
+++ b/src/DI/Definitions/FactoryDefinition.php
@@ -197,7 +197,7 @@ public function convertArguments(array &$args): void
}
- public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
$class = (new Php\ClassType)
->addImplement($this->getType());
@@ -208,8 +208,7 @@ public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $genera
->setType($generator->getClassName());
$methodCreate = $class->addMethod(self::MethodCreate);
- $this->resultDefinition->generateMethod($methodCreate, $generator);
- $body = $methodCreate->getBody();
+ $body = $this->resultDefinition->generateCode($generator);
$body = str_replace('$this', '$this->container', $body);
$body = str_replace('$this->container->container', '$this->container', $body);
@@ -219,7 +218,7 @@ public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $genera
->setReturnType((string) Type::fromReflection($rm))
->setBody($body);
- $method->setBody('return new class ($this) ' . $class . ';');
+ return 'return new class ($this) ' . $class . ';';
}
diff --git a/src/DI/Definitions/ImportedDefinition.php b/src/DI/Definitions/ImportedDefinition.php
index e9116653b..cc8caf70e 100644
--- a/src/DI/Definitions/ImportedDefinition.php
+++ b/src/DI/Definitions/ImportedDefinition.php
@@ -10,7 +10,6 @@
namespace Nette\DI\Definitions;
use Nette;
-use Nette\DI\PhpGenerator;
/**
@@ -34,9 +33,9 @@ public function complete(Nette\DI\Resolver $resolver): void
}
- public function generateMethod(Nette\PhpGenerator\Method $method, PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
- $method->setBody(
+ return $generator->formatPhp(
'throw new Nette\DI\ServiceCreationException(?);',
["Unable to create imported service '{$this->getName()}', it must be added using addService()"],
);
diff --git a/src/DI/Definitions/LocatorDefinition.php b/src/DI/Definitions/LocatorDefinition.php
index a26296d7b..c3601d858 100644
--- a/src/DI/Definitions/LocatorDefinition.php
+++ b/src/DI/Definitions/LocatorDefinition.php
@@ -128,7 +128,7 @@ public function complete(Nette\DI\Resolver $resolver): void
}
- public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
@@ -172,6 +172,6 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe
}
}
- $method->setBody('return new class ($this) ' . $class . ';');
+ return 'return new class ($this) ' . $class . ';';
}
}
diff --git a/src/DI/Definitions/Reference.php b/src/DI/Definitions/Reference.php
index 25625dec7..b153410e0 100644
--- a/src/DI/Definitions/Reference.php
+++ b/src/DI/Definitions/Reference.php
@@ -17,11 +17,9 @@ final class Reference
{
public const Self = 'self';
- /** @deprecated use Reference::Self */
+ #[\Deprecated('use Reference::Self')]
public const SELF = self::Self;
- private string $value;
-
public static function fromType(string $value): static
{
@@ -33,9 +31,9 @@ public static function fromType(string $value): static
}
- public function __construct(string $value)
- {
- $this->value = $value;
+ public function __construct(
+ private readonly string $value,
+ ) {
}
diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php
index b41fe8574..349a213d5 100644
--- a/src/DI/Definitions/ServiceDefinition.php
+++ b/src/DI/Definitions/ServiceDefinition.php
@@ -18,9 +18,9 @@
/**
* Definition of standard service.
*
- * @property string|null $class
- * @property Statement $factory
- * @property Statement[] $setup
+ * @property-deprecated string|null $class
+ * @property-deprecated Statement $factory
+ * @property-deprecated Statement[] $setup
*/
final class ServiceDefinition extends Definition
{
@@ -59,7 +59,7 @@ public function setFactory(string|array|Definition|Reference|Statement $factory,
*/
public function getFactory(): Statement
{
- return $this->getCreator();
+ return $this->creator;
}
@@ -169,7 +169,7 @@ public function complete(Nette\DI\Resolver $resolver): void
$this->creator = $resolver->completeStatement($this->creator);
foreach ($this->setup as &$setup) {
- $setup = $resolver->completeStatement($setup, true);
+ $setup = $resolver->completeStatement($setup, currentServiceAllowed: true);
}
}
@@ -182,7 +182,7 @@ private function prependSelf(Statement $setup): Statement
}
- public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
$lines = [];
foreach ([$this->creator, ...$this->setup] as $stmt) {
@@ -194,15 +194,15 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe
$lines[0] = (new \ReflectionClass($class))->hasMethod('__construct')
? $generator->formatPhp("\$service->__construct(...?:);\n", [$this->creator->arguments])
: '';
- $method->setBody("return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n"
+ return "return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n"
. Strings::indent(implode('', $lines))
- . '});');
+ . '});';
} elseif (count($lines) === 1) {
- $method->setBody('return ' . $lines[0]);
+ return 'return ' . $lines[0];
} else {
- $method->setBody('$service = ' . implode('', $lines) . 'return $service;');
+ return '$service = ' . implode('', $lines) . 'return $service;';
}
}
@@ -224,6 +224,3 @@ public function __clone()
$this->setup = unserialize(serialize($this->setup));
}
}
-
-
-class_exists(Nette\DI\ServiceDefinition::class);
diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php
index 27aebe3da..a4000ce0e 100644
--- a/src/DI/Definitions/Statement.php
+++ b/src/DI/Definitions/Statement.php
@@ -16,18 +16,16 @@
/**
* Assignment or calling statement.
*
- * @property string|array|Definition|Reference|null $entity
+ * @property-deprecated string|array|Definition|Reference|null $entity
*/
final class Statement implements Nette\Schema\DynamicParameter
{
use Nette\SmartObject;
- public array $arguments;
- private string|array|Definition|Reference|null $entity;
-
-
- public function __construct(string|array|Definition|Reference|null $entity, array $arguments = [])
- {
+ public function __construct(
+ private string|array|Definition|Reference|null $entity,
+ public array $arguments = [],
+ ) {
if (
$entity !== null
&& !is_string($entity) // Class, @service, not, tags, types, PHP literal, entity::member
@@ -55,7 +53,6 @@ public function __construct(string|array|Definition|Reference|null $entity, arra
}
$this->entity = $entity;
- $this->arguments = $arguments;
}
@@ -64,6 +61,3 @@ public function getEntity(): string|array|Definition|Reference|null
return $this->entity;
}
}
-
-
-class_exists(Nette\DI\Statement::class);
diff --git a/src/DI/DependencyChecker.php b/src/DI/DependencyChecker.php
index b58cb52e4..e9bd1424f 100644
--- a/src/DI/DependencyChecker.php
+++ b/src/DI/DependencyChecker.php
@@ -24,7 +24,7 @@ class DependencyChecker
{
public const Version = 1;
- /** @deprecated use DependencyChecker::Version */
+ #[\Deprecated('use DependencyChecker::Version')]
public const VERSION = self::Version;
/** @var array