Symfony2重叠日期的自定义约束

时间:2013-10-23 15:30:07

标签: symfony

我有一个学说实体,如下所述:

company\MyBundle\Entity\ProgramGrid:
    type: entity
    table: program_grid
    id:
        id_program_grid:
            type: integer
            generator: {strategy: IDENTITY}
    fields:
        name:
            type: text
            nullable: true
        start_date:
            type: date
            nullable: false
        end_date:
            type: date
            nullable: true

我想添加验证约束来验证这一点 start_date和end_date不会与另一条记录重叠。

如果我有2条记录A和B,我想:

  

B.start_date> A.end_date

实现这一目标的最佳方法是什么?

2 个答案:

答案 0 :(得分:0)

您的问题的答案是事件。

您需要为预先保留事件创建活动订阅者(如Symfony Docs中所述)。

在该事件订阅者中,您必须查询您的表格,看看您是否有重叠范围。该算法的最佳答案可在此问题的接受答案中找到:Determine Whether Two Date Ranges Overlap

答案 1 :(得分:0)

我刚刚实现了这样的约束及其验证器。这就是它的样子:

约束:

<?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;
    }
}

验证

<?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()
        ;
    }
}

您可以与示例实体in my gist一起找到它。