Passed
Pull Request — master (#489)
by
unknown
12:16 queued 09:47
created

Callback::getOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
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\RuleWithOptionsInterface;
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
    RuleWithOptionsInterface,
30
    SkipOnErrorInterface,
31
    WhenInterface,
32
    SkipOnEmptyInterface,
33 16
    AfterInitAttributeEventInterface
34
{
35
    use SkipOnEmptyTrait;
36
    use SkipOnErrorTrait;
37
    use WhenTrait;
38
39
    /**
40
     * @var object|null The object being validated. `null` if PHP attributes aren't used.
41
     */
42
    private ?object $validatedObject = null;
43
44
    /**
45
     * @param callable|null $callback Callable with the `function ($value, $rule, $context): Result` signature that
46
     * performs the validation.
47
     * @param string|null $method Name of a validated object method with the `function ($value, $rule, $context): Result`
48
     * signature that performs the validation.
49
     * @param bool|callable|null $skipOnEmpty Whether to skip this rule if the value validated is empty.
50 16
     * See {@see SkipOnEmptyInterface}.
51 1
     * @param bool $skipOnError Whether to skip this rule if any of the previous rules gave an error.
52
     * See {@see SkipOnErrorInterface}.
53
     * @param Closure|null $when A callable to define a condition for applying the rule.
54 15
     * See {@see WhenInterface}.
55 1
     * @psalm-param WhenType $when
56
     *
57
     * @throws InvalidArgumentException When neither {@see $callback} nor {@see $method} is specified or
58
     * both are specified at the same time.
59 1
     */
60
    public function __construct(
61 1
        private mixed $callback = null,
62
        private string|null $method = null,
63
        private mixed $skipOnEmpty = null,
64 17
        private bool $skipOnError = false,
65
        private Closure|null $when = null,
66 17
    ) {
67
        if ($this->callback === null && $this->method === null) {
68
            throw new InvalidArgumentException('Either "$callback" or "$method" must be specified.');
69 2
        }
70
71 2
        if ($this->callback !== null && $this->method !== null) {
72
            throw new InvalidArgumentException('"$callback" and "$method" are mutually exclusive.');
73
        }
74 4
    }
75
76 4
    public function getName(): string
77 1
    {
78
        return 'callback';
79
    }
80 3
81
    /**
82 3
     * Get the callable that performs validation.
83 3
     *
84 1
     * @return callable|null The callable that performs validation.
85
     *
86
     * @see $callback
87
     */
88
    public function getCallback(): callable|null
89
    {
90
        return $this->callback;
91
    }
92 2
93
    /**
94
     * Get a name of a validated object method that performs the validation.
95 3
     *
96
     * @return string|null Name of a method that performs the validation.
97
     *
98 3
     * @see $method
99 3
     */
100 3
    public function getMethod(): string|null
101
    {
102
        return $this->method;
103
    }
104 16
105
    /**
106 16
     * Get object being validated.
107
     *
108
     * @return object|null Object being validated. Null if PHP attributes aren't used.
109
     *
110
     * @see $validatedObject
111
     */
112
    public function getValidatedObject(): ?object
113
    {
114
        return $this->validatedObject;
115
    }
116
117
    public function afterInitAttribute(object $object, int $target): void
118
    {
119
        if ($target === Attribute::TARGET_CLASS) {
120
            $this->validatedObject = $object;
121
        }
122
123
        if ($this->method === null) {
124
            return;
125
        }
126
127
        $method = $this->method;
128
129
        $reflection = new ReflectionObject($object);
130
        if (!$reflection->hasMethod($method)) {
131
            throw new InvalidArgumentException(
132
                sprintf(
133
                    'Method "%s" does not exist in class "%s".',
134
                    $method,
135
                    $object::class,
136
                )
137
            );
138
        }
139
140
        /** @psalm-suppress MixedMethodCall */
141
        $this->callback = Closure::bind(fn (mixed ...$args): mixed => $object->{$method}(...$args), $object, $object);
142
    }
143
144
    public function getOptions(): array
145
    {
146
        return [
147
            'method' => $this->method,
148
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
149
            'skipOnError' => $this->skipOnError,
150
        ];
151
    }
152
153
    public function getHandler(): string
154
    {
155
        return CallbackHandler::class;
156
    }
157
}
158