Skip to content

Commit

Permalink
Add model transformer on child + saveAsTimestamp() on DateTimeChildBu…
Browse files Browse the repository at this point in the history
…ilder
  • Loading branch information
vincent4vx committed Jan 29, 2021
1 parent 5bdfe88 commit 9d312b9
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/Child/ChildBuilder.php
Expand Up @@ -441,6 +441,8 @@ public function transformer($transformer, bool $append = true)

/**
* {@inheritdoc}
*
* @return B
*/
final protected function getElementBuilder(): ElementBuilderInterface
{
Expand Down
42 changes: 42 additions & 0 deletions src/Leaf/Date/DateTimeChildBuilder.php
@@ -0,0 +1,42 @@
<?php

namespace Bdf\Form\Leaf\Date;

use Bdf\Form\Child\ChildBuilder;
use Bdf\Form\Leaf\Date\Transformer\DateTimeToTimestampTransformer;

/**
* Child builder for date time elements
*
* @extends ChildBuilder<DateTimeElementBuilder>
*/
class DateTimeChildBuilder extends ChildBuilder
{
/**
* The model value of the input will be transformer to a timestamp
*
* <code>
* // The entity : date is a timestamp
* class MyEntity {
* public int $date;
* }
*
* // Build the element
* $builder->dateTime('date')->saveAsTimestamp()->getter()->setter();
*
* $form->import(MyEntity::get($id));
* $form['date']->element()->value(); // Value is an instance of DateTime
*
* $entity = $form->value();
* $entity->date; // date is a timestamp (i.e. integer value)
* </code>
*
* @return $this
*
* @see DateTimeToTimestampTransformer
*/
public function saveAsTimestamp(): self
{
return $this->modelTransformer(new DateTimeToTimestampTransformer());
}
}
20 changes: 20 additions & 0 deletions src/Leaf/Date/DateTimeElement.php
Expand Up @@ -54,6 +54,26 @@ public function __construct(?ValueValidatorInterface $validator = null, ?Transfo
$this->timezone = $timezone;
}

/**
* Get the timezone of the element
*
* @return DateTimeZone|null The timezone, or null if not defined
*/
public function timezone(): ?DateTimeZone
{
return $this->timezone;
}

/**
* Get the handled date time class name
*
* @return class-string<DateTimeInterface>
*/
public function dateTimeClassName(): string
{
return $this->className;
}

/**
* {@inheritdoc}
*/
Expand Down
76 changes: 76 additions & 0 deletions src/Leaf/Date/Transformer/DateTimeToTimestampTransformer.php
@@ -0,0 +1,76 @@
<?php

namespace Bdf\Form\Leaf\Date\Transformer;

use Bdf\Form\ElementInterface;
use Bdf\Form\Leaf\Date\DateTimeElement;
use Bdf\Form\Transformer\TransformerInterface;
use DateTime;
use DateTimeInterface;
use DateTimeZone;

/**
* Transform a DateTime instance from a form element to a timestamp to a model
*/
final class DateTimeToTimestampTransformer implements TransformerInterface
{
/**
* @var class-string<DateTimeInterface>|null
*/
private $className;

/**
* @var DateTimeZone|null
*/
private $timezone;


/**
* DateTimeToTimestampTransformer constructor.
*
* @param class-string<DateTimeInterface>|null $className The date time class name to use when retrieving value from model. If null, will use the class defined in the input element
* @param DateTimeZone|null $timezone The timezone to set when retrieving value from model. If null will use the element's timezone
*/
public function __construct(?string $className = null, ?DateTimeZone $timezone = null)
{
$this->className = $className;
$this->timezone = $timezone;
}

/**
* {@inheritdoc}
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress PossiblyUndefinedMethod
*/
public function transformToHttp($value, ElementInterface $input): ?DateTimeInterface
{
if ($value === null) {
return null;
}

$className = $this->className ?? ($input instanceof DateTimeElement ? $input->dateTimeClassName() : DateTime::class);
$timezone = $this->timezone ?? ($input instanceof DateTimeElement ? $input->timezone() : null);

/** @var DateTimeInterface $dateTime */
$dateTime = new $className;

if ($timezone) {
$dateTime = $dateTime->setTimezone($timezone);
}

return $dateTime->setTimestamp($value);
}

/**
* {@inheritdoc}
*/
public function transformFromHttp($value, ElementInterface $input): ?int
{
if (!$value instanceof DateTimeInterface) {
return null;
}

return $value->getTimestamp();
}
}
34 changes: 30 additions & 4 deletions src/Registry/Registry.php
Expand Up @@ -19,6 +19,7 @@
use Bdf\Form\Filter\FilterInterface;
use Bdf\Form\Leaf\BooleanElement;
use Bdf\Form\Leaf\BooleanElementBuilder;
use Bdf\Form\Leaf\Date\DateTimeChildBuilder;
use Bdf\Form\Leaf\Date\DateTimeElement;
use Bdf\Form\Leaf\Date\DateTimeElementBuilder;
use Bdf\Form\Leaf\FloatElement;
Expand Down Expand Up @@ -50,7 +51,7 @@
final class Registry implements RegistryInterface
{
/**
* @var string[]|callable[]
* @var class-string<ElementBuilderInterface>[]|callable[]
*/
private $elementBuilderFactories = [
StringElement::class => StringElementBuilder::class,
Expand All @@ -70,6 +71,12 @@ final class Registry implements RegistryInterface
Form::class => FormBuilder::class,
];

/**
* @var class-string<ChildBuilderInterface>[]|callable[]
*/
private $childBuilderFactories = [
DateTimeElement::class => DateTimeChildBuilder::class,
];

/**
* Registry constructor.
Expand Down Expand Up @@ -159,7 +166,16 @@ public function transformer($transformer): TransformerInterface
*/
public function childBuilder(string $element, string $name): ChildBuilderInterface
{
return new ChildBuilder($name, $this->elementBuilder($element), $this);
$elementBuilder = $this->elementBuilder($element);

$builderFactory = $this->childBuilderFactories[$element] ?? ChildBuilder::class;

if (is_string($builderFactory)) {
/** @var class-string<ChildBuilderInterface> $builderFactory */
return new $builderFactory($name, $elementBuilder, $this);
}

return $builderFactory($name, $elementBuilder, $this);
}

/**
Expand Down Expand Up @@ -211,15 +227,25 @@ public function buttonBuilder(string $name): ButtonBuilderInterface
* $registry->register(MyCustomElement::class, function (Registry $registry, string $element) {
* return new MyCustomBuilder($registry);
* });
*
* // Register with a custom child builder
* $registry->register(MyCustomElement::class, MyCustomBuilder::class, function (string $name, ElementBuilderInterface $builder, Registry $registry) {
* return new MyCustomChildBuilder($registry, new ChildBuilder($name, $builder, $registry));
* });
* </code>
*
* @param string $elementType The element class name
* @param string|callable $builderFactory The builder factory, or builder class name
* @param class-string<ElementBuilderInterface>|callable $builderFactory The builder factory, or builder class name
* @param class-string<ChildBuilderInterface>|callable|null $childBuilderFactory The builder factory for child, or builder class name. If null, use default child builder
*
* @see Registry::elementBuilder()
*/
public function register(string $elementType, $builderFactory): void
public function register(string $elementType, $builderFactory, $childBuilderFactory = null): void
{
$this->elementBuilderFactories[$elementType] = $builderFactory;

if ($childBuilderFactory !== null) {
$this->childBuilderFactories[$elementType] = $childBuilderFactory;
}
}
}
43 changes: 43 additions & 0 deletions tests/Leaf/Date/DateTimeChildBuilderTest.php
@@ -0,0 +1,43 @@
<?php

namespace Bdf\Form\Leaf\Date;

use Bdf\Form\Aggregate\Collection\ChildrenCollection;
use Bdf\Form\Aggregate\Form;
use PHPUnit\Framework\TestCase;

class DateTimeChildBuilderTest extends TestCase
{
/**
*
*/
public function test_saveAsTimestamp()
{
$builder = new DateTimeChildBuilder('child', new DateTimeElementBuilder());

$child = $builder
->immutable()
->getter()->setter()
->saveAsTimestamp()
->buildChild()
;

$child->setParent(new Form(new ChildrenCollection()));
$child->import(['child' => 123]);

$this->assertInstanceOf(\DateTimeImmutable::class, $child->element()->value());
$this->assertEquals(123, $child->element()->value()->getTimestamp());

$child->element()->import(new \DateTimeImmutable('2020-10-15 00:00:00'));
$target = [];
$child->fill($target);

$this->assertSame(['child' => 1602712800], $target);

$child->import(['child' => null]);
$this->assertNull($child->element()->value());

$child->fill($target);
$this->assertSame(['child' => null], $target);
}
}
66 changes: 66 additions & 0 deletions tests/Leaf/Date/Transformer/DateTimeToTimestampTransformerTest.php
@@ -0,0 +1,66 @@
<?php

namespace Bdf\Form\Leaf\Date\Transformer;

use Bdf\Form\Aggregate\FormBuilder;
use PHPUnit\Framework\TestCase;

/**
* Class DateTimeToTimestampTransformerTest
*/
class DateTimeToTimestampTransformerTest extends TestCase
{
/**
*
*/
public function test_default()
{
$builder = new FormBuilder();
$builder->dateTime('foo')->getter()->setter()->modelTransformer(new DateTimeToTimestampTransformer());
$form = $builder->buildElement();

$form->import(['foo' => 123]);
$this->assertSame(['foo' => 123], $form->value());

$this->assertEquals(new \DateTime('@123'), $form['foo']->element()->value());
$this->assertEquals(new \DateTimeZone(date_default_timezone_get()), $form['foo']->element()->value()->getTimezone());
}

/**
*
*/
public function test_default_with_className()
{
$builder = new FormBuilder();
$builder->dateTime('foo')->getter()->setter()->modelTransformer(new DateTimeToTimestampTransformer(CustomDateTime::class));
$form = $builder->buildElement();

$form->import(['foo' => 123]);
$this->assertSame(['foo' => 123], $form->value());

$this->assertInstanceOf(CustomDateTime::class, $form['foo']->element()->value());
$this->assertEquals(new CustomDateTime('@123'), $form['foo']->element()->value());
$this->assertEquals(new \DateTimeZone(date_default_timezone_get()), $form['foo']->element()->value()->getTimezone());
}

/**
*
*/
public function test_default_with_className_and_timezone()
{
$builder = new FormBuilder();
$builder->dateTime('foo')->getter()->setter()->modelTransformer(new DateTimeToTimestampTransformer(CustomDateTime::class, new \DateTimeZone('Asia/Shanghai')));
$form = $builder->buildElement();

$form->import(['foo' => 123]);
$this->assertSame(['foo' => 123], $form->value());

$this->assertEquals(new CustomDateTime('@123'), $form['foo']->element()->value());
$this->assertEquals(new \DateTimeZone('Asia/Shanghai'), $form['foo']->element()->value()->getTimezone());
}
}

class CustomDateTime extends \DateTime
{

}
8 changes: 7 additions & 1 deletion tests/Registry/RegistryTest.php
Expand Up @@ -10,6 +10,7 @@
use Bdf\Form\Button\SubmitButtonBuilder;
use Bdf\Form\Child\Child;
use Bdf\Form\Child\ChildBuilder;
use Bdf\Form\Child\ChildBuilderInterface;
use Bdf\Form\Csrf\CsrfElement;
use Bdf\Form\Csrf\CsrfElementBuilder;
use Bdf\Form\Custom\CustomForm;
Expand All @@ -19,6 +20,7 @@
use Bdf\Form\Filter\TrimFilter;
use Bdf\Form\Leaf\BooleanElement;
use Bdf\Form\Leaf\BooleanElementBuilder;
use Bdf\Form\Leaf\Date\DateTimeChildBuilder;
use Bdf\Form\Leaf\Date\DateTimeElement;
use Bdf\Form\Leaf\Date\DateTimeElementBuilder;
use Bdf\Form\Leaf\FloatElement;
Expand Down Expand Up @@ -162,6 +164,8 @@ public function test_childBuilder()
$this->assertInstanceOf(Child::class, $child);
$this->assertEquals('child', $child->name());
$this->assertInstanceOf(StringElement::class, $child->element());

$this->assertInstanceOf(DateTimeChildBuilder::class, $this->registry->childBuilder(DateTimeElement::class, 'child'));
}

/**
Expand All @@ -185,9 +189,11 @@ public function test_elementBuilder()
$this->assertInstanceOf(MyCustomForm::class, $this->registry->elementBuilder(MyCustomForm::class)->buildElement());

$builder = $this->createMock(ElementBuilderInterface::class);
$this->registry->register(MyCustomTestElement::class, function () use($builder) { return $builder; });
$childBuilder = $this->createMock(ChildBuilderInterface::class);
$this->registry->register(MyCustomTestElement::class, function () use($builder) { return $builder; }, function () use ($childBuilder) { return $childBuilder; });

$this->assertSame($builder, $this->registry->elementBuilder(MyCustomTestElement::class));
$this->assertSame($childBuilder, $this->registry->childBuilder(MyCustomTestElement::class, 'child'));
}

/**
Expand Down

0 comments on commit 9d312b9

Please sign in to comment.