From 5fb5027e57efb28a304f238a33893ccbd338a43f Mon Sep 17 00:00:00 2001 From: Contributte AI Date: Fri, 2 Jan 2026 19:06:48 +0000 Subject: [PATCH 1/3] Composer: Downgrade symfony/var-exporter to ^7.0 Required for Doctrine ORM 3.x proxy factory compatibility. --- composer.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index c3f9fddcd..ee4a2bf7f 100644 --- a/composer.json +++ b/composer.json @@ -23,10 +23,11 @@ "contributte/phpstan": "^0.2.0", "contributte/qa": "^0.4", "contributte/tester": "^0.3.0", - "symfony/console": "^7.1.8 ", - "symfony/cache": "^7.1.9", - "monolog/monolog": "^3.8.0", "mockery/mockery": "^1.6.12", + "monolog/monolog": "^3.8.0", + "symfony/cache": "^7.1.9", + "symfony/console": "^7.1.8 ", + "symfony/var-exporter": "^7.0", "tracy/tracy": "^2.10.3" }, "conflict": { From 6a10db7cad6e2503c7afeccf7d81b9448f482c33 Mon Sep 17 00:00:00 2001 From: Contributte AI Date: Fri, 2 Jan 2026 19:07:01 +0000 Subject: [PATCH 2/3] Tests: Add comprehensive test coverage New test files: - ManagerProvider: EntityManagerProvider interface tests - ContainerEntityListenerResolver: Entity listener resolution tests - Binder: Closure binding utility tests - SmartStatement: Statement conversion tests - BuilderMan: DI helper tests - OrmExtension.console: Console commands registration tests - OrmExtension.customFunctions: Custom DQL functions tests - OrmExtension.errors: Error handling and validation tests - OrmExtension.multipleManagers: Multi-manager configuration tests Extended existing tests: - ContainerEventManager: hasListeners, getListeners, null args, multiple events - ManagerRegistry: getConnectionNames, getManagerNames, reopen, getRepository - MappingHelper: XML validation, error handling, fluent interface New mock classes: - DummyEntityListener, DummyHydrator, DummyStringFunction --- tests/Cases/DI/Helper/BuilderMan.phpt | 343 +++++++++++++++++ tests/Cases/DI/Helper/MappingHelper.phpt | 125 ++++++ tests/Cases/DI/Helper/SmartStatement.phpt | 55 +++ tests/Cases/DI/OrmExtension.console.phpt | 172 +++++++++ .../DI/OrmExtension.customFunctions.phpt | 240 ++++++++++++ tests/Cases/DI/OrmExtension.errors.phpt | 277 ++++++++++++++ .../DI/OrmExtension.multipleManagers.phpt | 345 +++++++++++++++++ tests/Cases/Events/ContainerEventManager.phpt | 132 +++++++ tests/Cases/ManagerProvider.phpt | 153 ++++++++ tests/Cases/ManagerRegistry.phpt | 355 ++++++++++++++++++ .../ContainerEntityListenerResolver.phpt | 118 ++++++ tests/Cases/Utils/Binder.phpt | 87 +++++ tests/Mocks/DummyEntityListener.php | 18 + tests/Mocks/DummyHydrator.php | 18 + tests/Mocks/DummyStringFunction.php | 22 ++ 15 files changed, 2460 insertions(+) create mode 100644 tests/Cases/DI/Helper/BuilderMan.phpt create mode 100644 tests/Cases/DI/Helper/SmartStatement.phpt create mode 100644 tests/Cases/DI/OrmExtension.console.phpt create mode 100644 tests/Cases/DI/OrmExtension.customFunctions.phpt create mode 100644 tests/Cases/DI/OrmExtension.errors.phpt create mode 100644 tests/Cases/DI/OrmExtension.multipleManagers.phpt create mode 100644 tests/Cases/ManagerProvider.phpt create mode 100644 tests/Cases/Mapping/ContainerEntityListenerResolver.phpt create mode 100644 tests/Cases/Utils/Binder.phpt create mode 100644 tests/Mocks/DummyEntityListener.php create mode 100644 tests/Mocks/DummyHydrator.php create mode 100644 tests/Mocks/DummyStringFunction.php diff --git a/tests/Cases/DI/Helper/BuilderMan.phpt b/tests/Cases/DI/Helper/BuilderMan.phpt new file mode 100644 index 000000000..153d7e79d --- /dev/null +++ b/tests/Cases/DI/Helper/BuilderMan.phpt @@ -0,0 +1,343 @@ +withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('test', new class extends CompilerExtension { + + public function beforeCompile(): void + { + $pass = new class ($this) extends AbstractPass { + }; + + $builderMan = BuilderMan::of($pass); + $connection = $builderMan->getConnectionByName('default'); + + Assert::type(ServiceDefinition::class, $connection); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); +}); + +// Get connection by name - not found +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('test', new class extends CompilerExtension { + + public function beforeCompile(): void + { + $pass = new class ($this) extends AbstractPass { + }; + + $builderMan = BuilderMan::of($pass); + $builderMan->getConnectionByName('nonexistent'); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, LogicalException::class, 'Connection "nonexistent" not found'); +}); + +// Get connections map +Toolkit::test(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('test', new class extends CompilerExtension { + + public function beforeCompile(): void + { + $pass = new class ($this) extends AbstractPass { + }; + + $builderMan = BuilderMan::of($pass); + $map = $builderMan->getConnectionsMap(); + + Assert::count(2, $map); + Assert::true(isset($map['default'])); + Assert::true(isset($map['second'])); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); +}); + +// Get managers map +Toolkit::test(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('test', new class extends CompilerExtension { + + public function beforeCompile(): void + { + $pass = new class ($this) extends AbstractPass { + }; + + $builderMan = BuilderMan::of($pass); + $map = $builderMan->getManagersMap(); + + Assert::count(2, $map); + Assert::true(isset($map['default'])); + Assert::true(isset($map['second'])); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); +}); + +// Get managers map with decorator +Toolkit::test(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('test', new class extends CompilerExtension { + + public function beforeCompile(): void + { + $pass = new class ($this) extends AbstractPass { + }; + + $builderMan = BuilderMan::of($pass); + $map = $builderMan->getManagersMap(); + + // With decorator, the decorator replaces the manager in the map + Assert::count(1, $map); + Assert::true(isset($map['default'])); + Assert::contains('entityManagerDecorator', $map['default']); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + entityManagerDecoratorClass: Tests\Mocks\DummyEntityManagerDecorator + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); +}); + +// Get all connections +Toolkit::test(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('test', new class extends CompilerExtension { + + public function beforeCompile(): void + { + $pass = new class ($this) extends AbstractPass { + }; + + $builderMan = BuilderMan::of($pass); + $connections = $builderMan->getConnections(); + + Assert::count(2, $connections); + Assert::type(ServiceDefinition::class, $connections['default']); + Assert::type(ServiceDefinition::class, $connections['second']); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); +}); diff --git a/tests/Cases/DI/Helper/MappingHelper.phpt b/tests/Cases/DI/Helper/MappingHelper.phpt index 7600a7f06..029746a6e 100644 --- a/tests/Cases/DI/Helper/MappingHelper.phpt +++ b/tests/Cases/DI/Helper/MappingHelper.phpt @@ -74,3 +74,128 @@ Toolkit::test(function (): void { Assert::count(3, $driver->getDrivers()); Assert::equal([DummyEntity::class], $driver->getAllClassNames()); }); + +// Validate path for XML +Toolkit::test(function (): void { + Assert::exception(function (): void { + MappingHelper::of(new DummyExtension())->addXml('default', 'fake', 'invalid'); + }, LogicalException::class, 'Given mapping path "invalid" does not exist'); +}); + +// No mapping driver found +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('custom', new class extends CompilerExtension { + + public function beforeCompile(): void + { + // Try to add mapping without ORM extension + MappingHelper::of($this)->addAttribute('default', 'App\Database', Tests::FIXTURES_PATH); + } + + }); + }) + ->build(); + }, LogicalException::class, 'No mapping driver found'); +}); + +// No mapping driver found for connection +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('custom', new class extends CompilerExtension { + + public function beforeCompile(): void + { + // Try to add mapping to non-existent connection + MappingHelper::of($this)->addAttribute('nonexistent', 'App\Database', Tests::FIXTURES_PATH); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, LogicalException::class, 'No mapping driver found for connection "nonexistent"'); +}); + +// Fluent interface - chaining +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addExtension('custom', new class extends CompilerExtension { + + public function beforeCompile(): void + { + $helper = MappingHelper::of($this); + + // Test that chaining returns the same instance + $result = $helper + ->addAttribute('default', 'App2\Database', Tests::FIXTURES_PATH . '/../Mocks') + ->addAttribute('default', 'App3\Database', Tests::FIXTURES_PATH . '/../Toolkit'); + + Assert::same($helper, $result); + } + + }); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + 'fixturesDir' => Tests::FIXTURES_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [%fixturesDir%/Entity] + namespace: Tests\Mocks + NEON + )); + }) + ->build(); + + Assert::true(true); +}); diff --git a/tests/Cases/DI/Helper/SmartStatement.phpt b/tests/Cases/DI/Helper/SmartStatement.phpt new file mode 100644 index 000000000..eab59cd72 --- /dev/null +++ b/tests/Cases/DI/Helper/SmartStatement.phpt @@ -0,0 +1,55 @@ +getEntity()); +}); + +// Return Statement as-is +Toolkit::test(function (): void { + $statement = new Statement('SomeClass', ['arg1', 'arg2']); + + $result = SmartStatement::from($statement); + + Assert::same($statement, $result); + Assert::equal(['arg1', 'arg2'], $result->arguments); +}); + +// Throw exception for invalid type - integer +Toolkit::test(function (): void { + Assert::exception(function (): void { + SmartStatement::from(123); + }, LogicalException::class, 'Unsupported type of service'); +}); + +// Throw exception for invalid type - array +Toolkit::test(function (): void { + Assert::exception(function (): void { + SmartStatement::from(['invalid']); + }, LogicalException::class, 'Unsupported type of service'); +}); + +// Throw exception for invalid type - null +Toolkit::test(function (): void { + Assert::exception(function (): void { + SmartStatement::from(null); + }, LogicalException::class, 'Unsupported type of service'); +}); + +// Throw exception for invalid type - object +Toolkit::test(function (): void { + Assert::exception(function (): void { + SmartStatement::from(new stdClass()); + }, LogicalException::class, 'Unsupported type of service'); +}); diff --git a/tests/Cases/DI/OrmExtension.console.phpt b/tests/Cases/DI/OrmExtension.console.phpt new file mode 100644 index 000000000..59459f857 --- /dev/null +++ b/tests/Cases/DI/OrmExtension.console.phpt @@ -0,0 +1,172 @@ +withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + // Schema tool commands + Assert::type(CreateCommand::class, $container->getService('nettrine.orm.schemaToolCreateCommand')); + Assert::type(UpdateCommand::class, $container->getService('nettrine.orm.schemaToolUpdateCommand')); + Assert::type(DropCommand::class, $container->getService('nettrine.orm.schemaToolDropCommand')); + + // Proxy command + Assert::type(GenerateProxiesCommand::class, $container->getService('nettrine.orm.generateProxiesCommand')); + + // Info commands + Assert::type(InfoCommand::class, $container->getService('nettrine.orm.infoCommand')); + Assert::type(MappingDescribeCommand::class, $container->getService('nettrine.orm.mappingDescribeCommand')); + + // DQL command + Assert::type(RunDqlCommand::class, $container->getService('nettrine.orm.runDqlCommand')); + + // Validation command + Assert::type(ValidateSchemaCommand::class, $container->getService('nettrine.orm.validateSchemaCommand')); + + // Cache command + Assert::type(MetadataCommand::class, $container->getService('nettrine.orm.clearMetadataCacheCommand')); +}); + +// Console commands have correct tags +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + $expectedCommands = [ + 'orm:schema-tool:create', + 'orm:schema-tool:update', + 'orm:schema-tool:drop', + 'orm:generate-proxies', + 'orm:info', + 'orm:mapping:describe', + 'orm:run-dql', + 'orm:validate-schema', + 'orm:clear-cache:metadata', + ]; + + $taggedServices = $container->findByTag('console.command'); + + foreach ($expectedCommands as $commandName) { + Assert::true(in_array($commandName, $taggedServices, true), "Command '$commandName' should be tagged"); + } +}); + +// Console commands count +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + $ormCommands = array_filter( + $container->findByTag('console.command'), + fn ($tag) => str_starts_with((string) $tag, 'orm:') + ); + + Assert::count(9, $ormCommands); +}); diff --git a/tests/Cases/DI/OrmExtension.customFunctions.phpt b/tests/Cases/DI/OrmExtension.customFunctions.phpt new file mode 100644 index 000000000..626b1227d --- /dev/null +++ b/tests/Cases/DI/OrmExtension.customFunctions.phpt @@ -0,0 +1,240 @@ +withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + customStringFunctions: + DUMMY: Tests\Mocks\DummyStringFunction + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $entityManager */ + $entityManager = $container->getService('nettrine.orm.managers.default.entityManager'); + + $function = $entityManager->getConfiguration()->getCustomStringFunction('DUMMY'); + + Assert::equal(DummyStringFunction::class, $function); +}); + +// Custom numeric functions +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + customNumericFunctions: + ROUND_CUSTOM: Tests\Mocks\DummyStringFunction + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $entityManager */ + $entityManager = $container->getService('nettrine.orm.managers.default.entityManager'); + + $function = $entityManager->getConfiguration()->getCustomNumericFunction('ROUND_CUSTOM'); + + Assert::equal(DummyStringFunction::class, $function); +}); + +// Custom datetime functions +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + customDatetimeFunctions: + DATE_CUSTOM: Tests\Mocks\DummyStringFunction + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $entityManager */ + $entityManager = $container->getService('nettrine.orm.managers.default.entityManager'); + + $function = $entityManager->getConfiguration()->getCustomDatetimeFunction('DATE_CUSTOM'); + + Assert::equal(DummyStringFunction::class, $function); +}); + +// Custom hydration modes +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + customHydrationModes: + custom: Tests\Mocks\DummyHydrator + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $entityManager */ + $entityManager = $container->getService('nettrine.orm.managers.default.entityManager'); + + $hydrator = $entityManager->getConfiguration()->getCustomHydrationMode('custom'); + + Assert::equal('Tests\Mocks\DummyHydrator', $hydrator); +}); + +// Multiple custom functions +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + customStringFunctions: + FUNC1: Tests\Mocks\DummyStringFunction + FUNC2: Tests\Mocks\DummyStringFunction + customNumericFunctions: + NUM1: Tests\Mocks\DummyStringFunction + customDatetimeFunctions: + DATE1: Tests\Mocks\DummyStringFunction + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $entityManager */ + $entityManager = $container->getService('nettrine.orm.managers.default.entityManager'); + + Assert::equal(DummyStringFunction::class, $entityManager->getConfiguration()->getCustomStringFunction('FUNC1')); + Assert::equal(DummyStringFunction::class, $entityManager->getConfiguration()->getCustomStringFunction('FUNC2')); + Assert::equal(DummyStringFunction::class, $entityManager->getConfiguration()->getCustomNumericFunction('NUM1')); + Assert::equal(DummyStringFunction::class, $entityManager->getConfiguration()->getCustomDatetimeFunction('DATE1')); +}); diff --git a/tests/Cases/DI/OrmExtension.errors.phpt b/tests/Cases/DI/OrmExtension.errors.phpt new file mode 100644 index 000000000..e208eb989 --- /dev/null +++ b/tests/Cases/DI/OrmExtension.errors.phpt @@ -0,0 +1,277 @@ +withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + entityManagerDecoratorClass: stdClass + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, InvalidConfigurationException::class, "~EntityManager decorator class must be subclass~"); +}); + +// Error: Second level cache enabled without cache +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + secondLevelCache: + enabled: true + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, LogicalException::class, 'Second level cache is enabled but no cache is set.'); +}); + +// Error: Missing connection +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: nonexistent + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, LogicalException::class, 'Connection "nonexistent" not found'); +}); + +// Error: Invalid mapping type +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: yaml + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, InvalidConfigurationException::class, "~mapping.*App.*type~"); +}); + +// Error: Invalid service reference for cache +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + defaultCache: Invalid + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, InvalidConfigurationException::class, "~defaultCache~"); +}); + +// Error: Empty proxyDir +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + // No tempDir parameter + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + proxyDir: null + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, InvalidConfigurationException::class, "~proxyDir~"); +}); + +// Error: Missing connection in manager +Toolkit::test(function (): void { + Assert::exception(function (): void { + ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + }, InvalidConfigurationException::class, "~connection~"); +}); diff --git a/tests/Cases/DI/OrmExtension.multipleManagers.phpt b/tests/Cases/DI/OrmExtension.multipleManagers.phpt new file mode 100644 index 000000000..ad4fbb0ee --- /dev/null +++ b/tests/Cases/DI/OrmExtension.multipleManagers.phpt @@ -0,0 +1,345 @@ +withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + // Autowiring should return the default manager + $autowired = $container->getByType(EntityManagerInterface::class); + $defaultManager = $container->getService('nettrine.orm.managers.default.entityManager'); + $secondManager = $container->getService('nettrine.orm.managers.second.entityManager'); + + Assert::same($autowired, $defaultManager); + Assert::notSame($autowired, $secondManager); +}); + +// Access non-default managers by name via registry +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + $defaultManager = $registry->getManager('default'); + $secondManager = $registry->getManager('second'); + + Assert::type(EntityManager::class, $defaultManager); + Assert::type(EntityManager::class, $secondManager); + Assert::notSame($defaultManager, $secondManager); +}); + +// Different connections per manager +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $defaultManager */ + $defaultManager = $container->getService('nettrine.orm.managers.default.entityManager'); + /** @var EntityManager $secondManager */ + $secondManager = $container->getService('nettrine.orm.managers.second.entityManager'); + + Assert::notSame($defaultManager->getConnection(), $secondManager->getConnection()); +}); + +// Different caches per manager +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + defaultCache: Symfony\Component\Cache\Adapter\ArrayAdapter() + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + defaultCache: Symfony\Component\Cache\Adapter\FilesystemAdapter(namespace: second, defaultLifetime: 0, directory: %tempDir%/cache/second) + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $defaultManager */ + $defaultManager = $container->getService('nettrine.orm.managers.default.entityManager'); + /** @var EntityManager $secondManager */ + $secondManager = $container->getService('nettrine.orm.managers.second.entityManager'); + + Assert::type(ArrayAdapter::class, $defaultManager->getConfiguration()->getMetadataCache()); + Assert::type(FilesystemAdapter::class, $secondManager->getConfiguration()->getMetadataCache()); +}); + +// Manager count in registry +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + third: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + third: + connection: third + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + Assert::count(3, $registry->getManagers()); + Assert::equal(['default', 'second', 'third'], array_keys($registry->getManagerNames())); +}); + +// Default manager name +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + Assert::equal('default', $registry->getDefaultManagerName()); +}); diff --git a/tests/Cases/Events/ContainerEventManager.phpt b/tests/Cases/Events/ContainerEventManager.phpt index 08ad250d9..4565b29d0 100644 --- a/tests/Cases/Events/ContainerEventManager.phpt +++ b/tests/Cases/Events/ContainerEventManager.phpt @@ -102,3 +102,135 @@ Toolkit::test(function (): void { Assert::count(1, $eventManager->getAllListeners()[Events::onClear]); // one subscriber Assert::type(DummyOnClearSubscriber::class, Arrays::first($eventManager->getAllListeners()[Events::onClear])); // one subscriber }); + +// hasListeners - empty +Toolkit::test(function (): void { + $container = new Container(); + $eventManager = new ContainerEventManager($container); + + Assert::false($eventManager->hasListeners(Events::onClear)); +}); + +// hasListeners - with listeners +Toolkit::test(function (): void { + $subscriber = new DummyOnClearSubscriber(); + + $container = new Container(); + $eventManager = new ContainerEventManager($container); + $eventManager->addEventSubscriber($subscriber); + + Assert::true($eventManager->hasListeners(Events::onClear)); +}); + +// getListeners for specific event +Toolkit::test(function (): void { + $subscriber = new DummyOnClearSubscriber(); + + $container = new Container(); + $eventManager = new ContainerEventManager($container); + $eventManager->addEventSubscriber($subscriber); + + $listeners = $eventManager->getListeners(Events::onClear); + + Assert::count(1, $listeners); + Assert::type(DummyOnClearSubscriber::class, Arrays::first($listeners)); +}); + +// dispatchEvent with null args - dispatches with empty EventArgs +Toolkit::test(function (): void { + $container = new Container(); + $eventManager = new ContainerEventManager($container); + + // Add a generic listener that accepts EventArgs + $called = false; + $listener = new class ($called) { + + public function __construct(private bool &$called) + { + } + + public function onClear(\Doctrine\Common\EventArgs $args): void + { + $this->called = true; + } + + }; + + $eventManager->addEventListener(Events::onClear, $listener); + + // This should not throw - null args should be handled + $eventManager->dispatchEvent(Events::onClear, null); + + Assert::true($called); +}); + +// addEventListener with multiple events +Toolkit::test(function (): void { + $subscriber = new DummyOnClearSubscriber(); + + $container = new Container(); + $eventManager = new ContainerEventManager($container); + + $eventManager->addEventListener([Events::onClear, Events::prePersist], $subscriber); + + Assert::true($eventManager->hasListeners(Events::onClear)); + Assert::true($eventManager->hasListeners(Events::prePersist)); +}); + +// addEventListener prevents duplicate same listener +Toolkit::test(function (): void { + $subscriber = new DummyOnClearSubscriber(); + + $container = new Container(); + $eventManager = new ContainerEventManager($container); + + $eventManager->addEventListener(Events::onClear, $subscriber); + $eventManager->addEventListener(Events::onClear, $subscriber); + + Assert::count(1, $eventManager->getListeners(Events::onClear)); +}); + +// removeEventListener from multiple events +Toolkit::test(function (): void { + $subscriber = new DummyOnClearSubscriber(); + + $container = new Container(); + $eventManager = new ContainerEventManager($container); + + $eventManager->addEventListener([Events::onClear, Events::prePersist], $subscriber); + $eventManager->removeEventListener([Events::onClear, Events::prePersist], $subscriber); + + Assert::false($eventManager->hasListeners(Events::onClear)); + Assert::false($eventManager->hasListeners(Events::prePersist)); +}); + +// Lazy loading - service listeners only loaded on dispatch +Toolkit::test(function (): void { + $subscriber = new DummyOnClearSubscriber(); + + $container = new Container(); + $container->addService('lazySubscriber', $subscriber); + $eventManager = new ContainerEventManager($container); + + $eventManager->addEventListener(Events::onClear, 'lazySubscriber'); + + // Before dispatch, getAllListeners should still return the service (lazy loaded) + $listeners = $eventManager->getAllListeners(); + Assert::count(1, $listeners[Events::onClear]); + + // After first access, the listener should be resolved + Assert::type(DummyOnClearSubscriber::class, Arrays::first($listeners[Events::onClear])); +}); + +// dispatchEvent does nothing when no listeners +Toolkit::test(function (): void { + $entityManager = Mockery::mock(EntityManager::class); + + $container = new Container(); + $eventManager = new ContainerEventManager($container); + + // Should not throw + $eventManager->dispatchEvent(Events::onClear, new OnClearEventArgs($entityManager)); + + Assert::true(true); +}); diff --git a/tests/Cases/ManagerProvider.phpt b/tests/Cases/ManagerProvider.phpt new file mode 100644 index 000000000..6e837e8a1 --- /dev/null +++ b/tests/Cases/ManagerProvider.phpt @@ -0,0 +1,153 @@ +withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerProvider $provider */ + $provider = $container->getByType(EntityManagerProvider::class); + + Assert::type(ManagerProvider::class, $provider); + Assert::type(EntityManagerInterface::class, $provider->getDefaultManager()); +}); + +// Get manager by name +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerProvider $provider */ + $provider = $container->getByType(EntityManagerProvider::class); + + $defaultManager = $provider->getDefaultManager(); + $secondManager = $provider->getManager('second'); + + Assert::type(EntityManagerInterface::class, $defaultManager); + Assert::type(EntityManagerInterface::class, $secondManager); + Assert::notSame($defaultManager, $secondManager); +}); + +// Provider implements EntityManagerProvider interface +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerProvider $provider */ + $provider = $container->getByType(ManagerProvider::class); + + Assert::true($provider instanceof EntityManagerProvider); +}); diff --git a/tests/Cases/ManagerRegistry.phpt b/tests/Cases/ManagerRegistry.phpt index 7ad8bc681..ed3b3c45c 100644 --- a/tests/Cases/ManagerRegistry.phpt +++ b/tests/Cases/ManagerRegistry.phpt @@ -3,12 +3,16 @@ use Contributte\Tester\Toolkit; use Contributte\Tester\Utils\ContainerBuilder; use Contributte\Tester\Utils\Neonkit; +use Doctrine\DBAL\Connection; +use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; use Nette\DI\Compiler; use Nettrine\DBAL\DI\DbalExtension; use Nettrine\ORM\DI\OrmExtension; +use Nettrine\ORM\ManagerRegistry as NettrineManagerRegistry; use Tester\Assert; +use Tests\Mocks\Entity\DummyEntity; use Tests\Toolkit\Tests; require_once __DIR__ . '/../bootstrap.php'; @@ -135,3 +139,354 @@ Toolkit::test(function (): void { Assert::true($em2->isOpen()); } }); + +// Get default manager name +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + Assert::equal('default', $registry->getDefaultManagerName()); +}); + +// Get default connection name +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + Assert::equal('default', $registry->getDefaultConnectionName()); +}); + +// Get connection +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + Assert::type(Connection::class, $registry->getConnection()); + Assert::type(Connection::class, $registry->getConnection('default')); +}); + +// Get connections +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + Assert::count(2, $registry->getConnections()); + Assert::equal(['default', 'second'], array_keys($registry->getConnectionNames())); +}); + +// Get manager names +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + second: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + second: + connection: second + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + Assert::equal(['default', 'second'], array_keys($registry->getManagerNames())); +}); + +// Reopen static method on EntityManager +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [app/Database] + namespace: App\Database + NEON + )); + }) + ->build(); + + /** @var EntityManager $em */ + $em = $container->getByType(EntityManagerInterface::class); + + Assert::true($em->isOpen()); + $em->close(); + Assert::false($em->isOpen()); + + NettrineManagerRegistry::reopen($em); + + Assert::true($em->isOpen()); +}); + +// Get repository for manager +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + 'fixturesDir' => Tests::FIXTURES_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [%fixturesDir%/Entity] + namespace: Tests\Mocks\Entity + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + $repository = $registry->getRepository(DummyEntity::class); + + Assert::type('Doctrine\ORM\EntityRepository', $repository); +}); + +// Get manager for class +Toolkit::test(function (): void { + $container = ContainerBuilder::of() + ->withCompiler(function (Compiler $compiler): void { + $compiler->addExtension('nettrine.dbal', new DbalExtension()); + $compiler->addExtension('nettrine.orm', new OrmExtension()); + $compiler->addConfig([ + 'parameters' => [ + 'tempDir' => Tests::TEMP_PATH, + 'fixturesDir' => Tests::FIXTURES_PATH, + ], + ]); + $compiler->addConfig(Neonkit::load( + <<<'NEON' + nettrine.dbal: + connections: + default: + driver: pdo_sqlite + password: test + user: test + path: ":memory:" + nettrine.orm: + managers: + default: + connection: default + mapping: + App: + type: attributes + directories: [%fixturesDir%/Entity] + namespace: Tests\Mocks\Entity + NEON + )); + }) + ->build(); + + /** @var ManagerRegistry $registry */ + $registry = $container->getByType(ManagerRegistry::class); + + $manager = $registry->getManagerForClass(DummyEntity::class); + + Assert::type(EntityManagerInterface::class, $manager); +}); diff --git a/tests/Cases/Mapping/ContainerEntityListenerResolver.phpt b/tests/Cases/Mapping/ContainerEntityListenerResolver.phpt new file mode 100644 index 000000000..aa1e11044 --- /dev/null +++ b/tests/Cases/Mapping/ContainerEntityListenerResolver.phpt @@ -0,0 +1,118 @@ +resolve(DummyEntityListener::class); + + Assert::type(DummyEntityListener::class, $resolved); +}); + +// Resolve creates new instance if not in container +Toolkit::test(function (): void { + $container = new Container(); + + $resolver = new ContainerEntityListenerResolver($container); + $resolved = $resolver->resolve(DummyEntityListener::class); + + Assert::type(DummyEntityListener::class, $resolved); +}); + +// Resolve caches instances +Toolkit::test(function (): void { + $container = new Container(); + + $resolver = new ContainerEntityListenerResolver($container); + $resolved1 = $resolver->resolve(DummyEntityListener::class); + $resolved2 = $resolver->resolve(DummyEntityListener::class); + + Assert::same($resolved1, $resolved2); +}); + +// Resolve handles leading backslashes +Toolkit::test(function (): void { + $container = new Container(); + + $resolver = new ContainerEntityListenerResolver($container); + $resolved = $resolver->resolve('\\' . DummyEntityListener::class); + + Assert::type(DummyEntityListener::class, $resolved); +}); + +// Register listener manually +Toolkit::test(function (): void { + $listener = new DummyEntityListener(); + $container = new Container(); + + $resolver = new ContainerEntityListenerResolver($container); + $resolver->register($listener); + + $resolved = $resolver->resolve(DummyEntityListener::class); + + Assert::same($listener, $resolved); +}); + +// Clear specific listener +Toolkit::test(function (): void { + $container = new Container(); + + $resolver = new ContainerEntityListenerResolver($container); + $resolved1 = $resolver->resolve(DummyEntityListener::class); + + $resolver->clear(DummyEntityListener::class); + + $resolved2 = $resolver->resolve(DummyEntityListener::class); + + Assert::notSame($resolved1, $resolved2); +}); + +// Clear specific listener with leading backslash +Toolkit::test(function (): void { + $container = new Container(); + + $resolver = new ContainerEntityListenerResolver($container); + $resolved1 = $resolver->resolve(DummyEntityListener::class); + + $resolver->clear('\\' . DummyEntityListener::class); + + $resolved2 = $resolver->resolve(DummyEntityListener::class); + + Assert::notSame($resolved1, $resolved2); +}); + +// Clear all listeners +Toolkit::test(function (): void { + $container = new Container(); + + $resolver = new ContainerEntityListenerResolver($container); + $resolved1 = $resolver->resolve(DummyEntityListener::class); + + $resolver->clear(); + + $resolved2 = $resolver->resolve(DummyEntityListener::class); + + Assert::notSame($resolved1, $resolved2); +}); + +// Clear non-existent listener does not throw +Toolkit::test(function (): void { + $container = new Container(); + $resolver = new ContainerEntityListenerResolver($container); + + // Should not throw + $resolver->clear('NonExistentClass'); + + Assert::true(true); +}); diff --git a/tests/Cases/Utils/Binder.phpt b/tests/Cases/Utils/Binder.phpt new file mode 100644 index 000000000..47c2a217d --- /dev/null +++ b/tests/Cases/Utils/Binder.phpt @@ -0,0 +1,87 @@ +secret; // @phpstan-ignore-line + }); + + Assert::equal('hidden', $result); +}); + +// Bind to object instance - modify private property +Toolkit::test(function (): void { + $obj = new class { + + private string $secret = 'hidden'; + + public function getSecret(): string + { + return $this->secret; + } + + }; + + Binder::use($obj, function (): void { + $this->secret = 'modified'; // @phpstan-ignore-line + }); + + Assert::equal('modified', $obj->getSecret()); +}); + +// Bind to class string - access static property +Toolkit::test(function (): void { + $result = Binder::use(TestClassWithStatic::class, function (): string { + return self::$staticValue; // @phpstan-ignore-line + }); + + Assert::equal('static_secret', $result); +}); + +// Return value from closure +Toolkit::test(function (): void { + $obj = new class { + + private int $value = 42; + + }; + + $result = Binder::use($obj, function (): int { + return $this->value * 2; // @phpstan-ignore-line + }); + + Assert::equal(84, $result); +}); + +// Return null from closure +Toolkit::test(function (): void { + $obj = new class { + }; + + $result = Binder::use($obj, function (): mixed { + return null; + }); + + Assert::null($result); +}); + +class TestClassWithStatic +{ + + private static string $staticValue = 'static_secret'; + +} diff --git a/tests/Mocks/DummyEntityListener.php b/tests/Mocks/DummyEntityListener.php new file mode 100644 index 000000000..d2f1d7793 --- /dev/null +++ b/tests/Mocks/DummyEntityListener.php @@ -0,0 +1,18 @@ +events[] = $args; + } + +} diff --git a/tests/Mocks/DummyHydrator.php b/tests/Mocks/DummyHydrator.php new file mode 100644 index 000000000..11aced359 --- /dev/null +++ b/tests/Mocks/DummyHydrator.php @@ -0,0 +1,18 @@ + Date: Sun, 4 Jan 2026 14:19:46 +0000 Subject: [PATCH 3/3] Tests: Fix code style issues - Fix class brace spacing in BuilderMan tests - Remove unused variable in MappingHelper test - Use sprintf instead of variable interpolation in console test - Use single quotes for regex patterns in error tests - Fix reference passing in ContainerEventManager test - Add phpcs:ignoreFile to Binder test --- tests/Cases/DI/Helper/BuilderMan.phpt | 6 ++++++ tests/Cases/DI/Helper/MappingHelper.phpt | 2 +- tests/Cases/DI/OrmExtension.console.phpt | 2 +- tests/Cases/DI/OrmExtension.errors.phpt | 10 +++++----- tests/Cases/Events/ContainerEventManager.phpt | 14 ++++++++------ tests/Cases/Utils/Binder.phpt | 19 +++++++------------ 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/tests/Cases/DI/Helper/BuilderMan.phpt b/tests/Cases/DI/Helper/BuilderMan.phpt index 153d7e79d..413007ea9 100644 --- a/tests/Cases/DI/Helper/BuilderMan.phpt +++ b/tests/Cases/DI/Helper/BuilderMan.phpt @@ -27,6 +27,7 @@ Toolkit::test(function (): void { public function beforeCompile(): void { $pass = new class ($this) extends AbstractPass { + }; $builderMan = BuilderMan::of($pass); @@ -77,6 +78,7 @@ Toolkit::test(function (): void { public function beforeCompile(): void { $pass = new class ($this) extends AbstractPass { + }; $builderMan = BuilderMan::of($pass); @@ -125,6 +127,7 @@ Toolkit::test(function (): void { public function beforeCompile(): void { $pass = new class ($this) extends AbstractPass { + }; $builderMan = BuilderMan::of($pass); @@ -181,6 +184,7 @@ Toolkit::test(function (): void { public function beforeCompile(): void { $pass = new class ($this) extends AbstractPass { + }; $builderMan = BuilderMan::of($pass); @@ -244,6 +248,7 @@ Toolkit::test(function (): void { public function beforeCompile(): void { $pass = new class ($this) extends AbstractPass { + }; $builderMan = BuilderMan::of($pass); @@ -297,6 +302,7 @@ Toolkit::test(function (): void { public function beforeCompile(): void { $pass = new class ($this) extends AbstractPass { + }; $builderMan = BuilderMan::of($pass); diff --git a/tests/Cases/DI/Helper/MappingHelper.phpt b/tests/Cases/DI/Helper/MappingHelper.phpt index 029746a6e..7d0b336b4 100644 --- a/tests/Cases/DI/Helper/MappingHelper.phpt +++ b/tests/Cases/DI/Helper/MappingHelper.phpt @@ -149,7 +149,7 @@ Toolkit::test(function (): void { // Fluent interface - chaining Toolkit::test(function (): void { - $container = ContainerBuilder::of() + ContainerBuilder::of() ->withCompiler(function (Compiler $compiler): void { $compiler->addExtension('nettrine.dbal', new DbalExtension()); $compiler->addExtension('nettrine.orm', new OrmExtension()); diff --git a/tests/Cases/DI/OrmExtension.console.phpt b/tests/Cases/DI/OrmExtension.console.phpt index 59459f857..9f129995c 100644 --- a/tests/Cases/DI/OrmExtension.console.phpt +++ b/tests/Cases/DI/OrmExtension.console.phpt @@ -125,7 +125,7 @@ Toolkit::test(function (): void { $taggedServices = $container->findByTag('console.command'); foreach ($expectedCommands as $commandName) { - Assert::true(in_array($commandName, $taggedServices, true), "Command '$commandName' should be tagged"); + Assert::true(in_array($commandName, $taggedServices, true), sprintf('Command %s should be tagged', $commandName)); } }); diff --git a/tests/Cases/DI/OrmExtension.errors.phpt b/tests/Cases/DI/OrmExtension.errors.phpt index e208eb989..53afb96f7 100644 --- a/tests/Cases/DI/OrmExtension.errors.phpt +++ b/tests/Cases/DI/OrmExtension.errors.phpt @@ -48,7 +48,7 @@ Toolkit::test(function (): void { )); }) ->build(); - }, InvalidConfigurationException::class, "~EntityManager decorator class must be subclass~"); + }, InvalidConfigurationException::class, '~EntityManager decorator class must be subclass~'); }); // Error: Second level cache enabled without cache @@ -161,7 +161,7 @@ Toolkit::test(function (): void { )); }) ->build(); - }, InvalidConfigurationException::class, "~mapping.*App.*type~"); + }, InvalidConfigurationException::class, '~mapping.*App.*type~'); }); // Error: Invalid service reference for cache @@ -199,7 +199,7 @@ Toolkit::test(function (): void { )); }) ->build(); - }, InvalidConfigurationException::class, "~defaultCache~"); + }, InvalidConfigurationException::class, '~defaultCache~'); }); // Error: Empty proxyDir @@ -237,7 +237,7 @@ Toolkit::test(function (): void { )); }) ->build(); - }, InvalidConfigurationException::class, "~proxyDir~"); + }, InvalidConfigurationException::class, '~proxyDir~'); }); // Error: Missing connection in manager @@ -273,5 +273,5 @@ Toolkit::test(function (): void { )); }) ->build(); - }, InvalidConfigurationException::class, "~connection~"); + }, InvalidConfigurationException::class, '~connection~'); }); diff --git a/tests/Cases/Events/ContainerEventManager.phpt b/tests/Cases/Events/ContainerEventManager.phpt index 4565b29d0..c896935ed 100644 --- a/tests/Cases/Events/ContainerEventManager.phpt +++ b/tests/Cases/Events/ContainerEventManager.phpt @@ -3,6 +3,7 @@ namespace Tests\Cases\Events; use Contributte\Tester\Toolkit; +use Doctrine\Common\EventArgs; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Event\OnClearEventArgs; use Doctrine\ORM\Events; @@ -142,16 +143,17 @@ Toolkit::test(function (): void { $eventManager = new ContainerEventManager($container); // Add a generic listener that accepts EventArgs - $called = false; - $listener = new class ($called) { + $state = new \stdClass(); + $state->called = false; + $listener = new class ($state) { - public function __construct(private bool &$called) + public function __construct(private \stdClass $state) { } - public function onClear(\Doctrine\Common\EventArgs $args): void + public function onClear(EventArgs $args): void { - $this->called = true; + $this->state->called = true; } }; @@ -161,7 +163,7 @@ Toolkit::test(function (): void { // This should not throw - null args should be handled $eventManager->dispatchEvent(Events::onClear, null); - Assert::true($called); + Assert::true($state->called); }); // addEventListener with multiple events diff --git a/tests/Cases/Utils/Binder.phpt b/tests/Cases/Utils/Binder.phpt index 47c2a217d..92ff77823 100644 --- a/tests/Cases/Utils/Binder.phpt +++ b/tests/Cases/Utils/Binder.phpt @@ -1,5 +1,7 @@ secret; // @phpstan-ignore-line - }); + $result = Binder::use($obj, fn (): string => $this->secret); Assert::equal('hidden', $result); }); @@ -45,9 +45,7 @@ Toolkit::test(function (): void { // Bind to class string - access static property Toolkit::test(function (): void { - $result = Binder::use(TestClassWithStatic::class, function (): string { - return self::$staticValue; // @phpstan-ignore-line - }); + $result = Binder::use(TestClassWithStatic::class, fn (): string => self::$staticValue); Assert::equal('static_secret', $result); }); @@ -60,9 +58,7 @@ Toolkit::test(function (): void { }; - $result = Binder::use($obj, function (): int { - return $this->value * 2; // @phpstan-ignore-line - }); + $result = Binder::use($obj, fn (): int => $this->value * 2); Assert::equal(84, $result); }); @@ -70,11 +66,10 @@ Toolkit::test(function (): void { // Return null from closure Toolkit::test(function (): void { $obj = new class { + }; - $result = Binder::use($obj, function (): mixed { - return null; - }); + $result = Binder::use($obj, fn (): mixed => null); Assert::null($result); });