Completed
Push — master ( e0017c...6b1304 )
by Tom
14s queued 11s
created

src/DoctrineModule/Validator/ObjectExists.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace DoctrineModule\Validator;
6
7
use Doctrine\Persistence\ObjectRepository;
8
use Laminas\Stdlib\ArrayUtils;
9
use Laminas\Validator\AbstractValidator;
10
use Laminas\Validator\Exception;
11
use function array_combine;
12
use function array_key_exists;
13
use function array_values;
14
use function count;
15
use function get_class;
16
use function gettype;
17
use function is_object;
18
use function is_string;
19
use function sprintf;
20
21
/**
22
 * Class that validates if objects exist in a given repository with a given list of matched fields
23
 *
24
 * @link    http://www.doctrine-project.org/
25
 */
26
class ObjectExists extends AbstractValidator
27
{
28
    /**
29
     * Error constants
30
     */
31
    public const ERROR_NO_OBJECT_FOUND = 'noObjectFound';
32
33
    /** @var mixed[] Message templates */
34
    protected $messageTemplates = [self::ERROR_NO_OBJECT_FOUND => "No object matching '%value%' was found"];
35
36
    /**
37
     * ObjectRepository from which to search for entities
38
     *
39
     * @var ObjectRepository
40
     */
41
    protected $objectRepository;
42
43
    /**
44
     * Fields to be checked
45
     *
46
     * @var mixed[]
47
     */
48
    protected $fields;
49
50
    /**
51
     * Constructor
52
     *
53
     * @param mixed[] $options required keys are `object_repository`, which must be an instance of
54
     *                       Doctrine\Persistence\ObjectRepository, and `fields`, with either
55
     *                       a string or an array of strings representing the fields to be matched by the validator.
56
     *
57
     * @throws Exception\InvalidArgumentException
58
     */
59 27
    public function __construct(array $options)
60
    {
61 27
        if (! isset($options['object_repository']) || ! $options['object_repository'] instanceof ObjectRepository) {
62 3
            if (! array_key_exists('object_repository', $options)) {
63 1
                $provided = 'nothing';
64
            } else {
65 2
                if (is_object($options['object_repository'])) {
66 1
                    $provided = get_class($options['object_repository']);
67
                } else {
68 1
                    $provided = gettype($options['object_repository']);
69
                }
70
            }
71
72 3
            throw new Exception\InvalidArgumentException(
73 3
                sprintf(
74
                    'Option "object_repository" is required and must be an instance of'
75 3
                    . ' Doctrine\Persistence\ObjectRepository, %s given',
76 3
                    $provided
77
                )
78
            );
79
        }
80
81 24
        $this->objectRepository = $options['object_repository'];
82
83 24
        if (! isset($options['fields'])) {
84 1
            throw new Exception\InvalidArgumentException(
85
                'Key `fields` must be provided and be a field or a list of fields to be used when searching for'
86 1
                . ' existing instances'
87
            );
88
        }
89
90 23
        $this->fields = $options['fields'];
0 ignored issues
show
Documentation Bug introduced by
It seems like $options['fields'] of type * is incompatible with the declared type array<integer,*> of property $fields.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
91 23
        $this->validateFields();
92
93 21
        parent::__construct($options);
94 21
    }
95
96
    /**
97
     * Filters and validates the fields passed to the constructor
98
     *
99
     * @return mixed[]
100
     *
101
     * @throws Exception\InvalidArgumentException
102
     */
103 23
    private function validateFields() : array
104
    {
105 23
        $fields = (array) $this->fields;
106
107 23
        if (empty($fields)) {
108 1
            throw new Exception\InvalidArgumentException('Provided fields list was empty!');
109
        }
110
111 22
        foreach ($fields as $key => $field) {
112 22
            if (! is_string($field)) {
113 1
                throw new Exception\InvalidArgumentException(
114 1
                    sprintf('Provided fields must be strings, %s provided for key %s', gettype($field), $key)
115
                );
116
            }
117
        }
118
119 21
        $this->fields = array_values($fields);
120
121 21
        return $this->fields;
122
    }
123
124
    /**
125
     * @param string|mixed[] $value a field value or an array of field values if more fields have been configured to be
126
     *                      matched
127
     *
128
     * @return mixed[]
129
     *
130
     * @throws Exception\RuntimeException
131
     */
132 19
    protected function cleanSearchValue($value) : array
133
    {
134 19
        $value = is_object($value) ? [$value] : (array) $value;
135
136 19
        if (ArrayUtils::isHashTable($value)) {
137 5
            $matchedFieldsValues = [];
138
139 5
            foreach ($this->fields as $field) {
140 5
                if (! array_key_exists($field, $value)) {
141 1
                    throw new Exception\RuntimeException(
142 1
                        sprintf(
143
                            'Field "%s" was not provided, but was expected since the configured field lists needs'
144 1
                            . ' it for validation',
145 1
                            $field
146
                        )
147
                    );
148
                }
149
150 5
                $matchedFieldsValues[$field] = $value[$field];
151
            }
152
        } else {
153 16
            $matchedFieldsValues = @array_combine($this->fields, $value);
154
155 16
            if ($matchedFieldsValues === false) {
156 1
                throw new Exception\RuntimeException(
157 1
                    sprintf(
158 1
                        'Provided values count is %s, while expected number of fields to be matched is %s',
159 1
                        count($value),
160 1
                        count($this->fields)
161
                    )
162
                );
163
            }
164
        }
165
166 17
        return $matchedFieldsValues;
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172 6
    public function isValid($value)
173
    {
174 6
        $cleanedValue = $this->cleanSearchValue($value);
175 4
        $match        = $this->objectRepository->findOneBy($cleanedValue);
176
177 4
        if (is_object($match)) {
178 2
            return true;
179
        }
180
181 2
        $this->error(self::ERROR_NO_OBJECT_FOUND, $value);
182
183 2
        return false;
184
    }
185
}
186