diff --git a/README.md b/README.md index a3403be..4412858 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Doctrine Tools +Doctrine Tools for Symfony ==== ![Build Status](https://github.com/headsnet/doctrine-tools-bundle/actions/workflows/ci.yml/badge.svg) @@ -27,7 +27,10 @@ return [ ## Features -### Auto-Register Custom Column Types +- [Auto-Register Custom Doctrine Types](#auto-register-custom-doctrine-types) +- [Auto-Register Carbon Doctrine Types](#auto-register-carbon-datetime-types) + +### Auto-Register Custom Doctrine Types The bundle can auto-register custom Doctrine DBAL types, eliminating the need to specify them all in `config/packages/doctrine.yaml`: @@ -47,20 +50,53 @@ Then add the `#[CustomType]` attribute to the custom type class: use Doctrine\DBAL\Types\Type; #[CustomType] -final class CarbonDateTimeType extends Type +final class ReservationIdType extends Type { - ... + // defines "reservation_id" type } ``` This will register a custom type based on the class name - in this case the custom column type will be called -`carbon_date_time`. +`reservation_id`. -To customise the type name, specify it in the `#[CustomType]` attribute - e.g. the following will register a type -called `custom_carbon_datetime`. +To customise the type name, specify it in the `#[CustomType]` attribute. The following will register a type +called `my_reservation_id`. ```php -#[CustomType(name: 'custom_carbon_datetime')] +#[CustomType(name: 'my_reservation_id')] +final class ReservationIdType extends Type +{ + // customised name "my_reservation_id" type +} +``` + +### Auto-Register Carbon datetime types + +If the `nesbot/carbon` package is installed, this package will automatically register the Doctrine types provided by +Carbon. + +By default, it will overwrite the default Doctrine types for `datetime` and `datetime_immutable` with the Carbon +equivalents: + +```yaml +datetime_immutable: \Carbon\Doctrine\DateTimeImmutableType +datetime: \Carbon\Doctrine\DateTimeType +``` +If you wish the Carbon types to operate alongside the default DateTime and DateTimeImmutable types, set `replace: +false` in the bundle configuration. This will result in additional types being defined for the Carbon columns. + +```yaml +carbon_immutable: \Carbon\Doctrine\DateTimeImmutableType +carbon: \Carbon\Doctrine\DateTimeType +``` + +If you wish to completely disable this behaviour, set `enabled: false` in the bundle configuration. + +```yaml +headsnet_doctrine_tools: + carbon_types: + enabled: true + replace: true ``` ## Contributions diff --git a/composer.json b/composer.json index 8ac33a5..7d434b0 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,11 @@ "phpunit/phpunit": "^10.0", "nyholm/symfony-bundle-test": "^3.0", "phpstan/phpstan": "^1.11", - "symplify/easy-coding-standard": "^12.2" + "symplify/easy-coding-standard": "^12.2", + "nesbot/carbon": "^3.3" + }, + "suggest": { + "nesbot/carbon": "If present, this package will automatically register the Carbon Doctrine types." }, "autoload": { "psr-4": { diff --git a/src/CarbonTypes/RegisterCarbonTypesCompilerPass.php b/src/CarbonTypes/RegisterCarbonTypesCompilerPass.php new file mode 100644 index 0000000..b84cc6d --- /dev/null +++ b/src/CarbonTypes/RegisterCarbonTypesCompilerPass.php @@ -0,0 +1,58 @@ +getParameter('headsnet_doctrine_tools.carbon_types.enabled')) { + return; + } + + // Skip if Doctrine is not installed + if (!$container->hasParameter(self::TYPE_DEFINITION_PARAMETER)) { + return; + } + + // Skip if Carbon is not installed + if (!class_exists('Carbon\Carbon')) { + return; + } + + /** @var array $typeDefinitions */ + $typeDefinitions = $container->getParameter(self::TYPE_DEFINITION_PARAMETER); + + // Use Carbon to upgrade standard datetime and datetime_immutable column types + if ($container->getParameter('headsnet_doctrine_tools.carbon_types.replace')) { + $immutableName = 'datetime_immutable'; + $mutableName = 'datetime'; + } + // Otherwise, register additional types and leave the existing datetime and + // datetime_immutable types in place + else { + $immutableName = 'carbon_immutable'; + $mutableName = 'carbon'; + } + + $typeDefinitions[$immutableName] = [ + 'class' => 'Carbon\Doctrine\DateTimeImmutableType', + ]; + + $typeDefinitions[$mutableName] = [ + 'class' => 'Carbon\Doctrine\DateTimeType', + ]; + + $container->setParameter(self::TYPE_DEFINITION_PARAMETER, $typeDefinitions); + } +} diff --git a/src/HeadsnetDoctrineToolsBundle.php b/src/HeadsnetDoctrineToolsBundle.php index ba7187c..420b85c 100644 --- a/src/HeadsnetDoctrineToolsBundle.php +++ b/src/HeadsnetDoctrineToolsBundle.php @@ -2,6 +2,7 @@ namespace Headsnet\DoctrineToolsBundle; +use Headsnet\DoctrineToolsBundle\CarbonTypes\RegisterCarbonTypesCompilerPass; use Headsnet\DoctrineToolsBundle\CustomTypes\RegisterDoctrineTypesCompilerPass; use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -20,18 +21,31 @@ public function configure(DefinitionConfigurator $definition): void ->scalarPrototype()->end() ->end() ->end() + ->end() // End custom_types + ->arrayNode('carbon_types') + ->canBeDisabled() + ->children() + ->booleanNode('enabled') + ->defaultTrue()->end() + ->booleanNode('replace') + ->defaultTrue()->end() ->end() - ->end() + ->end() // End carbon_types ; } /** - * @param array{custom_types: array{scan_dirs: array}} $config + * @param array{ + * custom_types: array{scan_dirs: array}, + * carbon_types: array{enabled: boolean, replace: boolean} + * } $config */ public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { $container->parameters() ->set('headsnet_doctrine_tools.custom_types.scan_dirs', $config['custom_types']['scan_dirs']) + ->set('headsnet_doctrine_tools.carbon_types.enabled', $config['carbon_types']['enabled']) + ->set('headsnet_doctrine_tools.carbon_types.replace', $config['carbon_types']['replace']) ; } @@ -42,5 +56,9 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass( new RegisterDoctrineTypesCompilerPass() ); + + $container->addCompilerPass( + new RegisterCarbonTypesCompilerPass() + ); } } diff --git a/tests/CarbonTypes/RegisterCarbonTypesCompilerPassTest.php b/tests/CarbonTypes/RegisterCarbonTypesCompilerPassTest.php new file mode 100644 index 0000000..04aee43 --- /dev/null +++ b/tests/CarbonTypes/RegisterCarbonTypesCompilerPassTest.php @@ -0,0 +1,77 @@ +setParameter('doctrine.dbal.connection_factory.types', []); + $container->setParameter('headsnet_doctrine_tools.carbon_types.enabled', true); + $container->setParameter('headsnet_doctrine_tools.carbon_types.replace', true); + $sut = new RegisterCarbonTypesCompilerPass(); + + $sut->process($container); + + $result = $container->getParameter('doctrine.dbal.connection_factory.types'); + $expected = [ + 'datetime_immutable' => [ + 'class' => DateTimeImmutableType::class, + ], + 'datetime' => [ + 'class' => DateTimeType::class, + ], + ]; + $this->assertEquals($expected, $result); + } + + #[Test] + public function carbon_types_are_registered_separately(): void + { + $container = new ContainerBuilder(); + $container->setParameter('doctrine.dbal.connection_factory.types', []); + $container->setParameter('headsnet_doctrine_tools.carbon_types.enabled', true); + $container->setParameter('headsnet_doctrine_tools.carbon_types.replace', false); + $sut = new RegisterCarbonTypesCompilerPass(); + + $sut->process($container); + + $result = $container->getParameter('doctrine.dbal.connection_factory.types'); + $expected = [ + 'carbon_immutable' => [ + 'class' => DateTimeImmutableType::class, + ], + 'carbon' => [ + 'class' => DateTimeType::class, + ], + ]; + $this->assertEquals($expected, $result); + } + + #[Test] + public function if_disabled_then_register_nothing(): void + { + $container = new ContainerBuilder(); + $container->setParameter('doctrine.dbal.connection_factory.types', []); + $container->setParameter('headsnet_doctrine_tools.carbon_types.enabled', false); + $sut = new RegisterCarbonTypesCompilerPass(); + + $sut->process($container); + + $result = $container->getParameter('doctrine.dbal.connection_factory.types'); + $expected = []; + $this->assertEquals($expected, $result); + } +} diff --git a/tests/HeadsnetDoctrineToolsBundleTest.php b/tests/HeadsnetDoctrineToolsBundleTest.php index 1eddace..8c4b8ff 100644 --- a/tests/HeadsnetDoctrineToolsBundleTest.php +++ b/tests/HeadsnetDoctrineToolsBundleTest.php @@ -3,6 +3,7 @@ namespace Headsnet\DoctrineToolsBundle\Tests; +use Headsnet\DoctrineToolsBundle\CarbonTypes\RegisterCarbonTypesCompilerPass; use Headsnet\DoctrineToolsBundle\CustomTypes\RegisterDoctrineTypesCompilerPass; use Headsnet\DoctrineToolsBundle\HeadsnetDoctrineToolsBundle; use Nyholm\BundleTest\TestKernel; @@ -12,6 +13,7 @@ use Symfony\Component\HttpKernel\KernelInterface; #[CoversClass(HeadsnetDoctrineToolsBundle::class)] +#[CoversClass(RegisterCarbonTypesCompilerPass::class)] #[CoversClass(RegisterDoctrineTypesCompilerPass::class)] class HeadsnetDoctrineToolsBundleTest extends KernelTestCase {