-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto-generated types for standard int/string/uuid objects
Not sure if this should become part of the main package. The API still needs work, and there is currently no interface for the string/int/uuid objects to enforce the correct methods.
- Loading branch information
Showing
13 changed files
with
430 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Symfony\Component\DependencyInjection\Loader\Configurator; | ||
|
||
return function (ContainerConfigurator $configurator): void | ||
{ | ||
$services = $configurator->services() | ||
->defaults() | ||
->autowire() | ||
->autoconfigure() | ||
; | ||
|
||
$services->load('Headsnet\\DoctrineToolsBundle\\', '../src/*'); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Headsnet\DoctrineToolsBundle\Attribute; | ||
|
||
use Attribute; | ||
|
||
#[Attribute(Attribute::TARGET_CLASS)] | ||
final class DoctrineType | ||
{ | ||
public function __construct( | ||
public readonly string $name, | ||
public readonly string $type | ||
) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Headsnet\DoctrineToolsBundle\Types; | ||
|
||
final class CandidateType | ||
{ | ||
private string $baseTypeClass; | ||
|
||
/** | ||
* @param class-string $objectClass | ||
*/ | ||
public function __construct( | ||
public readonly string $typeName, | ||
public readonly string $typeClass, | ||
public readonly string $baseType, | ||
public readonly string $objectClass, | ||
) { | ||
} | ||
|
||
public function setBaseTypeClass(string $baseTypeClass): void | ||
{ | ||
$this->baseTypeClass = $baseTypeClass; | ||
} | ||
|
||
public function getBaseTypeClass(): string | ||
{ | ||
return $this->baseTypeClass; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Headsnet\DoctrineToolsBundle\Types; | ||
|
||
use Headsnet\DoctrineToolsBundle\Attribute\DoctrineType; | ||
use Headsnet\DoctrineToolsBundle\Types\StandardTypes\MappingPrototype; | ||
use League\ConstructFinder\ConstructFinder; | ||
use ReflectionClass; | ||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
||
/** | ||
* Automatically create Doctrine types for any class that implements #[DoctrineType]. | ||
*/ | ||
final class DoctrineTypesCompilerPass implements CompilerPassInterface | ||
{ | ||
private const TYPE_DEFINITION_PARAMETER = 'doctrine.dbal.connection_factory.types'; | ||
|
||
private string $rootNamespace; | ||
|
||
public function process(ContainerBuilder $container): void | ||
{ | ||
if (!$container->hasParameter(self::TYPE_DEFINITION_PARAMETER)) { | ||
return; | ||
} | ||
|
||
/** @var array<string, array{class: class-string}> $typeDefinitions */ | ||
$typeDefinitions = $container->getParameter(self::TYPE_DEFINITION_PARAMETER); | ||
/** @var array<string> $scanDirs */ | ||
$scanDirs = $container->getParameter('headsnet_doctrine_tools.preset_types.scan_dirs'); | ||
$this->rootNamespace = $container->getParameter('headsnet_doctrine_tools.root_namespace'); // @phpstan-ignore-line | ||
|
||
$objectsToRegister = $this->findObjectsToRegister($scanDirs); | ||
|
||
$prototypeTypes = array_keys($container->findTaggedServiceIds(MappingPrototype::TAG)); | ||
|
||
foreach ($objectsToRegister as $candidate) { | ||
// Do not add the type if it's been manually defined already | ||
if (array_key_exists($candidate->typeName, $typeDefinitions)) { | ||
continue; | ||
} | ||
|
||
/** @var MappingPrototype $prototypeType */ | ||
foreach ($prototypeTypes as $prototypeType) { | ||
if ($prototypeType::supports($candidate->baseType)) { | ||
$candidate->setBaseTypeClass( | ||
$prototypeType::mappedBy() | ||
); | ||
} | ||
} | ||
|
||
if (!$candidate->getBaseTypeClass()) { | ||
throw new \RuntimeException('Unsupported base type for Doctrine!'); | ||
} | ||
|
||
$this->writeClassToFile($candidate); | ||
|
||
$typeDefinitions[$candidate->typeName] = [ | ||
'class' => sprintf( | ||
'%s\_generated\HeadsnetDoctrineTools\Types\\%s', | ||
$this->rootNamespace, | ||
$candidate->typeClass | ||
), | ||
]; | ||
} | ||
|
||
$container->setParameter(self::TYPE_DEFINITION_PARAMETER, $typeDefinitions); | ||
} | ||
|
||
/** | ||
* @param array<string> $scanDirs | ||
* | ||
* @return iterable<CandidateType> | ||
*/ | ||
private function findObjectsToRegister(array $scanDirs): iterable | ||
{ | ||
$classNames = ConstructFinder::locatedIn(...$scanDirs)->findClassNames(); | ||
|
||
foreach ($classNames as $className) { | ||
$reflection = new ReflectionClass($className); | ||
|
||
// Skip any abstract parent types | ||
if ($reflection->isAbstract()) { | ||
continue; | ||
} | ||
|
||
// Only register types that have the #[DoctrineType] attribute | ||
if ($reflection->getAttributes(DoctrineType::class)) { | ||
$attribute = $reflection->getAttributes(DoctrineType::class)[0]; | ||
$attributeArgs = $attribute->getArguments(); | ||
|
||
yield new CandidateType( | ||
typeName: $attributeArgs['name'], | ||
typeClass: $reflection->getShortName() . 'Type', | ||
baseType: $attributeArgs['type'], | ||
objectClass: $className | ||
); | ||
} | ||
} | ||
} | ||
|
||
private function generateClass(CandidateType $candidate): string | ||
{ | ||
return <<<PHP | ||
<?php | ||
namespace $this->rootNamespace\_generated\HeadsnetDoctrineTools\Types; | ||
class $candidate->typeClass extends \\{$candidate->getBaseTypeClass()} { | ||
public function getName(): string | ||
{ | ||
return '$candidate->typeName'; | ||
} | ||
public function getClass(): string | ||
{ | ||
return '$candidate->objectClass'; | ||
} | ||
} | ||
PHP; | ||
} | ||
|
||
private function writeClassToFile(CandidateType $candidate): void | ||
{ | ||
$classCode = $this->generateClass($candidate); | ||
|
||
$filePath = sprintf('src/_generated/HeadsnetDoctrineTools/Types/%s.php', $candidate->typeClass); | ||
|
||
if (!is_dir(dirname($filePath))) { | ||
mkdir(dirname($filePath), 0777, true); | ||
} | ||
|
||
file_put_contents($filePath, $classCode); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Headsnet\DoctrineToolsBundle\Types\StandardTypes; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types\Type; | ||
|
||
abstract class AbstractIntegerMappingType extends Type | ||
{ | ||
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string | ||
{ | ||
return $platform->getIntegerTypeDeclarationSQL($column); | ||
} | ||
|
||
/** | ||
* @param int|null $value | ||
*/ | ||
public function convertToPHPValue($value, AbstractPlatform $platform): ?object | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
$class = $this->getClass(); | ||
|
||
return $class::create($value); | ||
} | ||
|
||
/** | ||
* @param object|null $value | ||
*/ | ||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?int | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
return $value->asInteger(); // @phpstan-ignore-line | ||
} | ||
|
||
abstract public function getName(): string; | ||
|
||
abstract public function getClass(): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Headsnet\DoctrineToolsBundle\Types\StandardTypes; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types\Type; | ||
|
||
abstract class AbstractStringMappingType extends Type | ||
{ | ||
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string | ||
{ | ||
return $platform->getStringTypeDeclarationSQL($column); | ||
} | ||
|
||
/** | ||
* @param string|null $value | ||
*/ | ||
public function convertToPHPValue($value, AbstractPlatform $platform): ?object | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
$class = $this->getClass(); | ||
|
||
return $class::create($value); | ||
} | ||
|
||
/** | ||
* @param object|null $value | ||
*/ | ||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
return $value->asString(); // @phpstan-ignore-line | ||
} | ||
|
||
abstract public function getName(): string; | ||
|
||
abstract public function getClass(): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Headsnet\DoctrineToolsBundle\Types\StandardTypes; | ||
|
||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types\Type; | ||
|
||
abstract class AbstractUuidMappingType extends Type | ||
{ | ||
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string | ||
{ | ||
return $platform->getGuidTypeDeclarationSQL($column); | ||
} | ||
|
||
/** | ||
* @param string|null $value | ||
*/ | ||
public function convertToPHPValue($value, AbstractPlatform $platform): ?object | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
$class = $this->getClass(); | ||
|
||
return $class::fromString($value); | ||
} | ||
|
||
/** | ||
* @param object|string|null $value | ||
*/ | ||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
if (is_string($value)) { | ||
return $value; | ||
} | ||
|
||
return $value->asString(); // @phpstan-ignore-line | ||
} | ||
|
||
abstract public function getName(): string; | ||
|
||
abstract public function getClass(): string; | ||
} |
Oops, something went wrong.