Passed
Pull Request — master (#489)
by Alexander
02:38
created

Callback::getValidatedObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
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.
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
     * @throws InvalidArgumentException When either no callback or method is specified or
57
     * both are specified at the same time.
58
     */
59 1
    public function __construct(
60
        private mixed $callback = null,
61 1
        private string|null $method = null,
62
        private mixed $skipOnEmpty = null,
63
        private bool $skipOnError = false,
64 17
        private Closure|null $when = null,
65
    ) {
66 17
        if ($this->callback === null && $this->method === null) {
67
            throw new InvalidArgumentException('Either "$callback" or "$method" must be specified.');
68
        }
69 2
70
        if ($this->callback !== null && $this->method !== null) {
71 2
            throw new InvalidArgumentException('"$callback" and "$method" are mutually exclusive.');
72
        }
73
    }
74 4
75
    public function getName(): string
76 4
    {
77 1
        return 'callback';
78
    }
79
80 3
    /**
81
     * Get the callable that performs validation.
82 3
     *
83 3
     * @return callable|null The callable that performs validation.
84 1
     *
85
     * @see $callback
86
     */
87
    public function getCallback(): callable|null
88
    {
89
        return $this->callback;
90
    }
91
92 2
    /**
93
     * Get a name of a validated object method that performs the validation.
94
     *
95 3
     * @return string|null Name of a method that performs the validation.
96
     *
97
     * @see $method
98 3
     */
99 3
    public function getMethod(): string|null
100 3
    {
101
        return $this->method;
102
    }
103
104 16
    /**
105
     * Get object being validated.
106 16
     *
107
     * @return object|null Object being validated.
108
     *
109
     * @see $validatedObject
110
     */
111
    public function getValidatedObject(): ?object
112
    {
113
        return $this->validatedObject;
114
    }
115
116
    public function afterInitAttribute(object $object, int $target): void
117
    {
118
        if ($target === Attribute::TARGET_CLASS) {
119
            $this->validatedObject = $object;
120
        }
121
122
        if ($this->method === null) {
123
            return;
124
        }
125
126
        $method = $this->method;
127
128
        $reflection = new ReflectionObject($object);
129
        if (!$reflection->hasMethod($method)) {
130
            throw new InvalidArgumentException(
131
                sprintf(
132
                    'Method "%s" does not exist in class "%s".',
133
                    $method,
134
                    $object::class,
135
                )
136
            );
137
        }
138
139
        /** @psalm-suppress MixedMethodCall */
140
        $this->callback = Closure::bind(fn (mixed ...$args): mixed => $object->{$method}(...$args), $object, $object);
141
    }
142
143
    public function getOptions(): array
144
    {
145
        return [
146
            'method' => $this->method,
147
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
148
            'skipOnError' => $this->skipOnError,
149
        ];
150
    }
151
152
    public function getHandler(): string
153
    {
154
        return CallbackHandler::class;
155
    }
156
}
157