Skip to content

Commit 71b3457

Browse files
committed
Introduce Distance and Duration types
1 parent 03cc80d commit 71b3457

25 files changed

Lines changed: 1784 additions & 260 deletions

Time.plantuml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
@startuml
2+
3+
!theme plain
4+
top to bottom direction
5+
skinparam linetype ortho
6+
7+
class AbstractTimeUnit {
8+
getMapping():
9+
getCases():
10+
}
11+
class AbstractUnit {
12+
__construct(value):
13+
defaultPrecision:
14+
defaultRoundingMode:
15+
value:
16+
__toString():
17+
getMapping():
18+
convert(targetUnitClass):
19+
expandScientificNotation(number):
20+
create(value, unitType):
21+
fixTrailingZeros(number):
22+
round(precision, mode):
23+
getValue():
24+
normalize():
25+
format(precision, mode):
26+
getChoices():
27+
}
28+
class BackedEnum {
29+
value:
30+
from(value):
31+
tryFrom(value):
32+
}
33+
class Day {
34+
getFactor():
35+
getSymbol():
36+
getUnitType():
37+
}
38+
class Hour {
39+
getFactor():
40+
getSymbol():
41+
getUnitType():
42+
}
43+
class Microsecond {
44+
getFactor():
45+
getSymbol():
46+
getUnitType():
47+
}
48+
class Millisecond {
49+
getFactor():
50+
getSymbol():
51+
getUnitType():
52+
}
53+
class Minute {
54+
getFactor():
55+
getSymbol():
56+
getUnitType():
57+
}
58+
class Month {
59+
getFactor():
60+
getSymbol():
61+
getUnitType():
62+
}
63+
class Nanosecond {
64+
getFactor():
65+
getSymbol():
66+
getUnitType():
67+
}
68+
class Picosecond {
69+
getFactor():
70+
getSymbol():
71+
getUnitType():
72+
}
73+
class Second {
74+
getFactor():
75+
getSymbol():
76+
getUnitType():
77+
}
78+
class StringBackedEnum {
79+
value:
80+
from(value):
81+
tryFrom(value):
82+
}
83+
class TimeUnitInterface
84+
class TimeUnitType {
85+
fromString(unit):
86+
normalize(unit):
87+
}
88+
class UnitEnum {
89+
name:
90+
cases():
91+
}
92+
class UnitInterface {
93+
getMapping():
94+
convert(targetUnitClass):
95+
create(value, unitType):
96+
getFactor():
97+
getSymbol():
98+
getCases():
99+
round(precision, mode):
100+
getUnitType():
101+
getValue():
102+
normalize():
103+
format(precision, mode):
104+
getChoices():
105+
}
106+
class Week {
107+
getFactor():
108+
getSymbol():
109+
getUnitType():
110+
}
111+
class Year {
112+
getFactor():
113+
getSymbol():
114+
getUnitType():
115+
}
116+
117+
AbstractTimeUnit -[#000082,plain]-^ AbstractUnit
118+
AbstractTimeUnit -[#008200,dashed]-^ TimeUnitInterface
119+
AbstractUnit -[#008200,dashed]-^ UnitInterface
120+
BackedEnum -[#008200,plain]-^ UnitEnum
121+
Day -[#000082,plain]-^ AbstractTimeUnit
122+
Hour -[#000082,plain]-^ AbstractTimeUnit
123+
Microsecond -[#000082,plain]-^ AbstractTimeUnit
124+
Millisecond -[#000082,plain]-^ AbstractTimeUnit
125+
Minute -[#000082,plain]-^ AbstractTimeUnit
126+
Month -[#000082,plain]-^ AbstractTimeUnit
127+
Nanosecond -[#000082,plain]-^ AbstractTimeUnit
128+
Picosecond -[#000082,plain]-^ AbstractTimeUnit
129+
Second -[#000082,plain]-^ AbstractTimeUnit
130+
StringBackedEnum -[#008200,plain]-^ BackedEnum
131+
TimeUnitInterface -[#008200,plain]-^ UnitInterface
132+
TimeUnitType -[#008200,dashed]-^ BackedEnum
133+
Week -[#000082,plain]-^ AbstractTimeUnit
134+
Year -[#000082,plain]-^ AbstractTimeUnit
135+
@enduml

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"doctrine/doctrine-bundle": "^2.13.2",
2424
"doctrine/orm": "^3.3.2",
2525
"ergebnis/composer-normalize": "^2.45",
26+
"jetbrains/phpstorm-attributes": "^1.2",
2627
"nyholm/symfony-bundle-test": "^3.0",
2728
"php-cs-fixer/shim": "^3.75",
2829
"phpunit/phpunit": "^12.1",

config/packages/doctrine.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
<?php
22

3-
use SoureCode\Bundle\Unit\Doctrine\DBAL\Types\LengthType;
3+
use SoureCode\Bundle\Unit\Doctrine\DBAL\Types\DistanceType;
4+
use SoureCode\Bundle\Unit\Doctrine\DBAL\Types\DurationType;
45
use Symfony\Component\DependencyInjection\ContainerBuilder;
56

67
return static function (ContainerBuilder $containerBuilder) {
78
$containerBuilder->prependExtensionConfig('doctrine', [
89
'dbal' => [
910
'types' => [
10-
LengthType::NAME => LengthType::class,
11+
DistanceType::NAME => DistanceType::class,
12+
DurationType::NAME => DurationType::class,
1113
],
1214
],
1315
]);
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
use Doctrine\DBAL\Platforms\AbstractPlatform;
66
use Doctrine\DBAL\Types\ConversionException;
77
use Doctrine\DBAL\Types\FloatType;
8+
use SoureCode\Bundle\Unit\Model\Distance;
89
use SoureCode\Bundle\Unit\Model\Length\LengthUnitInterface;
910
use SoureCode\Bundle\Unit\Model\Length\Picometer;
1011
use SoureCode\Bundle\Unit\Model\UnitInterface;
1112

12-
class LengthType extends FloatType
13+
class DistanceType extends FloatType
1314
{
14-
public const string NAME = 'length';
15+
public const string NAME = 'distance';
1516

1617
/**
1718
* @var class-string<LengthUnitInterface>
1819
*/
1920
public static string $databaseUnitClass = Picometer::class;
2021

21-
public function convertToPHPValue($value, AbstractPlatform $platform): ?UnitInterface
22+
public function convertToPHPValue($value, AbstractPlatform $platform): ?Distance
2223
{
2324
$value = parent::convertToPHPValue($value, $platform);
2425

@@ -27,11 +28,11 @@ public function convertToPHPValue($value, AbstractPlatform $platform): ?UnitInte
2728
}
2829

2930
/**
30-
* @var UnitInterface $databaseUnit
31+
* @var LengthUnitInterface $databaseUnit
3132
*/
32-
$databaseUnit = new self::$databaseUnitClass($value);
33+
$databaseUnit = new static::$databaseUnitClass($value);
3334

34-
return $databaseUnit->normalize();
35+
return new Distance($databaseUnit->normalize());
3536
}
3637

3738
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
@@ -40,22 +41,25 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str
4041
return null;
4142
}
4243

43-
if (!$value instanceof UnitInterface) {
44-
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), [UnitInterface::class]);
44+
if (!$value instanceof Distance) {
45+
throw ConversionException::conversionFailedInvalidType($value, static::NAME, [UnitInterface::class]);
4546
}
4647

47-
$databaseUnit = $value->convert(self::$databaseUnitClass);
48+
/**
49+
* @var LengthUnitInterface $databaseUnit
50+
*/
51+
$databaseUnit = $value->getValue()->convert(static::$databaseUnitClass);
4852

4953
return parent::convertToDatabaseValue((string) $databaseUnit->getValue(), $platform);
5054
}
5155

52-
public function getName(): string
56+
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
5357
{
54-
return self::NAME;
58+
return true;
5559
}
5660

57-
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
61+
public function getName(): string
5862
{
59-
return true;
63+
return static::NAME;
6064
}
6165
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace SoureCode\Bundle\Unit\Doctrine\DBAL\Types;
4+
5+
use Doctrine\DBAL\Platforms\AbstractPlatform;
6+
use Doctrine\DBAL\Types\ConversionException;
7+
use Doctrine\DBAL\Types\FloatType;
8+
use SoureCode\Bundle\Unit\Model\Duration;
9+
use SoureCode\Bundle\Unit\Model\Time\Picosecond;
10+
use SoureCode\Bundle\Unit\Model\Time\TimeUnitInterface;
11+
use SoureCode\Bundle\Unit\Model\UnitInterface;
12+
13+
class DurationType extends FloatType
14+
{
15+
public const string NAME = 'duration';
16+
17+
/**
18+
* @var class-string<TimeUnitInterface>
19+
*/
20+
public static string $databaseUnitClass = Picosecond::class;
21+
22+
public function convertToPHPValue($value, AbstractPlatform $platform): ?Duration
23+
{
24+
$value = parent::convertToPHPValue($value, $platform);
25+
26+
if (null === $value) {
27+
return null;
28+
}
29+
30+
/**
31+
* @var TimeUnitInterface $databaseUnit
32+
*/
33+
$databaseUnit = new static::$databaseUnitClass($value);
34+
35+
return new Duration($databaseUnit->normalize());
36+
}
37+
38+
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
39+
{
40+
if (null === $value) {
41+
return null;
42+
}
43+
44+
if (!$value instanceof Duration) {
45+
throw ConversionException::conversionFailedInvalidType($value, static::NAME, [UnitInterface::class]);
46+
}
47+
48+
/**
49+
* @var TimeUnitInterface $databaseUnit
50+
*/
51+
$databaseUnit = $value->getValue()->convert(static::$databaseUnitClass);
52+
53+
return parent::convertToDatabaseValue((string) $databaseUnit->getValue(), $platform);
54+
}
55+
56+
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
57+
{
58+
return true;
59+
}
60+
61+
public function getName(): string
62+
{
63+
return static::NAME;
64+
}
65+
}

src/Form/DistanceType.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace SoureCode\Bundle\Unit\Form;
4+
5+
use SoureCode\Bundle\Unit\Model\Distance;
6+
use SoureCode\Bundle\Unit\Model\Length\AbstractLengthUnit;
7+
use SoureCode\Bundle\Unit\Model\Length\LengthUnitInterface;
8+
use Symfony\Component\Form\AbstractType;
9+
use Symfony\Component\Form\CallbackTransformer;
10+
use Symfony\Component\Form\FormBuilderInterface;
11+
use Symfony\Component\OptionsResolver\OptionsResolver;
12+
13+
/**
14+
* @template-extends AbstractType<Distance>
15+
*/
16+
class DistanceType extends AbstractType
17+
{
18+
public function buildForm(FormBuilderInterface $builder, array $options): void
19+
{
20+
$builder
21+
->add('value', ValueType::class, [
22+
'error_bubbling' => true,
23+
])
24+
->add('unit', UnitChoiceType::class, [
25+
'error_bubbling' => true,
26+
'default_unit_class' => $options['default_unit_class'],
27+
'unit_class' => AbstractLengthUnit::class,
28+
]);
29+
30+
$builder->addModelTransformer(new CallbackTransformer(
31+
function (?LengthUnitInterface $data): ?array {
32+
if (null === $data) {
33+
return null;
34+
}
35+
36+
return [
37+
'value' => (string) $data->getValue(),
38+
'unit' => $data::getUnitType(),
39+
];
40+
},
41+
function (?array $data): ?Distance {
42+
if (null === $data) {
43+
return null;
44+
}
45+
46+
if (null === $data['unit'] || null === $data['value']) {
47+
return null;
48+
}
49+
50+
$unit = AbstractLengthUnit::create($data['value'], $data['unit']);
51+
52+
return Distance::create($unit);
53+
}
54+
));
55+
}
56+
57+
public function configureOptions(OptionsResolver $resolver): void
58+
{
59+
$resolver->setDefaults([
60+
'data_class' => null,
61+
'compound' => true,
62+
'empty_data' => [],
63+
'by_reference' => false,
64+
'error_bubbling' => false,
65+
66+
'default_unit_class' => null,
67+
]);
68+
69+
$resolver->setAllowedValues('default_unit_class', function (?string $value): bool {
70+
return is_subclass_of($value, LengthUnitInterface::class) || null === $value;
71+
});
72+
}
73+
74+
public function getBlockPrefix(): string
75+
{
76+
return 'sourecode_unit_distance';
77+
}
78+
}

0 commit comments

Comments
 (0)