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": { diff --git a/tests/Cases/DI/Helper/BuilderMan.phpt b/tests/Cases/DI/Helper/BuilderMan.phpt new file mode 100644 index 000000000..413007ea9 --- /dev/null +++ b/tests/Cases/DI/Helper/BuilderMan.phpt @@ -0,0 +1,349 @@ +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..7d0b336b4 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 { + 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..9f129995c --- /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), sprintf('Command %s should be tagged', $commandName)); + } +}); + +// 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..53afb96f7 --- /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..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; @@ -102,3 +103,136 @@ 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 + $state = new \stdClass(); + $state->called = false; + $listener = new class ($state) { + + public function __construct(private \stdClass $state) + { + } + + public function onClear(EventArgs $args): void + { + $this->state->called = true; + } + + }; + + $eventManager->addEventListener(Events::onClear, $listener); + + // This should not throw - null args should be handled + $eventManager->dispatchEvent(Events::onClear, null); + + Assert::true($state->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..92ff77823 --- /dev/null +++ b/tests/Cases/Utils/Binder.phpt @@ -0,0 +1,82 @@ + $this->secret); + + 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, fn (): string => self::$staticValue); + + Assert::equal('static_secret', $result); +}); + +// Return value from closure +Toolkit::test(function (): void { + $obj = new class { + + private int $value = 42; + + }; + + $result = Binder::use($obj, fn (): int => $this->value * 2); + + Assert::equal(84, $result); +}); + +// Return null from closure +Toolkit::test(function (): void { + $obj = new class { + + }; + + $result = Binder::use($obj, fn (): mixed => 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 @@ +