UniqueObject::__construct()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0106

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 14
cts 15
cp 0.9333
rs 8.8657
c 0
b 0
f 0
cc 6
nc 5
nop 1
crap 6.0106
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DoctrineModule\Validator;
6
7
use Doctrine\Persistence\ObjectManager;
8
use Laminas\Validator\Exception;
9
use function array_diff_assoc;
10
use function array_key_exists;
11
use function count;
12
use function get_class;
13
use function gettype;
14
use function is_object;
15
use function sprintf;
16
17
/**
18
 * Class that validates if objects exist in a given repository with a given list of matched fields only once.
19
 */
20
class UniqueObject extends ObjectExists
21
{
22
    /**
23
     * Error constants
24
     */
25
    public const ERROR_OBJECT_NOT_UNIQUE = 'objectNotUnique';
26
27
    // phpcs:disable Generic.Files.LineLength
28
    /** @var mixed[] */
29
    protected $messageTemplates = [self::ERROR_OBJECT_NOT_UNIQUE => "There is already another object matching '%value%'"];
30
    // phpcs:enable Generic.Files.LineLength
31
32
    /** @var ObjectManager */
33
    protected $objectManager;
34
35
    /** @var bool */
36
    protected $useContext;
37
38
    /***
39
     * Constructor
40
     *
41
     * @param mixed[] $options required keys are `object_repository`, which must be an instance of
42
     *                       Doctrine\Persistence\ObjectRepository, `object_manager`, which
43
     *                       must be an instance of Doctrine\Persistence\ObjectManager,
44
     *                       and `fields`, with either a string or an array of strings representing
45
     *                       the fields to be matched by the validator.
46
     *
47
     * @throws Exception\InvalidArgumentException
48
     */
49 12
    public function __construct(array $options)
50
    {
51 12
        parent::__construct($options);
52
53 12
        if (! isset($options['object_manager']) || ! $options['object_manager'] instanceof ObjectManager) {
54 2
            if (! array_key_exists('object_manager', $options)) {
55 1
                $provided = 'nothing';
56
            } else {
57 1
                if (is_object($options['object_manager'])) {
58 1
                    $provided = get_class($options['object_manager']);
59
                } else {
60
                    $provided = gettype($options['object_manager']);
61
                }
62
            }
63
64 2
            throw new Exception\InvalidArgumentException(
65 2
                sprintf(
66
                    'Option "object_manager" is required and must be an instance of'
67 2
                    . ' Doctrine\Persistence\ObjectManager, %s given',
68 2
                    $provided
69
                )
70
            );
71
        }
72
73 10
        $this->objectManager = $options['object_manager'];
74 10
        $this->useContext    = isset($options['use_context']) ? (bool) $options['use_context'] : false;
75 10
    }
76
77
    /**
78
     * Returns false if there is another object with the same field values but other identifiers.
79
     *
80
     * @param mixed $value
81
     * @param mixed $context
82
     */
83 10
    public function isValid($value, $context = null) : bool
84
    {
85 10
        if (! $this->useContext) {
86 5
            $context = (array) $value;
87
        }
88
89 10
        $cleanedValue = $this->cleanSearchValue($value);
90 10
        $match        = $this->objectRepository->findOneBy($cleanedValue);
91
92 10
        if (! is_object($match)) {
93 2
            return true;
94
        }
95
96 8
        $expectedIdentifiers = $this->getExpectedIdentifiers($context);
97 5
        $foundIdentifiers    = $this->getFoundIdentifiers($match);
98
99 5
        if (count(array_diff_assoc($expectedIdentifiers, $foundIdentifiers)) === 0) {
100 3
            return true;
101
        }
102
103 2
        $this->error(self::ERROR_OBJECT_NOT_UNIQUE, $value);
104
105 2
        return false;
106
    }
107
108
    /**
109
     * Gets the identifiers from the matched object.
110
     *
111
     * @return mixed[]
112
     *
113
     * @throws Exception\RuntimeException
114
     */
115 5
    protected function getFoundIdentifiers(object $match) : array
116
    {
117 5
        return $this->objectManager
118 5
                    ->getClassMetadata($this->objectRepository->getClassName())
119 5
                    ->getIdentifierValues($match);
120
    }
121
122
    /**
123
     * Gets the identifiers from the context.
124
     *
125
     * @param  mixed[]|object $context
126
     *
127
     * @return mixed[]
128
     *
129
     * @throws Exception\RuntimeException
130
     */
131 8
    protected function getExpectedIdentifiers($context = null) : array
132
    {
133 8
        if ($context === null) {
134 1
            throw new Exception\RuntimeException(
135 1
                'Expected context to be an array but is null'
136
            );
137
        }
138
139 7
        $className = $this->objectRepository->getClassName();
140
141 7
        if ($context instanceof $className) {
142 1
            return $this->objectManager
143 1
                        ->getClassMetadata($this->objectRepository->getClassName())
144 1
                        ->getIdentifierValues($context);
145
        }
146
147 6
        $result = [];
148 6
        foreach ($this->getIdentifiers() as $identifierField) {
149 6
            if (! array_key_exists($identifierField, $context)) {
150 2
                throw new Exception\RuntimeException(sprintf('Expected context to contain %s', $identifierField));
151
            }
152
153 4
            $result[$identifierField] = $context[$identifierField];
154
        }
155
156 4
        return $result;
157
    }
158
159
    /**
160
     * @return mixed[] the names of the identifiers
161
     */
162 6
    protected function getIdentifiers() : array
163
    {
164 6
        return $this->objectManager
165 6
                    ->getClassMetadata($this->objectRepository->getClassName())
166 6
                    ->getIdentifierFieldNames();
167
    }
168
}
169