Callback   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 33
dl 0
loc 109
ccs 25
cts 25
cp 1
rs 10
c 1
b 0
f 0
wmc 13

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getMethod() 0 3 1
A getName() 0 3 1
A afterInitAttribute() 0 21 3
A __construct() 0 13 5
A getHandler() 0 3 1
A getCallback() 0 3 1
A getOptions() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Attribute;
8
use Closure;
9
use InvalidArgumentException;
10
use ReflectionObject;
11
use Yiisoft\Validator\AfterInitAttributeEventInterface;
12
use Yiisoft\Validator\Rule\Trait\SkipOnEmptyTrait;
13
use Yiisoft\Validator\Rule\Trait\SkipOnErrorTrait;
14
use Yiisoft\Validator\Rule\Trait\WhenTrait;
15
use Yiisoft\Validator\DumpedRuleInterface;
16
use Yiisoft\Validator\SkipOnEmptyInterface;
17
use Yiisoft\Validator\SkipOnErrorInterface;
18
use Yiisoft\Validator\WhenInterface;
19
20
/**
21
 * Defines validation options to validating the value using a callback.
22
 *
23
 * @see CallbackHandler
24
 *
25
 * @psalm-import-type WhenType from WhenInterface
26
 */
27
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
28
final class Callback implements
29
    DumpedRuleInterface,
30
    SkipOnErrorInterface,
31
    WhenInterface,
32
    SkipOnEmptyInterface,
33 16
    AfterInitAttributeEventInterface
34
{
35
    use SkipOnEmptyTrait;
36
    use SkipOnErrorTrait;
37
    use WhenTrait;
38
39
    /**
40
     * @param callable|null $callback Callable with the `function ($value, $rule, $context): Result` signature that
41
     * performs the validation. Mutually exclusive with {@see $method}.
42
     * @param string|null $method Name of a validated object method with the `function ($value, $rule, $context): Result`
43
     * signature that performs the validation. Mutually exclusive with {@see $callback}.
44
     * @param bool|callable|null $skipOnEmpty Whether to skip this rule if the value validated is empty.
45
     * See {@see SkipOnEmptyInterface}.
46
     * @param bool $skipOnError Whether to skip this rule if any of the previous rules gave an error.
47
     * See {@see SkipOnErrorInterface}.
48
     * @param Closure|null $when A callable to define a condition for applying the rule.
49
     * See {@see WhenInterface}.
50 16
     *
51 1
     * @psalm-param WhenType $when
52
     *
53
     * @throws InvalidArgumentException When neither {@see $callback} nor {@see $method} is specified or
54 15
     * both are specified at the same time.
55 1
     */
56
    public function __construct(
57
        private mixed $callback = null,
58
        private string|null $method = null,
59 1
        private mixed $skipOnEmpty = null,
60
        private bool $skipOnError = false,
61 1
        private Closure|null $when = null,
62
    ) {
63
        if ($this->callback === null && $this->method === null) {
64 17
            throw new InvalidArgumentException('Either "$callback" or "$method" must be specified.');
65
        }
66 17
67
        if ($this->callback !== null && $this->method !== null) {
68
            throw new InvalidArgumentException('"$callback" and "$method" are mutually exclusive.');
69 2
        }
70
    }
71 2
72
    public function getName(): string
73
    {
74 4
        return self::class;
75
    }
76 4
77 1
    /**
78
     * Get the callable that performs validation.
79
     *
80 3
     * @return callable|null The callable that performs validation.
81
     *
82 3
     * @see $callback
83 3
     */
84 1
    public function getCallback(): callable|null
85
    {
86
        return $this->callback;
87
    }
88
89
    /**
90
     * Get a name of a validated object method that performs the validation.
91
     *
92 2
     * @return string|null Name of a method that performs the validation.
93
     *
94
     * @see $method
95 3
     */
96
    public function getMethod(): string|null
97
    {
98 3
        return $this->method;
99 3
    }
100 3
101
    public function afterInitAttribute(object $object): void
102
    {
103
        if ($this->method === null) {
104 16
            return;
105
        }
106 16
107
        $method = $this->method;
108
109
        $reflection = new ReflectionObject($object);
110
        if (!$reflection->hasMethod($method)) {
111
            throw new InvalidArgumentException(
112
                sprintf(
113
                    'Method "%s" does not exist in class "%s".',
114
                    $method,
115
                    $object::class,
116
                )
117
            );
118
        }
119
120
        /** @psalm-suppress MixedMethodCall */
121
        $this->callback = Closure::bind(fn (mixed ...$args): mixed => $object->{$method}(...$args), $object, $object);
122
    }
123
124
    public function getOptions(): array
125
    {
126
        return [
127
            'method' => $this->method,
128
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
129
            'skipOnError' => $this->skipOnError,
130
        ];
131
    }
132
133
    public function getHandler(): string
134
    {
135
        return CallbackHandler::class;
136
    }
137
}
138