CallbackConstraint::applyOnChildBuilder()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
namespace Bdf\Form\Attribute\Constraint;
4
5
use Attribute;
6
use Bdf\Form\Attribute\AttributeForm;
7
use Bdf\Form\Attribute\ChildBuilderAttributeInterface;
8
use Bdf\Form\Attribute\Processor\CodeGenerator\AttributesProcessorGenerator;
9
use Bdf\Form\Attribute\Processor\GenerateConfiguratorStrategy;
10
use Bdf\Form\Child\ChildBuilderInterface;
11
use Bdf\Form\Constraint\Closure;
12
use Bdf\Form\ElementBuilderInterface;
13
use Nette\PhpGenerator\Literal;
14
use Symfony\Component\Validator\Constraint;
15
16
/**
17
 * Define a custom constraint for an element, using a validation method
18
 *
19
 * Note: prefer the usage of constraint class, declared as attribute
20
 *
21
 * This attribute is equivalent to call :
22
 * <code>
23
 * $builder->integer('foo')->satisfy([$this, 'validateFoo'], 'Foo is invalid');
24
 * </code>
25
 *
26
 * Usage:
27
 * <code>
28
 * class MyForm extends AttributeForm
29
 * {
30
 *     #[CustomConstraint('validateFoo', message: 'Foo is invalid')]
31
 *     private IntegerElement $foo;
32
 *
33
 *     public function validateFoo($value, ElementInterface $input): bool
34
 *     {
35
 *         return $value % 5 > 2;
36
 *     }
37
 * }
38
 * </code>
39
 *
40
 * @implements ChildBuilderAttributeInterface<\Bdf\Form\ElementBuilderInterface>
41
 *
42
 * @see ElementBuilderInterface::satisfy() The called method
43
 * @see Constraint
44
 * @see Closure The used constraint
45
 */
46
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
47
final class CallbackConstraint implements ChildBuilderAttributeInterface
48
{
49 5
    public function __construct(
50
        /**
51
         * The method name to use as validator
52
         * Must be a public method declared on the form class
53
         *
54
         * Its prototype should be :
55
         * `public function ($value, ElementInterface $input): bool|string|array{code: string message: string}|null`
56
         *
57
         * - Return true, or null (nothing) for a valid input
58
         * - Return false for invalid input, with the default error message (or the declared one)
59
         * - Return string for a custom error message
60
         * - Return array with error message and code
61
         *
62
         * @var literal-string
63
         * @readonly
64
         */
65
        private string $methodName,
66
        /**
67
         * The error message to use
68
         * This option is used only if the validator return false, in other cases,
69
         * the message returned by the validator will be used
70
         *
71
         * @var string|null
72
         * @readonly
73
         */
74
        private ?string $message = null,
75
    ) {
76 5
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 1
    public function applyOnChildBuilder(AttributeForm $form, ChildBuilderInterface $builder): void
82
    {
83 1
        $constraint = new Closure(['callback' => [$form, $this->methodName]]);
84
85 1
        if ($this->message) {
86 1
            $constraint->message = $this->message;
87
        }
88
89 1
        $builder->satisfy($constraint);
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 4
    public function generateCodeForChildBuilder(string $name, AttributesProcessorGenerator $generator, AttributeForm $form): void
96
    {
97 4
        $generator->use(Closure::class, 'ClosureConstraint');
98
99 4
        $parameters = $this->message
100 2
            ? new Literal("['callback' => [\$form, ?], 'message' => ?]", [$this->methodName, $this->message])
101 3
            : new Literal('[$form, ?]', [$this->methodName])
102 4
        ;
103
104 4
        $generator->line('$?->satisfy(new ClosureConstraint(?));', [$name, $parameters]);
105
    }
106
}
107