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
16 changes: 13 additions & 3 deletions src/Aggregate/ArrayElementBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public function satisfy(Constraint|callable $constraint, ?string $message = null
* Define a transformer on the inner element
*/
#[Override]
public function transformer(callable|TransformerInterface $transformer, bool $append = true): static
public function transformer(callable|TransformerInterface|string $transformer, bool $append = true): static
{
$this->getElementBuilder()->transformer($transformer, $append);

Expand Down Expand Up @@ -440,21 +440,31 @@ final public function required(string|Constraint|null $message = null, ?bool $al
*/
final public function choices(ChoiceInterface|array|string|callable $choices, ?string $message = null, ?bool $multiple = null, ?bool $strict = null, ?int $min = null, ?int $max = null, ?string $minMessage = null, ?string $maxMessage = null): static
{
/** @psalm-suppress MissingConstructor */
$builder = new class {
/** @psalm-suppress PropertyNotSetInConstructor */
$builder = new class($this->registry) {
use ChoiceBuilderTrait {
getChoices as public;
}

public ChoiceConstraint $constraint;

public function __construct(
private readonly RegistryInterface $registry,
) {}

#[Override]
public function satisfy(Constraint|callable $constraint, ?string $message = null, bool $append = true): static
{
assert($constraint instanceof ChoiceConstraint);
$this->constraint = $constraint;
return $this;
}

#[Override]
protected function registry(): RegistryInterface
{
return $this->registry;
}
};

// Force the multiple option to true
Expand Down
12 changes: 8 additions & 4 deletions src/Attribute/Aggregate/ArrayTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
* <code>
* class MyForm extends AttributeForm
* {
* #[ArrayTransformer(MyTransformer::class, ['foo', 'bar']), ElementType(IntegerElement::class)]
* #[ArrayTransformer(new MyTransformer(['foo', 'bar']), ElementType(IntegerElement::class)]
* private ArrayElement $foo;
*
* // Load the transformer from the registry/container
* #[ArrayTransformer(TransformerService::class)]
* private ArrayElement $bar;
* }
* </code>
*
Expand All @@ -35,11 +39,11 @@
class ArrayTransformer extends Transformer
{
/**
* @param class-string<TransformerInterface> $transformerClass The transformer class name
* @param class-string<TransformerInterface>|TransformerInterface $transformer The transformer class name or instance
* @param array $constructorArguments Arguments to provide on the transformer constructor
*/
public function __construct(string $transformerClass, array $constructorArguments = [])
public function __construct(string|TransformerInterface $transformer, array $constructorArguments = [])
{
parent::__construct($transformerClass, $constructorArguments, true);
parent::__construct($transformer, $constructorArguments, true);
}
}
8 changes: 2 additions & 6 deletions src/Attribute/Constraint/Satisfy.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,14 @@
*
* This attribute is equivalent to call :
* <code>
* $builder->integer('foo')->satisfy(MyConstraint::class, $options);
* $builder->integer('foo')->satisfy(new MyConstraint(foo: 'bar'));
* </code>
*
* Usage:
* <code>
* class MyForm extends AttributeForm
* {
* #[Satisfy(MyConstraint::class, ['foo' => 'bar'])]
* private IntegerElement $foo;
*
* // or on PHP 8.1
* #[Satisfy(new MyConstraint(['foo' => 'bar']))]
* #[Satisfy(new MyConstraint(foo: 'bar'))]
* private IntegerElement $foo;
* }
* </code>
Expand Down
10 changes: 7 additions & 3 deletions src/Attribute/Element/Choices.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* - a simple array of values (without labels)
* - an associative array for provide a label (in key), and inner value (in value)
* - a method name for resolving choices in lazy way
* - a choice class name to load from the registry/container
*
* Note: this attribute is not repeatable
*
Expand All @@ -50,6 +51,9 @@
* #[Choices('loadBazValues', 'Invalid value')]
* private StringElement $baz;
*
* #[Choices(MyChoices::class)]
* private StringElement $oof;
*
* // For dynamic choices, or with complex logic
* public function loadBazValues(): array
* {
Expand Down Expand Up @@ -86,7 +90,7 @@ public function __construct(
* If the value is an array, the key will be used as label (displayed value), and the value as real value
* The label is not required.
*
* @var literal-string|array
* @var literal-string|class-string<ChoiceInterface>|array
* @readonly
*/
private string|array $choices,
Expand Down Expand Up @@ -118,7 +122,7 @@ public function applyOnChildBuilder(object|string $context, ChildBuilderInterfac

$choices = $this->choices;

if (is_string($choices)) {
if (is_string($choices) && !class_exists($choices)) {
$choices = is_object($context)
? new LazyChoice($context->{$this->choices}(...))
: new LazyChoice($context::{$this->choices}(...))
Expand All @@ -139,7 +143,7 @@ public function generateCodeForChildBuilder(string $name, AttributesProcessorGen
$options['message'] = $this->message;
}

if (is_string($this->choices)) {
if (is_string($this->choices) && !class_exists($this->choices)) {
$generator->use(LazyChoice::class);

if (is_object($context)) {
Expand Down
116 changes: 104 additions & 12 deletions src/Attribute/Element/Transformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@
use Bdf\Form\Attribute\AttributeForm;
use Bdf\Form\Attribute\ChildBuilderAttributeInterface;
use Bdf\Form\Attribute\Processor\CodeGenerator\AttributesProcessorGenerator;
use Bdf\Form\Attribute\Processor\CodeGenerator\ObjectInstantiation;
use Bdf\Form\Attribute\Processor\GenerateConfiguratorStrategy;
use Bdf\Form\Child\ChildBuilderInterface;
use Bdf\Form\ElementBuilderInterface;
use Bdf\Form\Transformer\TransformerInterface;
use InvalidArgumentException;
use Nette\PhpGenerator\Literal;
use Override;

use function is_object;
use function is_string;
use function trigger_error;

/**
* Add a transformer on the element, using a transformer class
*
Expand All @@ -29,8 +35,12 @@
* <code>
* class MyForm extends AttributeForm
* {
* #[Transformer(MyTransformer::class, ['foo', 'bar'])]
* #[Transformer(new MyTransformer('foo', 'bar'))]
* private IntegerElement $foo;
*
* // Use a transformer loaded from the registry/container
* #[Transformer(TransformerService::class)]
* private IntegerElement $bar;
* }
* </code>
*
Expand All @@ -45,19 +55,30 @@
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class Transformer implements ChildBuilderAttributeInterface
{
/**
* The transformer class name or instance
*
* @var class-string<TransformerInterface>|TransformerInterface
*/
private readonly string|TransformerInterface $transformer;

/**
* @param class-string<TransformerInterface>|TransformerInterface|null $transformer
* @param class-string<TransformerInterface>|TransformerInterface|null $transformerClass
*/
public function __construct(
/**
* The transformer class name
* The transformer class name or instance
*
* @var class-string<TransformerInterface>
* @readonly
* @var class-string<TransformerInterface>|TransformerInterface|null
*/
private readonly string $transformerClass,
string|TransformerInterface|null $transformer = null,
/**
* Arguments to provide on the transformer constructor
* Arguments to provide on the transformer constructor.
* Only used if first parameter is a class name, and the transformer is instantiable
*
* @var array
* @readonly
* @deprecated Use object parameter instead
*/
private readonly array $constructorArguments = [],
/**
Expand All @@ -74,12 +95,47 @@
* @see ArrayTransformer Prefer use this attribute for array element, instead of manually set this flag
*/
private readonly bool $array = false,
) {}
/**
* @var class-string<TransformerInterface>|TransformerInterface
* @deprecated For compatiblity only. Use first parameter instead.
*/
string|TransformerInterface|null $transformerClass = null,
) {
$transformer ??= $transformerClass;

if ($transformerClass !== null) {

Check warning on line 106 in src/Attribute/Element/Transformer.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "NotIdentical": @@ @@ ) { $transformer ??= $transformerClass; - if ($transformerClass !== null) { + if ($transformerClass === null) { @trigger_error('The transformerClass parameter is deprecated since 2.0, use transformer parameter instead', E_USER_DEPRECATED); }
@trigger_error('The transformerClass parameter is deprecated since 2.0, use transformer parameter instead', E_USER_DEPRECATED);
}

if ($transformer === null) {
throw new InvalidArgumentException('The transformer parameter must not be null.');
}

$this->transformer = $transformer;

if ($this->constructorArguments) {
if (!is_string($this->transformer)) {
throw new \InvalidArgumentException('Constructor arguments can be used only with transformer class name');
}

@trigger_error('The constructorArguments parameter is deprecated since 2.0, use object parameter instead', E_USER_DEPRECATED);
}
}

#[Override]
public function applyOnChildBuilder(object|string $context, ChildBuilderInterface $builder): void
{
$transformer = new $this->transformerClass(...$this->constructorArguments);
$transformer = $this->transformer;

if (is_string($transformer)) {
$shouldBeInstantiated = !empty($this->constructorArguments) || self::canBeInstantiatedWithoutParameters($transformer);

if ($shouldBeInstantiated) {
@trigger_error('Passing a transformer class instead of object for inline instantiation is deprecated since 2.0, will use the registry in 3.0. Use object instead.', E_USER_DEPRECATED);

$transformer = new $transformer(...$this->constructorArguments);
}
}

if ($this->array) {
/** @var ChildBuilderInterface<ArrayElementBuilder> $builder */
Expand All @@ -92,9 +148,45 @@
#[Override]
public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, object|string $context): void
{
$transformer = $generator->useAndSimplifyType($this->transformerClass);
$code = $this->array ? '$?->arrayTransformer(new ?(...?));' : '$?->transformer(new ?(...?));';
$transformer = $this->transformer;

if (is_object($transformer)) {
$transformer = ObjectInstantiation::promotedProperties($transformer)->render($generator);
$code = $this->array ? '$?->arrayTransformer(?);' : '$?->transformer(?);';
$generator->line($code, [$name, $transformer]);
return;
}

// Transformer is the class name
$shouldBeInstantiated = !empty($this->constructorArguments) || self::canBeInstantiatedWithoutParameters($transformer);
$transformer = $generator->useAndSimplifyType($transformer);

if ($shouldBeInstantiated) {
@trigger_error('Passing a transformer class instead of object for inline instantiation is deprecated since 2.0, will use the registry in 3.0. Use object instead.', E_USER_DEPRECATED);

$code = $this->array ? '$?->arrayTransformer(new ?(...?));' : '$?->transformer(new ?(...?));';
$generator->line($code, [$name, new Literal($transformer), $this->constructorArguments]);
return;
}

$code = $this->array ? '$?->arrayTransformer(?::class);' : '$?->transformer(?::class);';
$generator->line($code, [$name, new Literal($transformer)]);
}

/**
* @param class-string $class
* @return bool
*/
private static function canBeInstantiatedWithoutParameters(string $class): bool
{
$r = new \ReflectionClass($class);

if (!$r->isInstantiable()) {

Check warning on line 184 in src/Attribute/Element/Transformer.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalNot": @@ @@ { $r = new \ReflectionClass($class); - if (!$r->isInstantiable()) { + if ($r->isInstantiable()) { return false; }
return false;
}

$constructor = $r->getConstructor();

$generator->line($code, [$name, new Literal($transformer), $this->constructorArguments]);
return $constructor === null || $constructor->getNumberOfRequiredParameters() === 0;

Check warning on line 190 in src/Attribute/Element/Transformer.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalOr": @@ @@ $constructor = $r->getConstructor(); - return $constructor === null || $constructor->getNumberOfRequiredParameters() === 0; + return $constructor === null && $constructor->getNumberOfRequiredParameters() === 0; } }
}
}
4 changes: 2 additions & 2 deletions src/Child/ChildBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -433,13 +433,13 @@
/**
* Forward call to element builder
*
* @param callable|TransformerInterface $transformer
* @param callable|TransformerInterface|class-string<TransformerInterface> $transformer
* @param bool $append
* @return $this
*
* @see ElementBuilderInterface::transformer()
*/
public function transformer(callable|TransformerInterface $transformer, bool $append = true): static
public function transformer(callable|TransformerInterface|string $transformer, bool $append = true): static

Check warning on line 442 in src/Child/ChildBuilder.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "PublicVisibility": @@ @@ * * @see ElementBuilderInterface::transformer() */ - public function transformer(callable|TransformerInterface|string $transformer, bool $append = true): static + protected function transformer(callable|TransformerInterface|string $transformer, bool $append = true): static { $this->elementBuilder->transformer($transformer, $append);
{
$this->elementBuilder->transformer($transformer, $append);

Expand Down
4 changes: 2 additions & 2 deletions src/Child/ChildBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,14 @@ public function depends(string ...$inputNames): static;
* });
* </code>
*
* @param callable|TransformerInterface $transformer The transformer
* @param callable|TransformerInterface|class-string<TransformerInterface> $transformer The transformer
* @param bool $append Append the transformer. Prepend if false
*
* @return $this
*
* @see TransformerInterface
*/
public function modelTransformer(callable|TransformerInterface $transformer, bool $append = true): static;
public function modelTransformer(callable|TransformerInterface|string $transformer, bool $append = true): static;

/**
* Creates the child instance
Expand Down
15 changes: 14 additions & 1 deletion src/Choice/ChoiceBuilderTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace Bdf\Form\Choice;

use BackedEnum;
use Bdf\Form\AbstractElementBuilder;
use Bdf\Form\ElementBuilderInterface;
use Bdf\Form\Registry\RegistryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Choice as ChoiceConstraint;

Expand Down Expand Up @@ -45,11 +47,14 @@
* // Using enum
* $builder->choices(MyEnum::class);
*
* // Using choice from service (e.g. if a container is used, the choice will be loaded from the container)
* $builder->choices(MyChoice::class);
*
* $builder->choices(['foo', 'bar'], 'my error'); // With message
* $builder->choices(['foo', 'bar'], min: 2, max: 6); // With custom options
* </code>
*
* @param ChoiceInterface|array|class-string<BackedEnum>|callable $choices The allowed values in PHP form.
* @param ChoiceInterface|array|class-string<BackedEnum|ChoiceInterface>|callable $choices The allowed values in PHP form.
* @param string|null $message The error message.
* @param non-negative-int|null $min
* @param positive-int|null $max
Expand All @@ -60,9 +65,10 @@
final public function choices(ChoiceInterface|array|string|callable $choices, ?string $message = null, ?bool $multiple = null, ?bool $strict = null, ?int $min = null, ?int $max = null, ?string $minMessage = null, ?string $maxMessage = null): static
{
if (!$choices instanceof ChoiceInterface) {
$choices = match (true) {

Check warning on line 68 in src/Choice/ChoiceBuilderTrait.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "MatchArmRemoval": @@ @@ is_array($choices) => new ArrayChoice($choices), is_string($choices) && is_subclass_of($choices, BackedEnum::class) => new EnumChoice($choices), is_string($choices) && is_subclass_of($choices, ChoiceInterface::class) => new LazyChoice(fn () => $this->registry()->service($choices)), - is_callable($choices) => new LazyChoice($choices), }; }
is_array($choices) => new ArrayChoice($choices),
is_string($choices) && is_subclass_of($choices, BackedEnum::class) => new EnumChoice($choices),
is_string($choices) && is_subclass_of($choices, ChoiceInterface::class) => new LazyChoice(fn () => $this->registry()->service($choices)),

Check warning on line 71 in src/Choice/ChoiceBuilderTrait.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "LogicalAnd": @@ @@ $choices = match (true) { is_array($choices) => new ArrayChoice($choices), is_string($choices) && is_subclass_of($choices, BackedEnum::class) => new EnumChoice($choices), - is_string($choices) && is_subclass_of($choices, ChoiceInterface::class) => new LazyChoice(fn () => $this->registry()->service($choices)), + is_string($choices) || is_subclass_of($choices, ChoiceInterface::class) => new LazyChoice(fn () => $this->registry()->service($choices)), is_callable($choices) => new LazyChoice($choices), }; }
is_callable($choices) => new LazyChoice($choices),
};
}
Expand Down Expand Up @@ -102,4 +108,11 @@
* @see ElementBuilderInterface::satisfy()
*/
abstract public function satisfy(Constraint|callable $constraint, ?string $message = null, bool $append = true): static;

/**
* {@inheritdoc}
*
* @see AbstractElementBuilder::registry()
*/
abstract protected function registry(): RegistryInterface;
}
2 changes: 1 addition & 1 deletion src/Csrf/CsrfElementBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
}

#[Override]
public function transformer(callable|TransformerInterface $transformer, bool $append = true): static
public function transformer(callable|TransformerInterface|string $transformer, bool $append = true): static

Check warning on line 139 in src/Csrf/CsrfElementBuilder.php

View workflow job for this annotation

GitHub Actions / Analysis

Escaped Mutant for Mutator "TrueValue": @@ @@ } #[Override] - public function transformer(callable|TransformerInterface|string $transformer, bool $append = true): static + public function transformer(callable|TransformerInterface|string $transformer, bool $append = false): static { throw new BadMethodCallException(); }
{
throw new BadMethodCallException();
}
Expand Down
Loading
Loading