Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Aggregate/ArrayElementBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use Bdf\Form\Phone\PhoneElement;
use Bdf\Form\Registry\Registry;
use Bdf\Form\Registry\RegistryInterface;
use Bdf\Form\Struct\StructForm;
use Bdf\Form\Struct\StructFormBuilder;
use Bdf\Form\Transformer\TransformerInterface;
use Bdf\Form\Util\MagicCallForwarding;
use Bdf\Form\Util\TransformerBuilderTrait;
Expand Down Expand Up @@ -329,6 +331,35 @@ public function enum(string $enumClass, ?callable $configurator = null): static
});
}

/**
* Define as array of struct
*
* <code>
* $builder->array('coordinates')->struct(Coordinate::class, function(StructFormBuilder $builder) {
* // ...
* })->getset();
* </code>
*
* @param class-string<S> $className The struct class name
* @param callable(StructFormBuilder):void|null $configurator Callback for configure the inner element builder
*
* @return static
* @psalm-this-out ArrayElementBuilder<S>
*
* @template S as object
* @since 2.0
*/
public function struct(string $className, ?callable $configurator = null): static
{
return $this->element(StructForm::class, function (StructFormBuilder $builder) use ($className, $configurator) {
$builder->class($className);

if ($configurator !== null) {
$configurator($builder);
}
});
}

/**
* Define as array of embedded forms
*
Expand Down
12 changes: 10 additions & 2 deletions src/Aggregate/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
use Iterator;
use Override;

use function assert;
use function is_array;
use function is_object;
use function sprintf;
use function trigger_error;

/**
* The base form element
Expand Down Expand Up @@ -216,7 +219,7 @@
}

/** @var T $value */
return $this->value = $value;
return $this->value = $this->generator->finalize($value);
}

#[Override]
Expand Down Expand Up @@ -296,6 +299,11 @@
#[Override]
public function attach($entity): FormInterface
{
/** @psalm-suppress RedundantCondition */
if (!is_object($entity) && !is_array($entity)) {

Check warning on line 303 in src/Aggregate/Form.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalAndNegation": @@ @@ public function attach($entity): FormInterface { /** @psalm-suppress RedundantCondition */ - if (!is_object($entity) && !is_array($entity)) { + if (!(!is_object($entity) && !is_array($entity))) { @trigger_error(sprintf('Attaching a non-object and non-array value is deprecated since bdf-form 2.0 and will be removed. Got %s.', get_debug_type($entity)), E_USER_DEPRECATED); }

Check warning on line 303 in src/Aggregate/Form.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalAndAllSubExprNegation": @@ @@ public function attach($entity): FormInterface { /** @psalm-suppress RedundantCondition */ - if (!is_object($entity) && !is_array($entity)) { + if (is_object($entity) && is_array($entity)) { @trigger_error(sprintf('Attaching a non-object and non-array value is deprecated since bdf-form 2.0 and will be removed. Got %s.', get_debug_type($entity)), E_USER_DEPRECATED); }

Check warning on line 303 in src/Aggregate/Form.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalAnd": @@ @@ public function attach($entity): FormInterface { /** @psalm-suppress RedundantCondition */ - if (!is_object($entity) && !is_array($entity)) { + if (!is_object($entity) || !is_array($entity)) { @trigger_error(sprintf('Attaching a non-object and non-array value is deprecated since bdf-form 2.0 and will be removed. Got %s.', get_debug_type($entity)), E_USER_DEPRECATED); }

Check warning on line 303 in src/Aggregate/Form.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalNot": @@ @@ public function attach($entity): FormInterface { /** @psalm-suppress RedundantCondition */ - if (!is_object($entity) && !is_array($entity)) { + if (!is_object($entity) && is_array($entity)) { @trigger_error(sprintf('Attaching a non-object and non-array value is deprecated since bdf-form 2.0 and will be removed. Got %s.', get_debug_type($entity)), E_USER_DEPRECATED); }

Check warning on line 303 in src/Aggregate/Form.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalNot": @@ @@ public function attach($entity): FormInterface { /** @psalm-suppress RedundantCondition */ - if (!is_object($entity) && !is_array($entity)) { + if (is_object($entity) && !is_array($entity)) { @trigger_error(sprintf('Attaching a non-object and non-array value is deprecated since bdf-form 2.0 and will be removed. Got %s.', get_debug_type($entity)), E_USER_DEPRECATED); }
@trigger_error(sprintf('Attaching a non-object and non-array value is deprecated since bdf-form 2.0 and will be removed. Got %s.', get_debug_type($entity)), E_USER_DEPRECATED);
}

$this->generator->attach($entity);
$this->value = null; // The value is only attached : it must be filled when calling value()

Expand Down
23 changes: 22 additions & 1 deletion src/Aggregate/FormBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
use Bdf\Form\Phone\PhoneElementBuilder;
use Bdf\Form\Registry\RegistryInterface;
use Bdf\Form\RootElementInterface;
use Bdf\Form\Struct\StructForm;
use Bdf\Form\Struct\StructFormBuilder;
use Bdf\Form\Transformer\TransformerInterface;
use Bdf\Form\Validator\ValueValidatorInterface;
use Override;
Expand Down Expand Up @@ -239,6 +241,25 @@ public function enum(string $name, string $enumClass): ChildBuilderInterface
return $builder->enumClass($enumClass);
}

/**
* {@inheritdoc}
*
* @param non-empty-string $name The child name
* @param class-string $structClass The struct class name
* @return ChildBuilder<StructFormBuilder>
*
* @psalm-suppress MoreSpecificReturnType
* @psalm-suppress LessSpecificReturnStatement
*/
#[Override]
public function struct(string $name, string $structClass): ChildBuilderInterface
{
/** @var ChildBuilderInterface<StructFormBuilder> $builder */
$builder = $this->add($name, StructForm::class);

return $builder->class($structClass);
}

/**
* {@inheritdoc}
*
Expand Down Expand Up @@ -390,7 +411,7 @@ public function generator(ValueGeneratorInterface $generator): FormBuilderInterf
* {@inheritdoc}
*/
#[Override]
public function generates($entity): FormBuilderInterface
public function generates(mixed $entity): FormBuilderInterface
{
return $this->generator(new ValueGenerator($entity));
}
Expand Down
20 changes: 19 additions & 1 deletion src/Aggregate/FormBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Bdf\Form\Leaf\StringElementBuilder;
use Bdf\Form\Phone\PhoneChildBuilder;
use Bdf\Form\Phone\PhoneElementBuilder;
use Bdf\Form\Struct\StructFormBuilder;
use Override;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
Expand Down Expand Up @@ -189,6 +190,23 @@ public function phone(string $name): ChildBuilderInterface;
*/
public function enum(string $name, string $enumClass): ChildBuilderInterface;

/**
* Add a new embedded form following the given struct
*
* <code>
* $builder->struct('options', Options::class)->getset();
* </code>
*
* @param non-empty-string $name The child name
* @param class-string $structClass The struct class name
*
* @return ChildBuilder|StructFormBuilder
* @psalm-return ChildBuilderInterface<StructFormBuilder>
*
* @since 2.0
*/
public function struct(string $name, string $structClass): ChildBuilderInterface;

/**
* Add a new csrf token on form
*
Expand Down Expand Up @@ -315,7 +333,7 @@ public function generator(ValueGeneratorInterface $generator): FormBuilderInterf
* });
* </code>
*
* @param callable|class-string|object|array $entity The entity to generate
* @param (callable(ElementInterface):array|object)|class-string|array|object $entity The entity to generate
*
* @return $this
*
Expand Down
2 changes: 1 addition & 1 deletion src/Aggregate/FormInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface FormInterface extends ChildAggregateInterface
* $this->repository->save($form->value());
* </code>
*
* @param T|class-string|callable():T $entity The entity object, or class name
* @param T $entity The entity object, or initial array values
*
* @return $this
*
Expand Down
51 changes: 51 additions & 0 deletions src/Aggregate/Value/ClosureValueGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Bdf\Form\Aggregate\Value;

use Bdf\Form\ElementInterface;
use Closure;
use Override;

/**
* Generate a value using a closure
*
* @template T as array|object
* @implements ValueGeneratorInterface<T>
*/
final class ClosureValueGenerator implements ValueGeneratorInterface
{
/**
* @var T|null
*/
private array|object|null $attachment = null;

public function __construct(
/**
* @var Closure(ElementInterface):T
*/
private readonly Closure $generator,
) {}

#[Override]
public function attach(mixed $entity): void
{
$this->attachment = $entity;
}

#[Override]
public function generate(ElementInterface $element): object|array
{
if ($this->attachment !== null) {
return $this->attachment;
}

return ($this->generator)($element);
}

#[Override]
public function finalize(object|array $value): object|array
{
/** @var T */
return $value;
}
}
62 changes: 62 additions & 0 deletions src/Aggregate/Value/ConstructorValueGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Bdf\Form\Aggregate\Value;

use Bdf\Form\ElementInterface;
use InvalidArgumentException;
use Override;

use function assert;
use function is_array;
use function sprintf;

/**
* Instantiate the form value using its constructor with named parameters
* Hydrators will fill an array which will be used as parameters of the class constructor
*
* @template T as object
* @implements ValueGeneratorInterface<T>
*/
final class ConstructorValueGenerator implements ValueGeneratorInterface
{
/**
* @var T|array|null
*/
private array|object|null $attachment = null;

public function __construct(
/**
* @var class-string<T>
*/
private readonly string $class,
) {}

#[Override]
public function attach(mixed $entity): void
{
if (is_array($entity) || $entity instanceof $this->class) {
$this->attachment = $entity;
return;
}

throw new InvalidArgumentException(sprintf('Expected array or instance of %s, %s given on %s::attach()', $this->class, get_debug_type($entity), self::class));
}

#[Override]
public function generate(ElementInterface $element): object|array
{
return $this->attachment ?? [];
}

#[Override]
public function finalize(object|array $value): object
{
if (is_array($value)) {
$value = new ($this->class)(...$value);
}

assert($value instanceof $this->class);

return $value;
}
}
62 changes: 62 additions & 0 deletions src/Aggregate/Value/DefaultConstructorValueGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Bdf\Form\Aggregate\Value;

use Bdf\Form\ElementInterface;
use InvalidArgumentException;
use Override;

use function assert;
use function get_class;
use function get_debug_type;
use function sprintf;

/**
* Generate a value by call its constructor without arguments
* Properties will be filled directly by hydrators
*
* @template T as object
* @implements ValueGeneratorInterface<T>
*/
final class DefaultConstructorValueGenerator implements ValueGeneratorInterface
{
/**
* @var T|null
*/
private ?object $attachment = null;

public function __construct(
/**
* @var class-string<T>
*/
private readonly string $class,
) {}

#[Override]
public function attach(mixed $entity): void
{
if (!$entity instanceof $this->class) {
throw new InvalidArgumentException(sprintf('Cannot attach a value of type %s, expecting %s', get_debug_type($entity), $this->class));
}

$this->attachment = $entity;
}

#[Override]
public function generate(ElementInterface $element): object
{
if ($this->attachment !== null) {
return $this->attachment;
}

return new $this->class;
}

#[Override]
public function finalize(object|array $value): object
{
assert($value instanceof $this->class);

return $value;
}
}
Loading
Loading