Skip to content
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
1 change: 0 additions & 1 deletion src/Aggregate/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Iterator;
use Override;

use function assert;
use function is_array;
use function is_object;
use function sprintf;
Expand Down
21 changes: 21 additions & 0 deletions 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
18 changes: 18 additions & 0 deletions 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
4 changes: 2 additions & 2 deletions src/Attribute/Aggregate/ArrayConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ public function __construct(
) {}

#[Override]
public function applyOnChildBuilder(AttributeForm $form, ChildBuilderInterface $builder): void
public function applyOnChildBuilder(object|string $context, ChildBuilderInterface $builder): void
{
$builder->arrayConstraint($this->constraint);
}

#[Override]
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, AttributeForm $form): void
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, object|string $context): void
{
$constraint = ObjectInstantiation::promotedProperties($this->constraint)->render($generator);
$generator->line('$?->arrayConstraint(?);', [$name, $constraint]);
Expand Down
23 changes: 18 additions & 5 deletions src/Attribute/Aggregate/CallbackArrayConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
use Override;
use Symfony\Component\Validator\Constraint;

use function is_object;
use function sprintf;

/**
* Define a custom constraint for an array element, using a validation method
*
Expand Down Expand Up @@ -76,21 +79,31 @@ public function __construct(
) {}

#[Override]
public function applyOnChildBuilder(AttributeForm $form, ChildBuilderInterface $builder): void
public function applyOnChildBuilder(object|string $context, ChildBuilderInterface $builder): void
{
$constraint = new Closure($form->{$this->methodName}(...), $this->message);
if (is_object($context)) {
$constraint = new Closure($context->{$this->methodName}(...), $this->message);
} else {
$constraint = new Closure($context::{$this->methodName}(...), $this->message);
}

$builder->arrayConstraint($constraint);
}

#[Override]
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, AttributeForm $form): void
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, object|string $context): void
{
$generator->use(Closure::class, 'ClosureConstraint');

if (is_object($context)) {
$closure = sprintf('$context->%s(...)', $this->methodName);
} else {
$closure = sprintf('\%s::%s(...)', $context, $this->methodName);
}

$parameters = $this->message !== null
? new Literal('$form->?(...), ?', [$this->methodName, $this->message])
: new Literal('$form->?(...)', [$this->methodName])
? new Literal($closure . ', ?', [$this->message])
: new Literal($closure)
;

$generator->line('$?->arrayConstraint(new ClosureConstraint(?));', [$name, $parameters]);
Expand Down
4 changes: 2 additions & 2 deletions src/Attribute/Aggregate/Count.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
final class Count extends CountConstraint implements ChildBuilderAttributeInterface
{
#[Override]
public function applyOnChildBuilder(AttributeForm $form, ChildBuilderInterface $builder): void
public function applyOnChildBuilder(object|string $context, ChildBuilderInterface $builder): void
{
$builder->arrayConstraint($this);
}
Expand All @@ -55,7 +55,7 @@ public function validatedBy(): string
}

#[Override]
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, AttributeForm $form): void
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, object|string $context): void
{
$defaultParameters = get_class_vars(CountConstraint::class);
/** @var array{
Expand Down
23 changes: 15 additions & 8 deletions src/Attribute/Aggregate/ElementType.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
use Nette\PhpGenerator\Literal;
use Override;

use function is_object;
use function is_string;

/**
* Attribute for define the array element type
* You can also define a configuration method (not required)
Expand Down Expand Up @@ -68,21 +71,25 @@ public function __construct(
}

#[Override]
public function applyOnChildBuilder(AttributeForm $form, ChildBuilderInterface $builder): void
public function applyOnChildBuilder(object|string $context, ChildBuilderInterface $builder): void
{
$configurator = $this->configurator !== null ? [$form, $this->configurator] : null;
$configurator = match (true) {
$this->configurator !== null && is_object($context) => $context->{$this->configurator}(...),
$this->configurator !== null && is_string($context) => $context::{$this->configurator}(...),
default => null,
};
$builder->element($this->elementType, $configurator);
}

#[Override]
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, AttributeForm $form): void
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, object|string $context): void
{
$elementType = new Literal($generator->useAndSimplifyType($this->elementType));

if ($this->configurator !== null) {
$generator->line('$?->element(?::class, [$form, ?]);', [$name, $elementType, $this->configurator]);
} else {
$generator->line('$?->element(?::class);', [$name, $elementType]);
}
match (true) {
$this->configurator !== null && is_object($context) => $generator->line('$?->element(?::class, $context->?(...));', [$name, $elementType, $this->configurator]),
$this->configurator !== null && is_string($context) => $generator->line('$?->element(?::class, ?::?(...));', [$name, $elementType, new Literal($generator->useAndSimplifyType($context)), $this->configurator]),
default => $generator->line('$?->element(?::class);', [$name, $elementType]),
};
}
}
21 changes: 21 additions & 0 deletions src/Attribute/Aggregate/Optional.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Bdf\Form\Attribute\Aggregate;

use Attribute;
use Bdf\Form\Aggregate\FormBuilderInterface;
use Bdf\Form\Attribute\BuilderMethodCall;

/**
* Define the embedded form element as optional
*
* @see FormBuilderInterface::optional() The called method
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final readonly class Optional extends BuilderMethodCall
{
public function __construct()
{
parent::__construct('optional', []);
}
}
26 changes: 26 additions & 0 deletions src/Attribute/Aggregate/StructElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Bdf\Form\Attribute\Aggregate;

use Attribute;
use Bdf\Form\Aggregate\ArrayElementBuilder;
use Bdf\Form\Attribute\BuilderMethodCall;
use Bdf\Form\Attribute\Element\StructClass;

/**
* Define the array element type as struct with the given class
*
* @see ArrayElementBuilder::struct() The called method
* @see StructClass To define the struct class on a simple property
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final readonly class StructElement extends BuilderMethodCall
{
/**
* @param class-string $className The struct class name
*/
public function __construct(string $className)
{
parent::__construct('struct', [$className]);
}
}
51 changes: 51 additions & 0 deletions src/Attribute/BuilderMethodCall.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Bdf\Form\Attribute;

use Attribute;
use Bdf\Form\Attribute\Processor\CodeGenerator\AttributesProcessorGenerator;
use Bdf\Form\Child\ChildBuilderInterface;
use Override;

use function array_is_list;

/**
* Call a method on the child/element builder
* The attribute can be overridden to simplify the declaration of common method calls on builders
*
* @implements ChildBuilderAttributeInterface<\Bdf\Form\ElementBuilderInterface>
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
readonly class BuilderMethodCall implements ChildBuilderAttributeInterface
{
public function __construct(
/**
* The method name to call
*
* @var non-empty-string
*/
public string $method,

/**
* Calling arguments.
* If an associative array is given, named arguments will be used to call the method
*/
public array $arguments,
) {}

#[Override]
public function applyOnChildBuilder(object|string $context, ChildBuilderInterface $builder): void
{
$builder->{$this->method}(...$this->arguments);
}

#[Override]
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, object|string $context): void
{
if (array_is_list($this->arguments)) {

Check warning on line 45 in src/Attribute/BuilderMethodCall.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "IfNegation": @@ @@ #[Override] public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, object|string $context): void { - if (array_is_list($this->arguments)) { + if (!array_is_list($this->arguments)) { $generator->line('$?->?(...?);', [$name, $this->method, $this->arguments]); } else { $generator->line('$?->?(...?:);', [$name, $this->method, $this->arguments]);
$generator->line('$?->?(...?);', [$name, $this->method, $this->arguments]);
} else {
$generator->line('$?->?(...?:);', [$name, $this->method, $this->arguments]);
}
}
}
8 changes: 4 additions & 4 deletions src/Attribute/Button/ButtonBuilderAttributeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ interface ButtonBuilderAttributeInterface
/**
* Configure the button builder
*
* @param AttributeForm $form The current form instance
* @param object|class-string $context The form instance or the DTO class name
* @param ButtonBuilderInterface $builder Builder to configure
*/
public function applyOnButtonBuilder(AttributeForm $form, ButtonBuilderInterface $builder): void;
public function applyOnButtonBuilder(object|string $context, ButtonBuilderInterface $builder): void;

/**
* Generate the code corresponding to the attribute
* The generated code must perform same action as `applyOnButtonBuilder()`
*
* @param AttributesProcessorGenerator $generator Code generator for the "configureBuilder" method
* @param AttributeForm $form The current form instance
* @param object|class-string $context The form instance or the DTO class name
*
* @return void
*/
public function generateCodeForButtonBuilder(AttributesProcessorGenerator $generator, AttributeForm $form): void;
public function generateCodeForButtonBuilder(AttributesProcessorGenerator $generator, object|string $context): void;
}
4 changes: 2 additions & 2 deletions src/Attribute/Button/Groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ public function __construct(string ...$groups)
}

#[Override]
public function applyOnButtonBuilder(AttributeForm $form, ButtonBuilderInterface $builder): void
public function applyOnButtonBuilder(object|string $context, ButtonBuilderInterface $builder): void
{
$builder->groups($this->groups);
}

#[Override]
public function generateCodeForButtonBuilder(AttributesProcessorGenerator $generator, AttributeForm $form): void
public function generateCodeForButtonBuilder(AttributesProcessorGenerator $generator, object|string $context): void
{
$generator->line(' ->groups(?)', [$this->groups]);
}
Expand Down
Loading
Loading