Last active
August 19, 2020 12:44
-
-
Save jonny-no1/52d066f458a23a88df8bd54f499d2ad9 to your computer and use it in GitHub Desktop.
NotOverlapping Symfony date period validator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
use AppBundle\Validator\Constraints\NotOverlapping; | |
/** | |
* @NotOverlapping("period") | |
*/ | |
class CalendarTeamEvent | |
{ | |
/** | |
* @ORM\Id | |
* @ORM\Column(type="integer") | |
* @ORM\GeneratedValue(strategy="AUTO") | |
*/ | |
protected $id; | |
/** | |
* @ORM\Embedded(class="League\Period\Period") | |
*/ | |
protected $period; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle\Validator\Constraints; | |
use Symfony\Component\Validator\Constraint; | |
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; | |
/** | |
* @Annotation | |
*/ | |
class NotOverlapping extends Constraint | |
{ | |
public $message = 'This value overlaps with other values.'; | |
public $service = 'app.validator.not_overlapping'; | |
public $field; | |
public $errorPath; | |
public function getRequiredOptions() | |
{ | |
return ['field']; | |
} | |
public function getDefaultOption() | |
{ | |
return 'field'; | |
} | |
/** | |
* The validator must be defined as a service with this name. | |
* | |
* @return string | |
*/ | |
public function validatedBy() | |
{ | |
return $this->service; | |
} | |
/** | |
* @return string | |
*/ | |
public function getTargets() | |
{ | |
return self::CLASS_CONSTRAINT; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace TriprHqBundle\Validator\Constraints; | |
use Doctrine\Common\Collections\Criteria; | |
use Doctrine\Common\Persistence\ManagerRegistry; | |
use League\Period\Period; | |
use Symfony\Component\Validator\Exception\UnexpectedTypeException; | |
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; | |
use Symfony\Component\Validator\Constraint; | |
use Symfony\Component\Validator\ConstraintValidator; | |
class NotOverlappingValidator extends ConstraintValidator | |
{ | |
/** | |
* @var ManagerRegistry | |
*/ | |
private $registry; | |
/** | |
* NotOverlappingValidator constructor. | |
* @param ManagerRegistry $registry | |
*/ | |
public function __construct(ManagerRegistry $registry) | |
{ | |
$this->registry = $registry; | |
} | |
/** | |
* @param object $entity | |
* @param Constraint $constraint | |
* | |
* @throws UnexpectedTypeException | |
* @throws ConstraintDefinitionException | |
*/ | |
public function validate($entity, Constraint $constraint) | |
{ | |
if (!$constraint instanceof NotOverlapping) { | |
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\NotOverlapping'); | |
} | |
if (!is_null($constraint->errorPath) && !is_string($constraint->errorPath)) { | |
throw new UnexpectedTypeException($constraint->errorPath, 'string or null'); | |
} | |
$em = $this->registry->getManagerForClass(get_class($entity)); | |
if (!$em) { | |
throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', get_class($entity))); | |
} | |
/* @var $class \Doctrine\Common\Persistence\Mapping\ClassMetadata */ | |
$class = $em->getClassMetadata(get_class($entity)); | |
if (!array_key_exists($constraint->field, $class->embeddedClasses)) { | |
throw new ConstraintDefinitionException(sprintf( | |
'The field "%s" is not a Doctrine embeddable, so it cannot be validated for overlapping time periods.', | |
$constraint->field | |
)); | |
} | |
$value = $class->reflFields[$constraint->field]->getValue($entity); | |
if (!is_null($value) && !($value instanceof Period)) { | |
throw new UnexpectedTypeException($value, 'null or League\Period\Period'); | |
} | |
if(is_null($value)) { | |
return; | |
} | |
// ... WHERE existing_start < new_end | |
// AND existing_end > new_start; | |
$criteria = new Criteria(); | |
$criteria | |
->where($criteria->expr()->lt(sprintf('%s.startDate', $constraint->field), $value->getEndDate())) | |
->andWhere($criteria->expr()->gt(sprintf('%s.endDate', $constraint->field), $value->getStartDate())) | |
; | |
$repository = $em->getRepository(get_class($entity)); | |
$result = $repository->matching($criteria); | |
if ($result instanceof \IteratorAggregate) { | |
$result = $result->getIterator(); | |
} | |
/* If no entity matched the query criteria or a single entity matched, | |
* which is the same as the entity being validated, there are no | |
* overlaps. | |
*/ | |
if (0 === count($result) || (1 === count($result) && $entity === ($result instanceof \Iterator ? $result->current() : current($result)))) { | |
return; | |
} | |
$errorPath = $constraint->errorPath ?: $constraint->field; | |
$this->context->buildViolation($constraint->message) | |
->atPath($errorPath) | |
->addViolation() | |
; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/Resources/LeaguePeriod/doctrine/Period.orm.yml | |
League\Period\Period: | |
type: embeddable | |
fields: | |
startDate: | |
type: datetime | |
endDate: | |
type: datetime |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# src/AppBundle/Resources/config/services.yml | |
services: | |
app.validator.not_overlapping: | |
class: AppBundle\Validator\Constraints\NotOverlappingValidator | |
arguments: ["@doctrine"] | |
tags: | |
- { name: validator.constraint_validator, alias: app.validator.not_overlapping } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment