DDC-2879: Persisting collections with Composite Primary Keys composed of 2 Foreign Keys and one metadata field #3636

Open
doctrinebot opened this Issue Dec 31, 2013 · 5 comments

2 participants

@doctrinebot

Jira issue originally created by user dylanlgs:

SYNOPSIS

Bug prevents persisting a collection of entities in a join table with a Composite Primary
Key made up of 2 Foreign Keys and one metadata field. From these mapping instructions:
http://docs.doctrine-project.org/en/latest/tutorials/composite-primary-keys.html#use-case-3-join-table-with-metadata

ISSUE DETAILS

SUCCESS: When FOREIGN KEY 1 is the same across items in a collection to be persisted, and FOREIGN KEY 2 is greater than FOREIGN KEY 2 in any existing PRIMARY KEY, the entity and collection are persisted correctly
**** Example: GPA "add val below" exists and has assessment value {"grade*point_average_id":1,"assessment_id":1,"value":4} We will try to add a new assessment value where assessmentid > that of any existing assessment value for GPA "add val below"
*
** Request Payload: {"name":"Add Val Below","courses":[],"assessmentValues":[{"assessment":1,"value":4},{"assessment":3,"value":2}]}
**** Debug Log:
[2014-01-07 11:48:01] app.INFO: START GRADE*POINT_AVERAGE_REPOSITORY #SAVE [GPA #GET*NAME =ADD VAL BELOW] [] []
[2014-01-07 11:48:01] app.INFO: BEGIN OUTPUT FOR GRADE POINT AVERAGE Add Val Below - ASSESSMENT VALUE 1 [] []
[2014-01-07 11:48:01] app.INFO: ASSESSMENT*VALUE #GET_GRADE_POINT_AVERAGE #GET*ID: 1 [] []
[2014-01-07 11:48:01] app.INFO: GRADE*POINT_AVERAGE #GET*ID: 1 [] []
[2014-01-07 11:48:01] app.INFO: ASSESSMENT*VALUE #GET_ASSESSMENT #GET*ID: 1 [] []
[2014-01-07 11:48:01] app.INFO: ASSESSMENT*VALUE #GET*VALUE: 4 [] []
[2014-01-07 11:48:01] app.INFO: MANAGED? 1 [] []
[2014-01-07 11:48:01] app.INFO: END OUTPUT FOR GRADE POINT AVERAGE Add Val Below - ASSESSMENT VALUE 2 [] []
[2014-01-07 11:48:01] app.INFO: BEGIN OUTPUT FOR GRADE POINT AVERAGE Add Val Below - ASSESSMENT VALUE 2 [] []
[2014-01-07 11:48:01] app.INFO: ASSESSMENT*VALUE #GET_GRADE_POINT_AVERAGE #GET*ID: 1 [] []
[2014-01-07 11:48:01] app.INFO: GRADE*POINT_AVERAGE #GET*ID: 1 [] []
[2014-01-07 11:48:01] app.INFO: ASSESSMENT*VALUE #GET_ASSESSMENT #GET*ID: 3 [] []
[2014-01-07 11:48:01] app.INFO: ASSESSMENT*VALUE #GET*VALUE: 2 [] []
[2014-01-07 11:48:01] app.INFO: MANAGED? [] []
[2014-01-07 11:48:01] app.INFO: END OUTPUT FOR GRADE POINT AVERAGE Add Val Below - ASSESSMENT VALUE 3 [] []
[2014-01-07 11:48:01] app.INFO: END GRADE*POINT_AVERAGE_REPOSITORY #SAVE [GPA #GET*NAME =ADD VAL BELOW] [] []
[2014-01-07 11:48:01] doctrine.DEBUG: "START TRANSACTION" [] []
[2014-01-07 11:48:01] doctrine.DEBUG: INSERT INTO gpa*assessment_value (point_value, grade_point_average_id, assessment*id) VALUES (?, ?, ?) {"1":2,"2":"1","3":"3"} []
[2014-01-07 11:48:01] doctrine.DEBUG: UPDATE gpa*assessment_value SET point_value = ? WHERE grade_point_average_id = ? AND assessment*id = ? [4,1,1] []
[2014-01-07 11:48:01] doctrine.DEBUG: "COMMIT" [] []

FAILURE:* When FOREIGN KEY 1 is the same across items in a collection, and FOREIGN KEY 2 is less than any existing FOREIGN KEY 2, the unit of work tries to INSERT existing entity and does not operate on new entity. *The EntityManager thinks it contains() the new entity, but not the old one

**** Example: GPA "add val above" exists and has assessment value {"assessment":3,"value":2} We will try to add a new assessment value where assessment_id < that of any existing assessment value for GPA "add val above"

**** Request Payload: {"name":"Add Val Above","courses":[],"assessmentValues":[{"assessment":1,"value":4},{"assessment":3,"value":2}]}

**** Debug log:

[2014-01-07 11:47:09] app.INFO: START GRADE*POINT_AVERAGE_REPOSITORY #SAVE [GPA #GET*NAME =ADD VAL ABOVE] [] []
[2014-01-07 11:47:09] app.INFO:   BEGIN OUTPUT FOR GRADE POINT AVERAGE Add Val Above - ASSESSMENT VALUE 1 [] []
[2014-01-07 11:47:09] app.INFO:         ASSESSMENT*VALUE #GET_GRADE_POINT_AVERAGE #GET*ID: 2 [] []
[2014-01-07 11:47:09] app.INFO:         GRADE*POINT_AVERAGE #GET*ID: 2 [] []
[2014-01-07 11:47:09] app.INFO:         ASSESSMENT*VALUE #GET_ASSESSMENT #GET*ID: 1 [] []
[2014-01-07 11:47:09] app.INFO:         ASSESSMENT*VALUE #GET*VALUE: 4 [] []
[2014-01-07 11:47:09] app.INFO:         MANAGED? 1 [] []
[2014-01-07 11:47:09] app.INFO:   END OUTPUT FOR GRADE POINT AVERAGE Add Val Above - ASSESSMENT VALUE 2 [] []
[2014-01-07 11:47:09] app.INFO:   BEGIN OUTPUT FOR GRADE POINT AVERAGE Add Val Above - ASSESSMENT VALUE 2 [] []
[2014-01-07 11:47:09] app.INFO:         ASSESSMENT*VALUE #GET_GRADE_POINT_AVERAGE #GET*ID: 2 [] []
[2014-01-07 11:47:09] app.INFO:         GRADE*POINT_AVERAGE #GET*ID: 2 [] []
[2014-01-07 11:47:09] app.INFO:         ASSESSMENT*VALUE #GET_ASSESSMENT #GET*ID: 3 [] []
[2014-01-07 11:47:09] app.INFO:         ASSESSMENT*VALUE #GET*VALUE: 2 [] []
[2014-01-07 11:47:09] app.INFO:         MANAGED?  [] []
[2014-01-07 11:47:09] app.INFO:   END OUTPUT FOR GRADE POINT AVERAGE Add Val Above - ASSESSMENT VALUE 3 [] []
[2014-01-07 11:47:09] app.INFO: END GRADE*POINT_AVERAGE_REPOSITORY #SAVE [GPA #GET*NAME =ADD VAL ABOVE] [] []
[2014-01-07 11:47:09] doctrine.DEBUG: "START TRANSACTION" [] []
[2014-01-07 11:47:09] doctrine.DEBUG: INSERT INTO gpa*assessment_value (point_value, grade_point_average_id, assessment*id) VALUES (?, ?, ?) {"1":2,"2":"2","3":"3"} []
[2014-01-07 11:47:09] doctrine.DEBUG: "ROLLBACK" [] []
[2014-01-07 11:47:09] event.DEBUG: Notified event "kernel.exception" to listener "Symfony\Component\Security\Http\Firewall\ExceptionListener::onKernelException". [] []
[2014-01-07 11:47:09] event.DEBUG: Notified event "kernel.exception" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelException". [] []
[2014-01-07 11:47:09] event.DEBUG: Notified event "kernel.exception" to listener "Symfony\Component\HttpKernel\EventListener\ExceptionListener::onKernelException". [] []
[2014-01-07 11:47:09] request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\DBALException: "An exception occurred while executing 'INSERT INTO gpa*assessment_value (point_value, grade_point_average_id, assessment*id) VALUES (?, ?, ?)' with params [2, "2", "3"]:

SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "gpa*assessment_value*pkey"
DETAIL:  Key (grade*point_average_id, assessment_id)=(2, 3) already exists." at /vagrant/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php line 47 {"exception":"[object] (Doctrine\\DBAL\\DBALException: An exception occurred while executing 'INSERT INTO gpa_assessment_value (point_value, grade_point_average_id, assessment_id) VALUES (?, ?, ?)' with params [2, \"2\", \"3\"]:\n\nSQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint \"gpa_assessment_value_pkey\"\nDETAIL:  Key (grade_point_average_id, assessment_id)=(2, 3) already exists. at /vagrant/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php:47, PDOException: SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint \"gpa_assessment_value_pkey\"\nDETAIL:  Key (grade_point_average_id, assessment*id)=(2, 3) already exists. at /vagrant/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php:138)"} []

CODE

CREATE TABLE assessment
(
    id       bigserial NOT NULL,
    scale_id bigint    NOT NULL,
    title    varchar   NOT NULL,
    passing  boolean   NOT NULL,
    rank     int,

    PRIMARY KEY (id)
);

CREATE TABLE assessment_scale
(
    id   bigserial NOT NULL,
    name varchar   NOT NULL,

    PRIMARY KEY (id)
);

-- ...

CREATE TABLE grade*point*average
(
    id                         bigserial       NOT NULL,
    name                       varchar         NOT NULL,
    additional*credit*allowance numeric(4, 2),

    PRIMARY KEY (id)
);

-- ...

CREATE TABLE gpa*assessment*value
(
    grade*point_average*id bigint        NOT NULL,
    assessment_id          bigint        NOT NULL,
    point_value            numeric(4, 2) NOT NULL,

    PRIMARY KEY (assessment*id, grade_point_average*id),
    FOREIGN KEY (assessment_id) REFERENCES assessment,
    FOREIGN KEY (grade*point_average_id) REFERENCES grade_point*average
);
<?php
namespace LGSConnect\Model;

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Column;
//...
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use LGSConnect\Util\ConstructorArgs;
use LGSConnect\Model\GradePointAverage\AssessmentValue;
// ...

/****
 * @Entity("LGSConnect\Repository\GradePointAverageRepository")
 */
class GradePointAverage
{
    // GradePointAverage Model (owning side): a tool for evaluating a student's performance 
    // by dividing the total points earned by total credits attempted.

    use ConstructorArgs;

    /****
     * @Id
     * @GeneratedValue
     * @Column(type="bigint")
     *
     * @var int
     */
    private $id;

    // ...

    /****
     * @OneToMany(targetEntity="LGSConnect\Model\GradePointAverage\AssessmentValue", mappedBy="gradePointAverage", cascade="persist")
     *
     * @var Collection
     */
    private $assessmentValues;

    // ...

    /****
     * @param array $args
     */
    public function **construct(array $args = [])
    {
        $this->assessmentValues = new ArrayCollection;
        // ...
        $this->handleArgs($args);
    }

    // ...

    /****
     * @return Collection
     */
    public function getAssessmentValues()
    {
        return $this->assessmentValues;
    }

    /****
     * @param ArrayCollection $assessmentValues
     */
    public function setAssessmentValues(ArrayCollection $assessmentValues)
    {
        $this->assessmentValues = $assessmentValues;
    }

    /****
     * @param AssessmentValue $assessmentValue
     */
    public function addAssessmentValue(AssessmentValue $assessmentValue)
    {
        $this->assessmentValues->add($assessmentValue);
    }

    /****
     * @param AssessmentValue $assessmentValue
     */
    public function removeAssessmentValue(AssessmentValue $assessmentValue)
    {
        $this->assessmentValues->removeElement($assessmentValue);
    }

    // ...
}
<?php
namespace LGSConnect\Model\GradePointAverage;

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\JoinColumn;
use LGSConnect\Model\GradePointAverage;
use LGSConnect\Model\Assessment;
use LGSConnect\Util\ConstructorArgs;

/****
 * @Entity("LGSConnect\Repository\GradePointAverage\AssessmentValueRepository")
 * @Table("gpa*assessment*value")
 */
class AssessmentValue
{
    // AssessmentValue Model (inverse side): a number of points assigned 
    // to an Assessment by a Grade Point Average

    use ConstructorArgs;

    /****
     * @Id
     * @ManyToOne(targetEntity="LGSConnect\Model\GradePointAverage")
     */
    private $gradePointAverage;

    /****
     * @Id
     * @ManyToOne(targetEntity="LGSConnect\Model\Assessment")
     */
    private $assessment;

    /****
     * @Column("point_value")
     *
     * @var float
     */
    private $value;

    /****
     * @param array $args
     */
    public function **construct(array $args = [])
    {
        $this->handleArgs($args);
    }

    /****
     * @return GradePointAverage
     */
    public function getGradePointAverage()
    {
        return $this->gradePointAverage;
    }

    /****
     * @param GradePointAverage $gradePointAverage
     */
    public function setGradePointAverage(GradePointAverage $gradePointAverage)
    {
        $this->gradePointAverage = $gradePointAverage;
    }

    /****
     * @return Assessment
     */
    public function getAssessment()
    {
        return $this->assessment;
    }

    /****
     * @param Assessment $assessment
     */
    public function setAssessment(Assessment $assessment)
    {
        $this->assessment = $assessment;
    }

    /****
     * @return float
     */
    public function getValue()
    {
        return $this->value;
    }

    /****
     * @param float $value
     */
    public function setValue($value)
    {
        $this->value = $value;
    }

    /****
     * @return AssessmentScale
     */
    public function getAssessmentScale()
    {
        return $this->assessment->getScale();
    }
}
<?php
namespace LGSConnect\Model;

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\ManyToOne;
use LGSConnect\Model\Assessment\Scale;
use LGSConnect\Util\ConstructorArgs;

/****
 * @Entity("LGSConnect\Repository\AssessmentRepository")
 */
class Assessment
{
    // Assessment (related, but unmapped): A "grade" assigned to a student 
    // for attending a course section

    use ConstructorArgs;

    /****
     * @Id
     * @GeneratedValue
     * @Column(type="bigint")
     *
     * @var int
     */
    private $id;

    // ...

    /****
     * @param array $args
     */
    public function **construct(array $args = [])
    {
        $this->handleArgs($args);
    }

    /****
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    // ...
}
<?php
namespace LGSConnect\Repository;

use Doctrine\ORM\EntityRepository;
// ...
use LGSConnect\Model\GradePointAverage;

class GradePointAverageRepository extends BaseRepository implements GradePointAverageRepositoryInterface
{
    // ...

    /****
     * @param GradePointAverage $gradePointAverage
     */
    public function save(GradePointAverage $gradePointAverage)
    {
        $this->getEntityManager()->persist($gradePointAverage);
        $this->getEntityManager()->flush();
    }
}
<?php
namespace LGSConnect\Repository\GradePointAverage;

use Doctrine\ORM\EntityRepository;
use LGSConnect\Model\GradePointAverage\AssessmentValue;

class AssessmentValueRepository extends EntityRepository
{
    /****
     * @param AssessmentValue $assessmentValue
     */
    public function save(AssessmentValue $assessmentValue)
    {
        $this->getEntityManager()->persist($assessmentValue);
        $this->getEntityManager()->flush();
    }
}
<?php
namespace LGSConnect\Manager;

use InvalidArgumentException;
use Symfony\Component\Validator\ValidatorInterface;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\SecurityExtraBundle\Annotation\PreAuthorize;
use Knp\Component\Pager\Pagination\PaginationInterface;
use LGSConnect\Repository\GradePointAverageRepository;
use LGSConnect\PaginationFactory\GradePointAveragePaginationFactoryInterface;
use LGSConnect\Model\GradePointAverage;

/****
 * @Service("grade*point_average*manager")
 */
class GradePointAverageManager
{
    /****
     * @var GradePointAverageRepository
     */
    private $gradePointAverageRepository;

    /****
     * @var GradePointAveragePaginationFactoryInterface
     */
    private $gradePointAveragePaginationFactory;

    /****
     * @var ValidatorInterface
     */
    private $validator;

    /****
     * @InjectParams
     *
     * @param GradePointAverageRepository $gradePointAverageRepository
     * @param GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory
     * @param ValidatorInterface $validator
     */
    public function **construct(
        GradePointAverageRepository $gradePointAverageRepository,
        GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory,
        ValidatorInterface $validator
    )
    {
        $this->gradePointAverageRepository = $gradePointAverageRepository;
        $this->gradePointAveragePaginationFactory = $gradePointAveragePaginationFactory;
        $this->validator = $validator;
    }

    /****
     * @PreAuthorize("isAllowedToManageTheGradePointAverage(#gradePointAverage)")
     * @param GradePointAverage $gradePointAverage
     * @throws InvalidArgumentException
     */
    public function save(GradePointAverage $gradePointAverage)
    {
        $violationList = $this->validator->validate($gradePointAverage);
        if ($violationList->count()) {
            throw new InvalidArgumentException;
        }

        $this->gradePointAverageRepository->save($gradePointAverage);
    }
}
<?php
namespace LGSConnect\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Doctrine\Common\Collections\ArrayCollection;
use FOS\RestBundle\View\View;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\SecurityExtraBundle\Annotation\PreAuthorize;
use Knp\Component\Pager\Pagination\PaginationInterface;
use LGSConnect\Manager\GradePointAverageManager;
use LGSConnect\Model\GradePointAverage;
use LGSConnect\Model\GradePointAverage\AssessmentValue;

/****
 * @Service("grade*point_average*controller", parent="lgs.controller.abstract")
 * @Route("/gpa", service="grade*point_average*controller")
 */
class GradePointAverageController extends BaseController
{
    /****
     * @var GradePointAverageManager
     */
    private $gradePointAverageManager;

    private $logger;

    /****
     * @InjectParams
     *
     * @param GradePointAverageManager $gradePointAverageManager
     * @param LoggerInterface $logger
     */
    public function **construct(GradePointAverageManager $gradePointAverageManager, LoggerInterface $logger)
    {
        $this->gradePointAverageManager = $gradePointAverageManager;
        $this->logger = $logger;
    }

    // ...

    /****
     * @Route("/{id}", name="gpa.edit", requirements={"id" = "\d+"})
     * @Method("PUT")
     *
     * @param Request $request
     * @param GradePointAverage $gpa
     * @return View
     */
    public function editAction(Request $request, GradePointAverage $gpa)
    {
        $form = $this->formFactory->createNamed(null, 'gpa', $gpa, [
            'method' => 'PUT',
        ]);
        $form->handleRequest($request);

        foreach ($gpa->getAssessmentValues() as $av) {
            $this->logger->info('GPA ID PREVALIDATE IN CONTROLLER:'.$gpa->getId());
            $this->logger->info('PREVALIDATE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:'.$av->getAssessment()->getId());
            $this->logger->info('PREVALIDATE IN CONTROLLER ASSESSMENT VAL POINTS:'.$av->getValue());
        }

        /*
        // try reversing the order of the collection to see if that helps
        $assessmentVals = $gpa->getAssessmentValues()->toArray();
        $reversed = array_reverse($assessmentVals);
        $reversedColl = new ArrayCollection($reversed);
        $gpa->setAssessmentValues($reversedColl);
        */

        if ($form->isValid()) {
            foreach ($gpa->getAssessmentValues() as $av) {
                $this->logger->info('GPA ID PRESAVE IN CONTROLLER:'.$gpa->getId());
                $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:'.$av->getAssessment()->getId());
                $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:'.$av->getValue());
            }
            $this->gradePointAverageManager->save($gpa);

            return new View($gpa, 204);
        }

        return new View($form);
    }

    // ...
}
<?php
namespace LGSConnect\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use JMS\DiExtraBundle\Annotation\FormType;

/****
 * @FormType
 */
class GradePointAverageType extends AbstractType
{
    /****
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('courses', 'entity', [
                'class' => 'Model:Course',
                'multiple' => true
            ])
            ->add('assessmentValues', 'collection', [
                'type' => 'gpa*assessment*value',
                'allow_add' => true,
                'by_reference' => false,
            ])
        ;
    }

    /****
     * @return string
     */
    public function getName()
    {
        return 'gpa';
    }
}
<?php
namespace LGSConnect\Form\Type\GradePointAverage;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use JMS\DiExtraBundle\Annotation\FormType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

/****
 * @FormType("gpa*assessment*value")
 */
class AssessmentValueType extends AbstractType
{
    /****
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('assessment', 'entity', [
                'class' => 'Model:Assessment',
            ])
            ->add('value', 'number', [
                'precision' => 2,
            ])
        ;
    }

    /****
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'LGSConnect\Model\GradePointAverage\AssessmentValue',
        ]);
    }

    /****
     * @return string
     */
    public function getName()
    {
        return 'gpa*assessment*value';
    }
}
@doctrinebot

Comment created by kozlice:

Any update on this issue? We've got same problem in our project (PostgreSQL 9.4, Doctrine 2.4.7)

@doctrinebot

Comment created by @ocramius:

Removed the "feedback required" flag.

[~kozlice] if there is no ticket update, well... then there is no actual update.

Somewhat related to #1113

I suggest getting your hands dirty and fixing it yourself if it affects you.

@doctrinebot

Comment created by @doctrinebot:

A related Github Pull-Request [GH-1113] was labeled:
#1113

@doctrinebot

Comment created by @doctrinebot:

A related Github Pull-Request [GH-1113] was labeled:
#1113

@doctrinebot

Comment created by @doctrinebot:

A related Github Pull-Request [GH-1113] was closed:
#1113

@Ocramius Ocramius was assigned by doctrinebot Dec 6, 2015
@doctrinebot doctrinebot added the Bug label Dec 7, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment