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); - true, Dumper::LIVE => true, Dumper::DEPTH => 5]) ?> + true, Dumper::LIVE => true, Dumper::DEPTH => 5, Dumper::KEYS_TO_HIDE => $keysToHide]) ?> 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 @@ {if isset($instances[$name]) && !$instances[$name] instanceof Nette\DI\Container} - {Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5])} + {Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5, Dumper::KEYS_TO_HIDE => $keysToHide])} {elseif isset($instances[$name])} {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 */ @@ -72,8 +72,8 @@ public function export(): array $classes = array_keys($classes); $functions = array_unique($functions, SORT_REGULAR); $hash = self::calculateHash($classes, $functions); - $files = @array_map('filemtime', array_combine($files, $files)); // @ - file may not exist - $phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles)); // @ - file may not exist + $files = @array_map(filemtime(...), array_combine($files, $files)); // @ - file may not exist + $phpFiles = @array_map(filemtime(...), array_combine($phpFiles, $phpFiles)); // @ - file may not exist return [self::Version, $files, $phpFiles, $classes, $functions, $hash]; } @@ -91,9 +91,9 @@ public static function isExpired( ): bool { try { - $currentFiles = @array_map('filemtime', array_combine($tmp = array_keys($files), $tmp)); // @ - files may not exist + $currentFiles = @array_map(filemtime(...), array_combine($tmp = array_keys($files), $tmp)); // @ - files may not exist $origPhpFiles = $phpFiles; - $phpFiles = @array_map('filemtime', array_combine($tmp = array_keys($phpFiles), $tmp)); // @ - files may not exist + $phpFiles = @array_map(filemtime(...), array_combine($tmp = array_keys($phpFiles), $tmp)); // @ - files may not exist return $version !== self::Version || $files !== $currentFiles || ($phpFiles !== $origPhpFiles && $hash !== self::calculateHash($classes, $functions)); diff --git a/src/DI/Extensions/DIExtension.php b/src/DI/Extensions/DIExtension.php index 0bcf76948..8bdd33d85 100644 --- a/src/DI/Extensions/DIExtension.php +++ b/src/DI/Extensions/DIExtension.php @@ -30,7 +30,7 @@ final class DIExtension extends Nette\DI\CompilerExtension public function __construct(bool $debugMode = false) { $this->debugMode = $debugMode; - $this->time = microtime(true); + $this->time = microtime(as_float: true); $this->config = new class { public ?bool $debugger = null; diff --git a/src/DI/Extensions/DecoratorExtension.php b/src/DI/Extensions/DecoratorExtension.php index 00c2596bc..3ec91f624 100644 --- a/src/DI/Extensions/DecoratorExtension.php +++ b/src/DI/Extensions/DecoratorExtension.php @@ -81,7 +81,7 @@ private function findByType(string $type): array { return array_filter( $this->getContainerBuilder()->getDefinitions(), - fn(Definitions\Definition $def): bool => is_a($def->getType(), $type, true) + fn(Definitions\Definition $def): bool => is_a($def->getType(), $type, allow_string: true) || ($def instanceof Definitions\FactoryDefinition && is_a($def->getResultType(), $type, allow_string: true)), ); } diff --git a/src/DI/Extensions/DefinitionSchema.php b/src/DI/Extensions/DefinitionSchema.php index 97a16166a..a334230c1 100644 --- a/src/DI/Extensions/DefinitionSchema.php +++ b/src/DI/Extensions/DefinitionSchema.php @@ -24,12 +24,9 @@ */ class DefinitionSchema implements Schema { - private Nette\DI\ContainerBuilder $builder; - - - public function __construct(Nette\DI\ContainerBuilder $builder) - { - $this->builder = $builder; + public function __construct( + private readonly Nette\DI\ContainerBuilder $builder, + ) { } @@ -50,7 +47,7 @@ public function complete($def, Context $context) } $type = $this->sniffType(end($context->path), $def); - $def = $this->getSchema($type)->complete($def, $context); + $def = self::getSchema($type)->complete($def, $context); if ($def) { $def->defType = $type; } @@ -124,7 +121,7 @@ private function sniffType($key, array $def): string : $key; if ($name && $this->builder->hasDefinition($name)) { - return get_class($this->builder->getDefinition($name)); + return $this->builder->getDefinition($name)::class; } } @@ -151,7 +148,7 @@ private function sniffType($key, array $def): string private static function getSchema(string $type): Schema { static $cache; - $cache = $cache ?: [ + $cache ??= [ Definitions\ServiceDefinition::class => self::getServiceSchema(), Definitions\AccessorDefinition::class => self::getAccessorSchema(), Definitions\FactoryDefinition::class => self::getFactorySchema(), diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index 278f1019a..789428c5b 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -17,13 +17,13 @@ /** - * Calls inject methods and fills @inject properties. + * Calls inject methods and fills #[Inject] properties. */ final class InjectExtension extends DI\CompilerExtension { public const TagInject = 'nette.inject'; - /** @deprecated use InjectExtension::TagInject */ + #[\Deprecated('use InjectExtension::TagInject')] public const TAG_INJECT = self::TagInject; @@ -111,26 +111,19 @@ public static function getInjectMethods(string $class): array /** - * Generates list of properties with annotation @inject. + * Generates list of properties with attribute #[Inject]. * @internal */ public static function getInjectProperties(string $class): array { $res = []; foreach ((new \ReflectionClass($class))->getProperties() as $rp) { - $hasAttr = $rp->getAttributes(DI\Attributes\Inject::class); - if ($hasAttr || DI\Helpers::parseAnnotation($rp, 'inject') !== null) { + if ($rp->getAttributes(DI\Attributes\Inject::class)) { if (!$rp->isPublic() || $rp->isStatic() || $rp->isReadOnly()) { throw new Nette\InvalidStateException(sprintf('Property %s for injection must not be static, readonly and must be public.', Reflection::toString($rp))); } - $type = Nette\Utils\Type::fromReflection($rp); - if (!$type && !$hasAttr && ($annotation = DI\Helpers::parseAnnotation($rp, 'var'))) { - $annotation = Reflection::expandClassName($annotation, Reflection::getPropertyDeclaringClass($rp)); - $type = Nette\Utils\Type::fromString($annotation); - } - - $res[$rp->getName()] = DI\Helpers::ensureClassType($type, 'type of property ' . Reflection::toString($rp)); + $res[$rp->getName()] = DI\Helpers::ensureClassType(Nette\Utils\Type::fromReflection($rp), 'type of property ' . Reflection::toString($rp)); } } diff --git a/src/DI/Extensions/ParametersExtension.php b/src/DI/Extensions/ParametersExtension.php index 1d2f3365d..f42826ff5 100644 --- a/src/DI/Extensions/ParametersExtension.php +++ b/src/DI/Extensions/ParametersExtension.php @@ -39,7 +39,7 @@ public function loadConfiguration(): void $builder = $this->getContainerBuilder(); $params = $this->config; foreach ($this->dynamicParams as $key) { - $params[$key] = new DynamicParameter('$this->getParameter(' . var_export($key, true) . ')'); + $params[$key] = new DynamicParameter('$this->getParameter(' . var_export($key, return: true) . ')'); } $builder->parameters = Helpers::expand($params, $params, recursive: true); @@ -85,7 +85,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void } $method->addBody("\tdefault => parent::getDynamicParameter(\$key),\n};"); - if ($preload = array_keys($dynamicParams, true, true)) { + if ($preload = array_keys($dynamicParams, filter_value: true, strict: true)) { $method = $manipulator->inheritMethod('getParameters'); $method->addBody('array_map($this->getParameter(...), ?);', [$preload]); $method->addBody('return parent::getParameters();'); diff --git a/src/DI/Extensions/SearchExtension.php b/src/DI/Extensions/SearchExtension.php index 4739b3777..72674fc91 100644 --- a/src/DI/Extensions/SearchExtension.php +++ b/src/DI/Extensions/SearchExtension.php @@ -22,12 +22,11 @@ final class SearchExtension extends Nette\DI\CompilerExtension { private array $classes = []; - private string $tempDir; - public function __construct(string $tempDir) - { - $this->tempDir = $tempDir; + public function __construct( + private readonly string $tempDir, + ) { } @@ -105,7 +104,7 @@ public function findClasses(\stdClass $config): array || ($rc->isInterface() && count($methods = $rc->getMethods()) === 1 - && in_array($methods[0]->name, ['get', 'create'], true)) + && in_array($methods[0]->name, ['get', 'create'], strict: true)) ) && (!$acceptRE || preg_match($acceptRE, $rc->name)) && (!$rejectRE || !preg_match($rejectRE, $rc->name)) diff --git a/src/DI/Extensions/ServicesExtension.php b/src/DI/Extensions/ServicesExtension.php index dc69ec4a7..0054e3ece 100644 --- a/src/DI/Extensions/ServicesExtension.php +++ b/src/DI/Extensions/ServicesExtension.php @@ -235,7 +235,7 @@ private function updateDefinition(Definitions\Definition $definition, \stdClass } - private function convertKeyToName($key): ?string + private function convertKeyToName(int|string $key): ?string { if (is_int($key)) { return null; diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index 51bcda464..e015bb6f4 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -45,12 +45,6 @@ public static function expand(mixed $var, array $params, bool|array $recursive = self::expand($var->arguments, $params, $recursive), ); - } elseif ($var === '%parameters%' && !array_key_exists('parameters', $params)) { - trigger_error('%parameters% is deprecated, use @container::getParameters()', E_USER_DEPRECATED); - return $recursive - ? self::expand($params, $params, $recursive) - : $params; - } elseif (is_string($var)) { $recursive = is_array($recursive) ? $recursive : ($recursive ? [] : null); return self::expandString($var, $params, $recursive); @@ -119,10 +113,10 @@ private static function expandParameter( } $val = $fullExpand ? self::expand($val, $params, $recursive + [$pathStr => 1]) - : self::expandString($val, $params, $recursive + [$pathStr => 1], true); + : self::expandString($val, $params, $recursive + [$pathStr => 1], onlyString: true); } } elseif ($val instanceof DynamicParameter) { - $val = new DynamicParameter($val . '[' . var_export($key, true) . ']'); + $val = new DynamicParameter($val . '[' . var_export($key, return: true) . ']'); } elseif ($val instanceof Statement) { $val = new Statement('(?)[?]', [$val, $key]); } else { @@ -202,38 +196,6 @@ public static function prefixServiceName(mixed $config, string $namespace): mixe } - /** - * Returns an annotation value. - */ - public static function parseAnnotation(\Reflector $ref, string $name): ?string - { - if (!Reflection::areCommentsAvailable()) { - throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.'); - } - - $re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#'; - if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) { - return $m[1] ?? ''; - } - - return null; - } - - - public static function getReturnTypeAnnotation(\ReflectionFunctionAbstract $func): ?Type - { - $type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return')); - if (!$type || $type === 'object' || $type === 'mixed') { - return null; - } elseif ($func instanceof \ReflectionMethod) { - $type = $type === '$this' ? 'static' : $type; - $type = Reflection::expandClassName($type, $func->getDeclaringClass()); - } - - return Type::fromString($type); - } - - public static function ensureClassType(?Type $type, string $hint, bool $allowNullable = false): string { if (!$type) { diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index 8c02cb236..0bcda0004 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -21,13 +21,12 @@ */ class PhpGenerator { - private ContainerBuilder $builder; private ?string $className = null; - public function __construct(ContainerBuilder $builder) - { - $this->builder = $builder; + public function __construct( + private readonly ContainerBuilder $builder, + ) { } @@ -95,7 +94,7 @@ public function generateMethod(Definitions\Definition $def): Php\Method $method = new Php\Method(Container::getMethodName($name)); $method->setPublic(); $method->setReturnType($def->getType()); - $def->generateMethod($method, $this); + $method->setBody($def->generateCode($this)); return $method; } catch (\Throwable $e) { diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index a540d2ae1..51a99c4c4 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -27,7 +27,6 @@ */ class Resolver { - private ContainerBuilder $builder; private ?Definition $currentService = null; private ?string $currentServiceType = null; private bool $currentServiceAllowed = false; @@ -36,9 +35,9 @@ class Resolver private \SplObjectStorage $recursive; - public function __construct(ContainerBuilder $builder) - { - $this->builder = $builder; + public function __construct( + private readonly ContainerBuilder $builder, + ) { $this->recursive = new \SplObjectStorage; } @@ -123,20 +122,13 @@ public function resolveEntityType(Statement $statement): ?string $this->addDependency($reflection); - $type = Nette\Utils\Type::fromReflection($reflection) ?? ($annotation = Helpers::getReturnTypeAnnotation($reflection)); - if ($type && !in_array($type->getSingleName(), ['object', 'mixed'], strict: true)) { - if (isset($annotation)) { - trigger_error('Annotation @return should be replaced with native return type at ' . Callback::toString($entity), E_USER_DEPRECATED); - } - - return Helpers::ensureClassType( + return ($type = Nette\Utils\Type::fromReflection($reflection)) && !in_array($type->getSingleName(), ['object', 'mixed'], strict: true) + ? Helpers::ensureClassType( $type, sprintf('return type of %s()', Callback::toString($entity)), allowNullable: true, - ); - } - - return null; + ) + : null; } elseif ($entity instanceof Reference) { // alias or factory return $this->resolveReferenceType($entity); @@ -243,8 +235,7 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo case $entity instanceof Reference: if ($arguments) { - $e = $this->completeException(new ServiceCreationException(sprintf('Parameters were passed to reference @%s, although references cannot have any parameters.', $entity->getValue())), $this->currentService); - trigger_error($e->getMessage(), E_USER_DEPRECATED); + throw $this->completeException(new ServiceCreationException(sprintf('Parameters were passed to reference @%s, although references cannot have any parameters.', $entity->getValue())), $this->currentService); } $entity = [new Reference(ContainerBuilder::ThisContainer), Container::getMethodName($entity->getValue())]; break; diff --git a/src/compatibility.php b/src/compatibility.php deleted file mode 100644 index f0a0e53fe..000000000 --- a/src/compatibility.php +++ /dev/null @@ -1,39 +0,0 @@ -loadConfig('files/closure.config.php'); +$code = $compiler->compile(); + +Assert::contains('closureService', $code); diff --git a/tests/DI/Container.inject.properties.phpt b/tests/DI/Container.inject.properties.phpt index b633c0f8b..73324665c 100644 --- a/tests/DI/Container.inject.properties.phpt +++ b/tests/DI/Container.inject.properties.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\DI; +use Nette\DI\Attributes\Inject; use Tester\Assert; @@ -23,20 +24,17 @@ class Foo implements IFoo class Test1 { - /** @inject @var stdClass */ - public $varA; - - /** @var stdClass @inject */ - public $varB; + #[Inject] + public stdClass $varA; } class Test2 extends Test1 { - /** @var stdClass @inject */ - public $varC; + #[Inject] + public stdClass $varC; - /** @var IFoo @inject */ - public $varD; + #[Inject] + public IFoo $varD; } @@ -52,6 +50,5 @@ $container = createContainer($builder); $test = new Test2; $container->callInjects($test); Assert::type(stdClass::class, $test->varA); -Assert::type(stdClass::class, $test->varB); Assert::type(stdClass::class, $test->varC); Assert::type(Foo::class, $test->varD); diff --git a/tests/DI/ContainerBuilder.resolveTypes.next.phpt b/tests/DI/ContainerBuilder.resolveTypes.next.phpt index f19770352..c1b4d354a 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.next.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.next.phpt @@ -24,52 +24,24 @@ class Lorem class Factory { - /** @return Lorem */ - public function createClassPhpDoc() - { - return []; - } - - public function createClass(): Lorem { return []; } - /** @return Lorem|null */ - public function createNullableClassPhpDoc() - { - return []; - } - - public function createNullableClass(): ?Lorem { return []; } - /** @return array */ - public function createScalarPhpDoc() - { - return []; - } - - public function createScalar(): array { return []; } - /** @return object */ - public function createObjectPhpDoc() - { - return (object) null; - } - - public function createObject(): object { return (object) null; @@ -82,29 +54,12 @@ class Factory } - /** @return mixed */ - public function createMixedPhpDoc() - { - return (object) null; - } - - public function createMixed(): mixed { return (object) null; } - /** - * @template T - * @return T - */ - public function createGeneric() - { - return (object) null; - } - - public function createUnion(): stdClass|array { return []; @@ -115,13 +70,6 @@ class Factory require __DIR__ . '/../bootstrap.php'; -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createClassPhpDoc']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -129,13 +77,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createNullableClassPhpDoc']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -143,13 +84,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createScalarPhpDoc']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -157,13 +91,6 @@ Assert::exception(function () { $container = createContainer($builder); }, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createObjectPhpDoc']), 'next']); - $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -178,13 +105,6 @@ Assert::exception(function () { $container = createContainer($builder); }, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createMixedPhpDoc']), 'next']); - $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -192,14 +112,6 @@ Assert::exception(function () { $container = createContainer($builder); }, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createGeneric']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Class 'T' not found. -Check the return type of Factory::createGeneric()."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') diff --git a/tests/DI/ContainerBuilder.resolveTypes.phpt b/tests/DI/ContainerBuilder.resolveTypes.phpt index 67cb3ea47..424fd12d5 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.phpt @@ -15,52 +15,24 @@ use Tester\Assert; class Factory { - /** @return stdClass */ - public function createClassPhpDoc() - { - return []; - } - - public function createClass(): stdClass { return []; } - /** @return stdClass|null */ - public function createNullableClassPhpDoc() - { - return []; - } - - public function createNullableClass(): ?stdClass { return []; } - /** @return array */ - public function createScalarPhpDoc() - { - return []; - } - - public function createScalar(): array { return []; } - /** @return object */ - public function createObjectPhpDoc() - { - return (object) null; - } - - public function createObject(): object { return (object) null; @@ -73,29 +45,12 @@ class Factory } - /** @return mixed */ - public function createMixedPhpDoc() - { - return (object) null; - } - - public function createMixed(): mixed { return (object) null; } - /** - * @template T - * @return T - */ - public function createGeneric() - { - return (object) null; - } - - public function createUnion(): stdClass|array { return []; @@ -106,13 +61,6 @@ class Factory require __DIR__ . '/../bootstrap.php'; -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createClassPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -120,13 +68,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createNullableClassPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -134,13 +75,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createScalarPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -148,13 +82,6 @@ Assert::exception(function () { $container = createContainer($builder); }, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createObjectPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -169,13 +96,6 @@ Assert::exception(function () { $container = createContainer($builder); }, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createMixedPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -183,14 +103,6 @@ Assert::exception(function () { $container = createContainer($builder); }, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createGeneric']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Class 'T' not found. -Check the return type of Factory::createGeneric()."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') diff --git a/tests/DI/Helpers.expand().phpt b/tests/DI/Helpers.expand().phpt index bf7f36ef1..586ea1f34 100644 --- a/tests/DI/Helpers.expand().phpt +++ b/tests/DI/Helpers.expand().phpt @@ -87,17 +87,3 @@ Assert::exception( Nette\InvalidArgumentException::class, 'Circular reference detected for parameters: %exp%, %array.a%, %array%', ); - - -Assert::same( - ['key1' => 'hello', 'key2' => '*%key1%*'], - @Helpers::expand('%parameters%', ['key1' => 'hello', 'key2' => '*%key1%*']), // deprecated -); -Assert::same( - ['key1' => 'hello', 'key2' => '*hello*'], - @Helpers::expand('%parameters%', ['key1' => 'hello', 'key2' => '*%key1%*'], recursive: true), // deprecated -); -Assert::same( - 'own', - @Helpers::expand('%parameters%', ['key1' => 'hello', 'key2' => '*%key1%*', 'parameters' => 'own']), // deprecated -); diff --git a/tests/DI/Helpers.getReturnType.phpt b/tests/DI/Helpers.getReturnType.phpt deleted file mode 100644 index 9a0a47cbf..000000000 --- a/tests/DI/Helpers.getReturnType.phpt +++ /dev/null @@ -1,30 +0,0 @@ - AInjected::class, + 'varB' => BInjected::class, + 'varF' => stdClass::class, +], InjectExtension::getInjectProperties(AClass::class)); -namespace { - use Nette\DI\Extensions\InjectExtension; - use Tester\Assert; - - require __DIR__ . '/../bootstrap.php'; - - - Assert::same([ - 'varA' => A\AInjected::class, - 'varB' => A\B\BInjected::class, - 'varC' => A\AInjected::class, - ], InjectExtension::getInjectProperties(A\AClass::class)); - - Assert::same([ - 'varA' => A\AInjected::class, - 'varB' => A\B\BInjected::class, - 'varC' => A\AInjected::class, - 'varF' => A\B\BInjected::class, - ], InjectExtension::getInjectProperties(A\B\BClass::class)); - - Assert::same([ - 'var1' => A\AInjected::class, - 'var2' => A\B\BInjected::class, - 'var3' => C\CInjected::class, - 'var4' => C\CInjected::class, - ], InjectExtension::getInjectProperties(C\CClass::class)); - - Assert::exception( - fn() => InjectExtension::getInjectProperties(A\BadClass::class), - Nette\InvalidStateException::class, - "Type of property A\\BadClass::\$var is expected to not be nullable/built-in/complex, 'A\\AClass|stdClass' given.", - ); -} +Assert::exception( + fn() => InjectExtension::getInjectProperties(BadClass::class), + Nette\InvalidStateException::class, + "Type of property BadClass::\$var is expected to not be nullable/built-in/complex, 'AClass|stdClass' given.", +); diff --git a/tests/DI/InjectExtension.getInjectProperties().traits.phpt b/tests/DI/InjectExtension.getInjectProperties().traits.phpt index 1fc6c211e..ba4022d49 100644 --- a/tests/DI/InjectExtension.getInjectProperties().traits.phpt +++ b/tests/DI/InjectExtension.getInjectProperties().traits.phpt @@ -16,11 +16,12 @@ namespace A namespace B { use A\AInjected; + use Nette\DI\Attributes\Inject; trait BTrait { - /** @var AInjected @inject */ - public $varA; + #[Inject] + public AInjected $varA; } } diff --git a/tests/DI/NeonAdapter.preprocess.phpt b/tests/DI/NeonAdapter.preprocess.phpt index 7b59be016..350dbd259 100644 --- a/tests/DI/NeonAdapter.preprocess.phpt +++ b/tests/DI/NeonAdapter.preprocess.phpt @@ -70,17 +70,6 @@ Assert::equal( ); -// ... deprecated -$data = @$adapter->load(Tester\FileMock::create(' -- Class(arg1, ..., [...]) -', 'neon')); - -Assert::equal( - [new Statement('Class', ['arg1', 2 => ['...']])], - $data, -); - - // @ escaping $data = @$adapter->load(Tester\FileMock::create(' - @@double diff --git a/tests/DI/files/closure.config.php b/tests/DI/files/closure.config.php new file mode 100644 index 000000000..9d18830b9 --- /dev/null +++ b/tests/DI/files/closure.config.php @@ -0,0 +1,8 @@ +addDefinition('closureService') + ->setType(stdClass::class); +}; diff --git a/tests/DI/fixtures/Helpers.getReturnType.php b/tests/DI/fixtures/Helpers.getReturnType.php deleted file mode 100644 index 93bb93e5b..000000000 --- a/tests/DI/fixtures/Helpers.getReturnType.php +++ /dev/null @@ -1,49 +0,0 @@ -