|
@@ -0,0 +1,167 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Filter\Doctrine\TimeConstraint;
|
|
|
|
|
+
|
|
|
|
|
+use App\Service\Constraint\ActivityYearConstraint;
|
|
|
|
|
+use App\Service\Constraint\DateTimeConstraint;
|
|
|
|
|
+use App\Service\Constraint\TimeConstraintInterface;
|
|
|
|
|
+use Doctrine\ORM\Mapping\ClassMetadata;
|
|
|
|
|
+use Doctrine\ORM\Query\Filter\SQLFilter;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Applique les contraintes temporelles aux entités possédant l'annotation requise.
|
|
|
|
|
+ */
|
|
|
|
|
+abstract class AbstractTimeFilter extends SQLFilter
|
|
|
|
|
+{
|
|
|
|
|
+ protected bool $disabled = false;
|
|
|
|
|
+ protected ?int $accessId = null;
|
|
|
|
|
+ protected ?TimeConstraintInterface $timeConstraint = null;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Annotation expected, have to be re-defined in subclasses.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected static ?string $constraintAnnotation = null;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Parameter of the annotation that design the field containing the starting date of the period
|
|
|
|
|
+ * Have to be re-defined in subclasses.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected static ?string $annotationStartField = null;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Parameter of the annotation that design the field containing the ending date of the period
|
|
|
|
|
+ * Have to be re-defined in subclasses.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected static ?string $annotationEndField = null;
|
|
|
|
|
+
|
|
|
|
|
+ public function setDisabled(bool $disabled): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->disabled = $disabled;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function isDisabled(): bool
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->disabled;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function setAccessId(int $accessId): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->accessId = $accessId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function setTimeConstraint(TimeConstraintInterface $timeConstraint): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->timeConstraint = $timeConstraint;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Retourne les noms des champs contenant les dates de début et de fin de la période de la ressource testée.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<string, string> $arguments
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return array<string>
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function getStartAndEndFields(array $arguments): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if (null === static::$annotationStartField || null === static::$annotationEndField) {
|
|
|
|
|
+ throw new \RuntimeException('Constraint annotation has not been properly configured');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $startField = $arguments[static::$annotationStartField] ?? null;
|
|
|
|
|
+ $endField = $arguments[static::$annotationEndField] ?? null;
|
|
|
|
|
+
|
|
|
|
|
+ return [$startField, $endField];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Invoke the TimeConstraintUtils to retrieve the active constraints for the given user.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return array<string, array<string, list<int>>>
|
|
|
|
|
+ *
|
|
|
|
|
+ * @throws \Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function getConstraints(int $accessId): array
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->timeConstraint->invoke($accessId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Méthode surchargée de SQLFilter permettant d'appliquer un filtre supplémentaire aux requêtes SQL.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $targetTableAlias
|
|
|
|
|
+ */
|
|
|
|
|
+ public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
|
|
|
|
|
+ {
|
|
|
|
|
+ if (null === static::$constraintAnnotation) {
|
|
|
|
|
+ throw new \RuntimeException('Constraint annotation has not been set');
|
|
|
|
|
+ }
|
|
|
|
|
+ if (null === $this->accessId) {
|
|
|
|
|
+ throw new \RuntimeException('AccessId has not been set');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $constraintAnnotation = $targetEntity->getReflectionClass()->getAttributes(static::$constraintAnnotation)[0] ?? null;
|
|
|
|
|
+ if (!$constraintAnnotation || $this->disabled) {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [$startFieldName, $endFieldName] = $this->getStartAndEndFields($constraintAnnotation->getArguments());
|
|
|
|
|
+ if (!$startFieldName || !$endFieldName) {
|
|
|
|
|
+ throw new \RuntimeException('Missing start and/or end field names in constraint annotation');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $constraints = $this->getConstraints($this->accessId);
|
|
|
|
|
+
|
|
|
|
|
+ $fields = [
|
|
|
|
|
+ ActivityYearConstraint::START_KEY => $startFieldName,
|
|
|
|
|
+ ActivityYearConstraint::END_KEY => $endFieldName,
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ return $this->constructQuery($constraints, $targetTableAlias, $fields);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Fonction permettant de construire la requête SQL correspondante aux contraintes.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<string, array<string, int[]>> $constraints
|
|
|
|
|
+ * @param array<string, mixed> $fields
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function constructQuery(array $constraints, string $targetTableAlias, array $fields): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $queryConditionsAND = [];
|
|
|
|
|
+ foreach ($constraints as $key => $constraint) {
|
|
|
|
|
+ $queryConditionsOR = [];
|
|
|
|
|
+ foreach ($constraint as $date => $conditions) {
|
|
|
|
|
+ foreach ($conditions as $condition) {
|
|
|
|
|
+ $arithmetic = $this->getArithmeticValue($condition);
|
|
|
|
|
+ if (!is_null($arithmetic)) {
|
|
|
|
|
+ $queryConditionsOR[] = sprintf("%s.%s %s '%s'", $targetTableAlias, $fields[$key], $arithmetic, $date);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $queryConditionsOR[] = sprintf('%s.%s IS NULL', $targetTableAlias, $fields[$key]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!empty($queryConditionsOR)) {
|
|
|
|
|
+ $queryConditionsAND[] = sprintf('(%s)', join(' OR ', $queryConditionsOR));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return implode(' AND ', $queryConditionsAND);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Fonction retournant la valeur arithmétique correspondant à la condition de la contrainte.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @see DateTimeFilterTest::testGetArithmeticValue()
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function getArithmeticValue(int $condition): ?string
|
|
|
|
|
+ {
|
|
|
|
|
+ return match ($condition) {
|
|
|
|
|
+ DateTimeConstraint::INF => '<',
|
|
|
|
|
+ DateTimeConstraint::EQUAL => '=',
|
|
|
|
|
+ DateTimeConstraint::SUP => '>',
|
|
|
|
|
+ DateTimeConstraint::INF + DateTimeConstraint::EQUAL => '<=',
|
|
|
|
|
+ DateTimeConstraint::SUP + DateTimeConstraint::EQUAL => '>=',
|
|
|
|
|
+ default => null,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|