Skip to content

Commit

Permalink
Merge pull request #1 from headsnet/carbon-type-support
Browse files Browse the repository at this point in the history
Auto-register Doctrine types provided by Carbon
  • Loading branch information
benr77 authored May 23, 2024
2 parents f54953e + cae4efa commit ea37043
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 11 deletions.
52 changes: 44 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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`:
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
58 changes: 58 additions & 0 deletions src/CarbonTypes/RegisterCarbonTypesCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);

namespace Headsnet\DoctrineToolsBundle\CarbonTypes;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* Automatically registers the custom Doctrine types provided by the Carbon library.
*/
final class RegisterCarbonTypesCompilerPass implements CompilerPassInterface
{
private const TYPE_DEFINITION_PARAMETER = 'doctrine.dbal.connection_factory.types';

public function process(ContainerBuilder $container): void
{
// Skip if carbon_types is disabled in the configuration
if (!$container->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<string, array{class: class-string}> $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);
}
}
22 changes: 20 additions & 2 deletions src/HeadsnetDoctrineToolsBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<string>}} $config
* @param array{
* custom_types: array{scan_dirs: array<string>},
* 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'])
;
}

Expand All @@ -42,5 +56,9 @@ public function build(ContainerBuilder $container): void
$container->addCompilerPass(
new RegisterDoctrineTypesCompilerPass()
);

$container->addCompilerPass(
new RegisterCarbonTypesCompilerPass()
);
}
}
77 changes: 77 additions & 0 deletions tests/CarbonTypes/RegisterCarbonTypesCompilerPassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);

namespace Headsnet\DoctrineToolsBundle\Tests\CarbonTypes;

use Carbon\Doctrine\DateTimeImmutableType;
use Carbon\Doctrine\DateTimeType;
use Headsnet\DoctrineToolsBundle\CarbonTypes\RegisterCarbonTypesCompilerPass;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;

#[CoversClass(RegisterCarbonTypesCompilerPass::class)]
class RegisterCarbonTypesCompilerPassTest extends TestCase
{
#[Test]
public function carbon_types_are_registered(): 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', 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);
}
}
2 changes: 2 additions & 0 deletions tests/HeadsnetDoctrineToolsBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,6 +13,7 @@
use Symfony\Component\HttpKernel\KernelInterface;

#[CoversClass(HeadsnetDoctrineToolsBundle::class)]
#[CoversClass(RegisterCarbonTypesCompilerPass::class)]
#[CoversClass(RegisterDoctrineTypesCompilerPass::class)]
class HeadsnetDoctrineToolsBundleTest extends KernelTestCase
{
Expand Down

0 comments on commit ea37043

Please sign in to comment.