Skip to content

Commit

Permalink
Add model transformer to child
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent4vx committed Jan 29, 2021
1 parent 2c8dca1 commit 5bdfe88
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 8 deletions.
20 changes: 17 additions & 3 deletions src/Child/Child.php
Expand Up @@ -11,6 +11,8 @@
use Bdf\Form\Filter\FilterInterface;
use Bdf\Form\PropertyAccess\ExtractorInterface;
use Bdf\Form\PropertyAccess\HydratorInterface;
use Bdf\Form\Transformer\NullTransformer;
use Bdf\Form\Transformer\TransformerInterface;
use Bdf\Form\View\ElementViewInterface;

/**
Expand Down Expand Up @@ -63,6 +65,11 @@ final class Child implements ChildInterface
*/
private $dependencies;

/**
* @var TransformerInterface
*/
private $transformer;


/**
* ArrayOffsetChild constructor.
Expand All @@ -76,7 +83,7 @@ final class Child implements ChildInterface
* @param ExtractorInterface|null $extractor
* @param string[] $dependencies
*/
public function __construct(string $name, ElementInterface $element, ?HttpFieldsInterface $fields = null, array $filters = [], $defaultValue = null, ?HydratorInterface $hydrator = null, ?ExtractorInterface $extractor = null, array $dependencies = [])
public function __construct(string $name, ElementInterface $element, ?HttpFieldsInterface $fields = null, array $filters = [], $defaultValue = null, ?HydratorInterface $hydrator = null, ?ExtractorInterface $extractor = null, array $dependencies = [], ?TransformerInterface $transformer = null)
{
$this->name = $name;
$this->element = $element->setContainer($this);
Expand All @@ -86,6 +93,7 @@ public function __construct(string $name, ElementInterface $element, ?HttpFields
$this->hydrator = $hydrator;
$this->extractor = $extractor;
$this->dependencies = $dependencies;
$this->transformer = $transformer ?? NullTransformer::instance();
}

/**
Expand Down Expand Up @@ -150,7 +158,10 @@ public function import($entity): void
$this->extractor->setPropertyAccessor($propertyAccessor);
$this->extractor->setFormElement($this);

$this->element->import($this->extractor->extract($entity));
$value = $this->extractor->extract($entity);
$value = $this->transformer->transformToHttp($value, $this->element);

$this->element->import($value);
}

/**
Expand All @@ -167,7 +178,10 @@ public function fill(&$entity): void
$this->hydrator->setPropertyAccessor($propertyAccessor);
$this->hydrator->setFormElement($this);

$this->hydrator->hydrate($entity, $this->element->value());
$value = $this->element->value();
$value = $this->transformer->transformFromHttp($value, $this->element);

$this->hydrator->hydrate($entity, $value);
}

/**
Expand Down
36 changes: 34 additions & 2 deletions src/Child/ChildBuilder.php
Expand Up @@ -13,7 +13,10 @@
use Bdf\Form\PropertyAccess\Setter;
use Bdf\Form\Registry\Registry;
use Bdf\Form\Registry\RegistryInterface;
use Bdf\Form\Transformer\TransformerInterface;
use Bdf\Form\Util\MagicCallForwarding;
use Bdf\Form\Util\TransformerBuilderTrait;
use Symfony\Component\Form\DataTransformerInterface;

/**
* Base builder for a child
Expand Down Expand Up @@ -41,6 +44,9 @@
class ChildBuilder implements ChildBuilderInterface
{
use MagicCallForwarding;
use TransformerBuilderTrait {
transformer as modelTransformer;
}

/**
* @var string
Expand Down Expand Up @@ -206,7 +212,8 @@ final public function buildChild(): ChildInterface
$default,
$this->hydrator,
$this->extractor,
$this->viewDependencies
$this->viewDependencies,
$this->buildTransformer()
);
}

Expand All @@ -218,7 +225,8 @@ final public function buildChild(): ChildInterface
$default,
$this->hydrator,
$this->extractor,
$this->viewDependencies
$this->viewDependencies,
$this->buildTransformer()
);
}

Expand Down Expand Up @@ -415,11 +423,35 @@ final public function configure(callable $configurator): self
return $this;
}

/**
* Forward call to element builder
*
* @param callable|TransformerInterface|DataTransformerInterface $transformer
* @param bool $append
* @return $this
*
* @see ElementBuilderInterface::transformer()
*/
public function transformer($transformer, bool $append = true)
{
$this->elementBuilder->transformer($transformer, $append);

return $this;
}

/**
* {@inheritdoc}
*/
final protected function getElementBuilder(): ElementBuilderInterface
{
return $this->elementBuilder;
}

/**
* {@inheritdoc}
*/
final protected function registry(): RegistryInterface
{
return $this->registry;
}
}
36 changes: 36 additions & 0 deletions src/Child/ChildBuilderInterface.php
Expand Up @@ -6,6 +6,8 @@
use Bdf\Form\Filter\FilterInterface;
use Bdf\Form\PropertyAccess\ExtractorInterface;
use Bdf\Form\PropertyAccess\HydratorInterface;
use Bdf\Form\Transformer\TransformerInterface;
use Symfony\Component\Form\DataTransformerInterface;

/**
* Builder type for instantiate a child
Expand Down Expand Up @@ -113,6 +115,40 @@ public function childFactory($factory);
*/
public function depends(string ...$inputNames);

/**
* Add a model transformer
* The model transformer as the responsability of transform the element value to model PHP value (i.e. fill()'ed value), and vice-versa
*
* When transform to PHP, the transformers are executed in reverse order (last registered is the first executed),
* and there are called in order for transform to HTTP (last registered is the last executed).
* The value parameter of each transformer is the previous transformer result
*
* <code>
* $builder->modelTransformer(new MyTransformer()); // Add a transformer (will be executed before previous ones on submit)
* $builder->modelTransformer(new MyTransformer(), false); // Prepend a transformer (will be executed after previous ones on submit)
*
* // Register a custom transformer
* // The first parameter is the value to transform
* // The second is the current element
* // The third is a flag : if true, the transformation is from element to model, if false, it's from PHP to HTTP
* $builder->modelTransformer(function ($value, ElementInterface $input, bool $toPhp) {
* if ($toPhp) {
* return new Entity($value);
* } else {
* return $value->export();
* }
* });
* </code>
*
* @param callable|TransformerInterface|DataTransformerInterface $transformer The transformer. Symfony transformer can be used
* @param bool $append Append the transformer. Prepend if false
*
* @return $this
*
* @see TransformerInterface
*/
public function modelTransformer($transformer, bool $append = true);

/**
* Creates the child instance
*
Expand Down
4 changes: 3 additions & 1 deletion src/Child/ChildCreationStrategyInterface.php
Expand Up @@ -7,6 +7,7 @@
use Bdf\Form\Filter\FilterInterface;
use Bdf\Form\PropertyAccess\ExtractorInterface;
use Bdf\Form\PropertyAccess\HydratorInterface;
use Bdf\Form\Transformer\TransformerInterface;

/**
* Invokable interface for define the child factory
Expand All @@ -24,8 +25,9 @@ interface ChildCreationStrategyInterface
* @param HydratorInterface|null $hydrator
* @param ExtractorInterface|null $extractor
* @param string[] $dependencies
* @param TransformerInterface|null $transformer
*
* @return ChildInterface
*/
public function __invoke(string $name, ElementInterface $element, HttpFieldsInterface $fields, array $filters, $defaultValue, ?HydratorInterface $hydrator, ?ExtractorInterface $extractor, array $dependencies): ChildInterface;
public function __invoke(string $name, ElementInterface $element, HttpFieldsInterface $fields, array $filters, $defaultValue, ?HydratorInterface $hydrator, ?ExtractorInterface $extractor, array $dependencies, ?TransformerInterface $transformer): ChildInterface;
}
4 changes: 2 additions & 2 deletions src/ElementBuilderInterface.php
Expand Up @@ -85,7 +85,7 @@ public function satisfy($constraint, $options = null, bool $append = true);
* // Register a custom transformer
* // The first parameter is the value to transform
* // The second is the current element
* // The third is a flag t: if true, the transformation is from HTTP to PHP, if false, it's from PHP to HTTP
* // The third is a flag : if true, the transformation is from HTTP to PHP, if false, it's from PHP to HTTP
* $builder->transformer(function ($value, ElementInterface $input, bool $toPhp) {
* if ($toPhp) {
* return new Entity($value);
Expand All @@ -96,7 +96,7 @@ public function satisfy($constraint, $options = null, bool $append = true);
* </code>
*
* @param callable|TransformerInterface|DataTransformerInterface $transformer The transformer. Symfony transformer can be used
* @param bool $append Append the validator. Prepend if false
* @param bool $append Append the transformer. Prepend if false
*
* @return $this
*
Expand Down
24 changes: 24 additions & 0 deletions tests/Child/ChildBuilderTest.php
Expand Up @@ -338,6 +338,30 @@ public function test_configure()

$this->assertInstanceOf(StringElementBuilder::class, $param);
}

/**
*
*/
public function test_modelTransformer()
{
$child = $this->builder
->modelTransformer(function ($value, $input, $toPhp) {
return $toPhp ? base64_encode($value) : base64_decode($value);
})
->setter('prop')->getter('prop')
->buildChild()
;
$child->setParent(new Form(new ChildrenCollection()));
$child->element()->import('value');

$target = [];
$child->fill($target);

$target = ['prop' => base64_encode('foo')];
$child->import($target);

$this->assertEquals('foo', $child->element()->value());
}
}

class MyCustomChild implements ChildInterface
Expand Down
32 changes: 32 additions & 0 deletions tests/Child/ChildTest.php
Expand Up @@ -12,6 +12,7 @@
use Bdf\Form\Leaf\View\SimpleElementView;
use Bdf\Form\PropertyAccess\Getter;
use Bdf\Form\PropertyAccess\Setter;
use Bdf\Form\Transformer\ClosureTransformer;
use Bdf\Form\Validator\ConstraintValueValidator;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\NotBlank;
Expand Down Expand Up @@ -78,6 +79,20 @@ public function test_import_with_object()
$this->assertSame('my value', $child->element()->value());
}

/**
*
*/
public function test_import_with_transformer()
{
$child = new Child('child', new StringElement(), new ArrayOffsetHttpFields('child'), [], new NotBlank(['message' => 'required error']), null, new Getter(), [], new ClosureTransformer(function($value) {
return base64_encode($value);
}));
$child->setParent(new Form(new ChildrenCollection()));

$child->import(['child' => 'my value']);
$this->assertSame(base64_encode('my value'), $child->element()->value());
}

/**
*
*/
Expand Down Expand Up @@ -108,6 +123,23 @@ public function test_fill_with_object()
$this->assertEquals('my value', $target->child);
}

/**
*
*/
public function test_fill_with_transformer()
{
$child = new Child('child', new StringElement(), new ArrayOffsetHttpFields('child'), [], new NotBlank(['message' => 'required error']), new Setter(), null, [], new ClosureTransformer(function($value) {
return base64_encode($value);
}));
$child->setParent(new Form(new ChildrenCollection()));
$child->element()->import('my value');

$target = (object) ['child' => null];
$child->fill($target);

$this->assertEquals(base64_encode('my value'), $target->child);
}

/**
* @dataProvider emptyValues
*/
Expand Down

0 comments on commit 5bdfe88

Please sign in to comment.