1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of the zibios/sharep. |
7
|
|
|
* |
8
|
|
|
* (c) Zbigniew Ślązak |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace App\Validator\Constraints; |
12
|
|
|
|
13
|
|
|
use App\Entity\EntityInterface; |
14
|
|
|
use App\Enum\Functional\ApplicationEnum; |
15
|
|
|
use App\Repository\Functional\NotOverlappedDatesRepository; |
16
|
|
|
use Symfony\Component\Validator\Constraint; |
17
|
|
|
use Symfony\Component\Validator\ConstraintValidator; |
18
|
|
|
use Symfony\Component\Validator\Exception\InvalidArgumentException; |
19
|
|
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException; |
20
|
|
|
use Symfony\Component\Validator\Exception\UnexpectedValueException; |
21
|
|
|
|
22
|
|
|
class NotOverlappedDatesValidator extends ConstraintValidator |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* @var NotOverlappedDatesRepository |
26
|
|
|
*/ |
27
|
|
|
private $repository; |
28
|
|
|
|
29
|
4 |
|
public function __construct(NotOverlappedDatesRepository $repository) |
30
|
|
|
{ |
31
|
4 |
|
$this->repository = $repository; |
32
|
4 |
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param EntityInterface $entity |
36
|
|
|
* @param NotOverlappedDates|Constraint $constraint |
37
|
|
|
*/ |
38
|
4 |
|
public function validate($entity, Constraint $constraint): void |
39
|
|
|
{ |
40
|
4 |
|
assert($entity instanceof EntityInterface); |
41
|
4 |
|
assert($constraint instanceof NotOverlappedDates); |
42
|
|
|
|
43
|
4 |
|
$this->assertInstances($entity, $constraint); |
44
|
4 |
|
$this->assertConstraintProperties($entity, $constraint); |
45
|
4 |
|
$this->assertDatesInstanceOf($entity, $constraint); |
46
|
|
|
|
47
|
4 |
|
if (false === $this->validateDatesNotEmpty($entity, $constraint)) { |
48
|
|
|
return; |
49
|
|
|
} |
50
|
|
|
|
51
|
4 |
|
if (false === $this->validateDatesOrder($entity, $constraint)) { |
52
|
|
|
return; |
53
|
|
|
} |
54
|
|
|
|
55
|
4 |
|
$overlappedEntities = $this->repository->getOverlappedEntities( |
56
|
4 |
|
\get_class($entity), |
57
|
4 |
|
$constraint->fromDateProperty, |
58
|
4 |
|
$constraint->toDateProperty, |
59
|
4 |
|
$this->getFromDate($entity, $constraint), |
60
|
4 |
|
$this->getToDate($entity, $constraint) |
61
|
|
|
); |
62
|
|
|
|
63
|
4 |
|
if (1 === \count($overlappedEntities) && $overlappedEntities[0]->getId() === $entity->getId()) { |
64
|
1 |
|
return; |
65
|
|
|
} |
66
|
|
|
|
67
|
4 |
|
if (\count($overlappedEntities) > 0) { |
68
|
|
|
$periodsString = ''; |
69
|
|
|
foreach ($overlappedEntities as $overlappedEntity) { |
70
|
|
|
$periodsString .= sprintf( |
71
|
|
|
'%s - %s,', |
72
|
|
|
$this->getFromDateString($overlappedEntity, $constraint), |
73
|
|
|
$this->getToDateString($overlappedEntity, $constraint) |
74
|
|
|
); |
75
|
|
|
} |
76
|
|
|
$this->context->buildViolation($constraint::INVALID_PERIOD_OVERLAPPED) |
77
|
|
|
->setParameter('{{ fromDate }}', $this->getFromDateString($entity, $constraint)) |
78
|
|
|
->setParameter('{{ toDate }}', $this->getToDateString($entity, $constraint)) |
79
|
|
|
->setParameter('{{ periods }}', $periodsString) |
80
|
|
|
->atPath($constraint->toDateProperty) |
81
|
|
|
->addViolation(); |
82
|
|
|
} |
83
|
4 |
|
} |
84
|
|
|
|
85
|
|
|
//------------------------------------------------------------------------------------------------------------------ |
86
|
|
|
|
87
|
4 |
|
private function assertInstances($entity, Constraint $constraint): void |
88
|
|
|
{ |
89
|
4 |
|
assert($entity instanceof EntityInterface); |
90
|
4 |
|
assert($constraint instanceof NotOverlappedDates); |
91
|
|
|
|
92
|
4 |
|
if (!$constraint instanceof NotOverlappedDates) { |
93
|
|
|
throw new UnexpectedTypeException($constraint, NotOverlappedDates::class); |
94
|
|
|
} |
95
|
4 |
|
if (!$entity instanceof EntityInterface) { |
96
|
|
|
throw new UnexpectedValueException($entity, EntityInterface::class); |
97
|
|
|
} |
98
|
4 |
|
} |
99
|
|
|
|
100
|
4 |
|
private function assertConstraintProperties(EntityInterface $entity, NotOverlappedDates $constraint): void |
101
|
|
|
{ |
102
|
4 |
|
if (!method_exists($entity, $constraint->fromDateMethod)) { |
103
|
|
|
throw new InvalidArgumentException( |
104
|
|
|
sprintf('Method \'%s\' not exist \'%s\'', $constraint->fromDateMethod, \get_class($entity)) |
105
|
|
|
); |
106
|
|
|
} |
107
|
4 |
View Code Duplication |
if (!\is_callable([$entity, $constraint->fromDateMethod])) { |
|
|
|
|
108
|
|
|
throw new InvalidArgumentException( |
109
|
|
|
sprintf('Method \'%s\' not public \'%s\'', $constraint->fromDateMethod, \get_class($entity)) |
110
|
|
|
); |
111
|
|
|
} |
112
|
4 |
View Code Duplication |
if (!property_exists($entity, $constraint->fromDateProperty)) { |
|
|
|
|
113
|
|
|
throw new InvalidArgumentException( |
114
|
|
|
sprintf('Property \'%s\' not exist \'%s\'', $constraint->fromDateProperty, \get_class($entity)) |
115
|
|
|
); |
116
|
|
|
} |
117
|
|
|
|
118
|
4 |
View Code Duplication |
if (!method_exists($entity, $constraint->toDateMethod)) { |
|
|
|
|
119
|
|
|
throw new InvalidArgumentException( |
120
|
|
|
sprintf('Method \'%s\' not exist in \'%s\'', $constraint->toDateMethod, \get_class($entity)) |
121
|
|
|
); |
122
|
|
|
} |
123
|
4 |
View Code Duplication |
if (!\is_callable([$entity, $constraint->toDateMethod])) { |
|
|
|
|
124
|
|
|
throw new InvalidArgumentException( |
125
|
|
|
sprintf('Method \'%s\' not public \'%s\'', $constraint->toDateMethod, \get_class($entity)) |
126
|
|
|
); |
127
|
|
|
} |
128
|
4 |
View Code Duplication |
if (!property_exists($entity, $constraint->toDateProperty)) { |
|
|
|
|
129
|
|
|
throw new InvalidArgumentException( |
130
|
|
|
sprintf('Property \'%s\' not exist \'%s\'', $constraint->toDateProperty, \get_class($entity)) |
131
|
|
|
); |
132
|
|
|
} |
133
|
4 |
|
} |
134
|
|
|
|
135
|
4 |
|
private function assertDatesInstanceOf(EntityInterface $entity, NotOverlappedDates $constraint): void |
136
|
|
|
{ |
137
|
4 |
|
$fromDate = $entity->{$constraint->fromDateMethod}(); |
138
|
4 |
|
if (null !== $fromDate && !$fromDate instanceof \DateTimeInterface) { |
139
|
|
|
throw new UnexpectedValueException($fromDate, \DateTimeInterface::class); |
140
|
|
|
} |
141
|
4 |
|
$toDate = $entity->{$constraint->toDateMethod}(); |
142
|
4 |
|
if (null !== $toDate && !$toDate instanceof \DateTimeInterface) { |
143
|
|
|
throw new UnexpectedValueException($toDate, \DateTimeInterface::class); |
144
|
|
|
} |
145
|
4 |
|
} |
146
|
|
|
|
147
|
4 |
|
private function validateDatesNotEmpty(EntityInterface $entity, NotOverlappedDates $constraint): bool |
|
|
|
|
148
|
|
|
{ |
149
|
4 |
|
$result = true; |
150
|
4 |
View Code Duplication |
if (!$this->getFromDate($entity, $constraint) instanceof \DateTimeInterface) { |
|
|
|
|
151
|
|
|
$this->context->buildViolation($constraint::INVALID_FROM_DATE) |
152
|
|
|
->setParameter('{{ fromDate }}', 'N/A') |
153
|
|
|
->atPath($constraint->fromDateProperty) |
154
|
|
|
->addViolation(); |
155
|
|
|
$result = false; |
156
|
|
|
} |
157
|
4 |
View Code Duplication |
if (!$this->getToDate($entity, $constraint) instanceof \DateTimeInterface) { |
|
|
|
|
158
|
|
|
$this->context->buildViolation($constraint::INVALID_TO_DATE) |
159
|
|
|
->setParameter('{{ toDate }}', 'N/A') |
160
|
|
|
->atPath($constraint->toDateProperty) |
161
|
|
|
->addViolation(); |
162
|
|
|
$result = false; |
163
|
|
|
} |
164
|
|
|
|
165
|
4 |
|
return $result; |
166
|
|
|
} |
167
|
|
|
|
168
|
4 |
|
private function validateDatesOrder(EntityInterface $entity, NotOverlappedDates $constraint): bool |
|
|
|
|
169
|
|
|
{ |
170
|
4 |
|
$fromDateString = $this->getFromDateString($entity, $constraint); |
171
|
4 |
|
$toDateString = $this->getToDateString($entity, $constraint); |
172
|
|
|
|
173
|
4 |
View Code Duplication |
if ($fromDateString >= $toDateString) { |
|
|
|
|
174
|
|
|
$this->context->buildViolation($constraint::INVALID_ORDER) |
175
|
|
|
->setParameter('{{ fromDate }}', $fromDateString) |
176
|
|
|
->setParameter('{{ toDate }}', $toDateString) |
177
|
|
|
->atPath($constraint->toDateProperty) |
178
|
|
|
->addViolation(); |
179
|
|
|
|
180
|
|
|
return false; |
181
|
|
|
} |
182
|
|
|
|
183
|
4 |
|
return true; |
184
|
|
|
} |
185
|
|
|
|
186
|
4 |
|
private function getFromDate(EntityInterface $entity, NotOverlappedDates $constraint): \DateTimeInterface |
187
|
|
|
{ |
188
|
4 |
|
return $entity->{$constraint->fromDateMethod}(); |
189
|
|
|
} |
190
|
|
|
|
191
|
4 |
|
private function getToDate(EntityInterface $entity, NotOverlappedDates $constraint): \DateTimeInterface |
192
|
|
|
{ |
193
|
4 |
|
return $entity->{$constraint->toDateMethod}(); |
194
|
|
|
} |
195
|
|
|
|
196
|
4 |
|
private function getFromDateString(EntityInterface $entity, NotOverlappedDates $constraint): string |
197
|
|
|
{ |
198
|
4 |
|
return $this->getFromDate($entity, $constraint)->format(ApplicationEnum::DATE_FORMAT); |
199
|
|
|
} |
200
|
|
|
|
201
|
4 |
|
private function getToDateString(EntityInterface $entity, NotOverlappedDates $constraint): string |
202
|
|
|
{ |
203
|
4 |
|
return $this->getToDate($entity, $constraint)->format(ApplicationEnum::DATE_FORMAT); |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.