Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-3234

Empty properties when filtering collections

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Cannot Reproduce
    • Affects Version/s: 2.5, 2.4.2
    • Fix Version/s: None
    • Component/s: ORM
    • Security Level: All
    • Environment:
      PHP 5.5.9, Nginx 1.4.6, PHP FPM, Zend Framework 2.3.1, Linux Ubuntu 14.04

      Description

      I'm facing some troubles when filtering an entity association (ArrayCollection) by using Criteria.

      The scenario is the following: I have 3 entities:

      User.php
      <?php
      
      namespace Entity;
      
      use Doctrine\ORM\Mapping as ORM;
      use \Doctrine\Common\Collections\Criteria;
      use Zend\Stdlib\Hydrator;
      
      /**
       * User
       *
       * @ORM\Table(name="user")
       * @ORM\Entity(repositoryClass="Entity\UserRepository")
       * @ORM\HasLifecycleCallbacks
       * @author domanski
       */
      class User implements \Serializable {
      	/**
      	 *
      	 * @ORM\Id
      	 * @ORM\Column(name="id", type="integer", nullable=false)
      	 * @ORM\GeneratedValue(strategy="AUTO")
      	 * @var integer
      	 */
      	private $id;
      
      	/**
      	 * @ORM\Column(name="delete_date", type="datetime")
      	 * @var \DateTime
      	 */
      	private $deleteDate;
      	
      	public function __construct(array $options = array()) {
      		if (!empty($options))
      			$this->hydrate($options);
      	}
      
      	public function hydrate(array $options = array(), \Doctrine\ORM\EntityManager $em = null) {
      		$hydrator = new Hydrator\ClassMethods();
      		$hydrator->hydrate($options, $this);
      	}
      
      	/**
      	 * 
      	 * @return int
      	 */
      	public function getId() {
      		return $this->id;
      	}
      
      	/**
      	 * 
      	 * @param int $id
      	 * @return \Entity\User
      	 */
      	public function setId($id) {
      		$this->id = $id;
      		return $this;
      	}
      
      	/**
      	 * 
      	 * @return \DateTime
      	 */
      	public function getDeleteDate() {
      		return $this->deleteDate;
      	}
      
      	/**
      	 * @param \DateTime|null $deleteDate
      	 * @return \Entity\User
      	 */
      	public function setDeleteDate($deleteDate = null) {
      		$this->deleteDate = $deleteDate;
      		return $this;
      	}
      
      	/**
      	 * 
      	 * @return array
      	 */
      	public function toArray() {
      		$result = array(
      			'id' => $this->getId(),
      			'delete_date' => $this->getDeleteDate()
      		);
      
      		return $result;
      	}
      
      }
      
      FieldWorker.php
      <?php
      
      namespace Entity;
      
      use Doctrine\ORM\Mapping as ORM;
      use \Doctrine\Common\Collections\Criteria;
      
      use Zend\Stdlib\Hydrator;
      
      /**
       * Description of FieldWorker
       *
       * @ORM\Entity(repositoryClass="Entity\FieldWorkerRepository")
       * @ORM\Table(name="field_worker")
       * @ORM\HasLifecycleCallbacks
       * @author domanski
       */
      class FieldWorker {
      	
      	/**
      	 * This attribute must exist so the inverse join with any other entity can work
      	 * 
      	 * @ORM\Id
      	 * @ORM\Column(type="integer", name="user_id")
      	 * @var string
      	 */
      	protected $id;
      	
      	/**
      	 * 
      	 * @ORM\OneToOne(targetEntity="Entity\User")
      	 * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
      	 * @var \Entity\User
      	 */
      	protected $user;
      	
      	public function __construct($options = array()) {		
      		if(!empty($options))
      			$this->hydrate($options);
      	}
      	
      	public function hydrate(array $options = array(), \Doctrine\ORM\EntityManager $em = null) {
      		if(!empty($em)) {
      			// user
      			if(isset($options['user']))
      				$options['user'] = $em->getReference('Entity\User', $options['user']);
      			else if(isset($options['user_id']))
      				$options['user'] = $em->getReference('Entity\User', $options['user_id']);
      						
      		}
      		
      		$hydrator = new Hydrator\ClassMethods();
      		$hydrator->hydrate($options, $this);
      	}
      
      	/**
      	 * 
      	 * @return \Entity\User
      	 */
      	public function getUser() {
      		return $this->user;
      	}
      
      	/**
      	 * 
      	 * @param \Entity\User $user
      	 * @return \Entity\FieldWorker
      	 */
      	public function setUser(\Entity\User $user) {
      		$this->user = $user;
      		$this->id = $user->getId();
      		return $this;
      	}
      	
      	/**
      	 * 
      	 * @return array
      	 */
      	public function toArray() {
      		return $this->getUser()->toArray();
      	}
      }
      
      Stage.php
      <?php
      
      namespace Obra\Entity;
      
      use Doctrine\ORM\Mapping as ORM;
      use Zend\Stdlib\Hydrator;
      use Doctrine\Common\Collections\Criteria;
      
      /**
       * Description of Stage
       *
       * @ORM\Table(name="stage")
       * @ORM\Entity(repositoryClass="Entity\StageRepository")
       * @ORM\HasLifecycleCallbacks
       * @author domanski
       */
      class Stage {
      
      	/**
      	 *
      	 * @ORM\Id
      	 * @ORM\Column(name="id", type="integer", nullable=false)
      	 * @ORM\GeneratedValue(strategy="AUTO")
      	 * @var integer
      	 */
      	private $id;
      
      	/**
      	 * @ORM\ManyToMany(targetEntity="Entity\FieldWorker")
      	 * @ORM\JoinTable(name="stage_field_worker",
      	 * 		joinColumns={@ORM\JoinColumn(name="stage_id", referencedColumnName="id")},
      	 * 		inverseJoinColumns={@ORM\JoinColumn(name="field_worker_id", referencedColumnName="user_id")}
      	 * 	)
      	 * @var \Doctrine\Common\Collections\ArrayCollection
      	 */
      	private $fieldWorkers;
      
      	public function __construct(array $options = array()) {
      		$this->fieldWorkers = new \Doctrine\Common\Collections\ArrayCollection();
      
      		if (!empty($options))
      			$this->hydrate($options);
      	}
      
      	public function hydrate(array $options = array(), \Doctrine\ORM\EntityManager $em = null) {
      		$hydrator = new Hydrator\ClassMethods();
      		$hydrator->hydrate($options, $this);
      	}
      
      	/**
      	 * 
      	 * @return int
      	 */
      	public function getId() {
      		return $this->id;
      	}
      
      	/**
      	 * 
      	 * @param int $id
      	 * @return \Entity\Stage
      	 */
      	public function setId($id) {
      		$this->id = $id;
      		return $this;
      	}
      
      	/**
      	 * 
      	 * @return \Doctrine\Common\Collections\ArrayCollection
      	 */
      	public function getFieldWorkers() {
      		$criteria = Criteria::create()
      				->where(Criteria::expr()->isNull("user.deleteDate"))
      				->orderBy(array("user.name" => Criteria::ASC));
      
      		return $this->fieldWorkers->matching($criteria);
      	}
      
      	/**
      	 * 
      	 * @return \Entity\Stage
      	 */
      	public function clearFieldWorkers() {
      		$this->fieldWorkers->clear();
      		return $this;
      	}
      
      	/**
      	 * 
      	 * @param \Entity\FieldWorker $fieldWorker
      	 * @return \Entity\Stage
      	 */
      	public function addFiscal(\Entity\FieldWorker $fieldWorker) {
      		$this->fieldWorkers->add($fieldWorker);
      		return $this;
      	}
      
      	/**
      	 * 
      	 * @return array
      	 */
      	public function toArray() {
      		$result = array(
      			"id" => $this->getId(),
      			"field_workers" => array()
      		);
      
      		foreach ($this->getFieldWorkers() as $fieldWorker) {
      			$result['field_workers'][] = $fieldWorker->toArray();
      		}
      
      		return $result;
      	}
      }
      

      The problem is that whenever the Stage::getFieldWorkers() method is invoked, the list of associated field workers is returned correctly, however if I try to retrieve the respective user of any field worker, it is NULL.

      For example:

      $stageEntity = $em->getRerefence('Entity\Stage', 1);
      $fieldWorkers = $stageEntity->getFieldWorkers();
      
      foreach($fieldWorkers as $fieldWorker) {
         $userId = $fieldWorker->getUser()->getId(); // This throws an error message saying that the result of getUser() is NULL
      }
      

      Another example would be if I try to call $stageEntity->toArray() (because it does the same thing as show above).

      The database tables are as following:

      Database tables creation
      CREATE TABLE `user` (
        `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
        `delete_date` datetime DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      CREATE TABLE `field_worker` (
        `user_id` int(10) unsigned NOT NULL,
        PRIMARY KEY (`user_id`),
        CONSTRAINT `fk_field_worker_user1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      
      CREATE TABLE `stage` (
        `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      
      CREATE TABLE `stage_field_worker` (
        `stage_id` int(10) unsigned NOT NULL,
        `field_worker_id` int(10) unsigned NOT NULL,
        PRIMARY KEY (`stage_id`,`field_worker_id`),
        KEY `fk_stage_field_worker_stage_idx` (`stage_id`),
        KEY `fk_stage_field_worker_field_worker_idx` (`field_worker_id`),
        CONSTRAINT `fk_stage_field_worker_field_worker` FOREIGN KEY (`field_worker_id`) REFERENCES `field_worker` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
        CONSTRAINT `fk_stage_field_worker_stage` FOREIGN KEY (`stage_id`) REFERENCES `stage` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      INSERT INTO `user` SET `id` = 1;
      INSERT INTO `field_worker` SET `user_id` = 1;
      INSERT INTO `stage` SET `id` = 1;
      INSERT INTO `stage_field_worker` SET `stage_id` = 1, `field_worker_id` = 1;
      

      After hours trying to understand what was wrong, I decided to make a test and modify the Stage::getFieldWorkers() method by removing the filtering Criteria:

      public function getFieldWorkers() {
          return $this->fieldWorkers;
      }
      

      By simply doing that, the getUser() method started to return an entity of type User (as expected).

      I am not a Doctrine ORM expert, but there seems to be something wrong with ArrayCollection filtering (matching() method)

        Activity

        Hide
        Diogo Domanski de Souza added a comment -

        The same problem occurs with QueryBuilder. For example:

        StageRepository.php
        <?php
        
        namespace Entity;
        
        use Doctrine\ORM\EntityRepository;
        
        /**
         * Description of StageRepository
         *
         * @author domanski
         */
        class StageRepository extends EntityRepository 
        {
        
        	public function findByFieldWorkerId($fieldWorkerId) {
        		$qb = $this->getEntityManager()->createQueryBuilder()
        			->select('s')
        			->from('Entity\Stage', 's')
        			->innerJoin('s.fieldWorkers', 'f')
        			->innerJoin('f.user', 'u', \Doctrine\ORM\Query\Expr\Join::WITH, "u.deleteDate IS NULL AND u.id = :field_worker_id")
        				->setParameter('field_worker_id', $fieldWorkerId);
        
        		return $qb->getQuery()->getResult();
        	 }
        }
        

        If I try to iterate over the result of StageRepository::findByFieldWorkerId() and call toArray() of any element, I get the same error. I've even tried to remove the inner join with User entity from query:

        	public function findByFieldWorkerId($fieldWorkerId) {
        		$qb = $this->getEntityManager()->createQueryBuilder()
        			->select('s')
        			->from('Entity\Stage', 's')
        			->innerJoin('s.fieldWorkers', 'f');
        
        		return $qb->getQuery()->getResult();
        	 }
        
        Show
        Diogo Domanski de Souza added a comment - The same problem occurs with QueryBuilder. For example: StageRepository.php <?php namespace Entity; use Doctrine\ORM\EntityRepository; /** * Description of StageRepository * * @author domanski */ class StageRepository extends EntityRepository { public function findByFieldWorkerId($fieldWorkerId) { $qb = $ this ->getEntityManager()->createQueryBuilder() ->select('s') ->from('Entity\Stage', 's') ->innerJoin('s.fieldWorkers', 'f') ->innerJoin('f.user', 'u', \Doctrine\ORM\Query\Expr\Join::WITH, "u.deleteDate IS NULL AND u.id = :field_worker_id" ) ->setParameter('field_worker_id', $fieldWorkerId); return $qb->getQuery()->getResult(); } } If I try to iterate over the result of StageRepository::findByFieldWorkerId() and call toArray() of any element, I get the same error. I've even tried to remove the inner join with User entity from query: public function findByFieldWorkerId($fieldWorkerId) { $qb = $ this ->getEntityManager()->createQueryBuilder() ->select('s') ->from('Entity\Stage', 's') ->innerJoin('s.fieldWorkers', 'f'); return $qb->getQuery()->getResult(); }
        Hide
        Marco Pivetta added a comment -

        I suspect that something is wrong in your mappings then...

        Show
        Marco Pivetta added a comment - I suspect that something is wrong in your mappings then...
        Hide
        Diogo Domanski de Souza added a comment -

        Hi Marco,

        The mappings are shown above. The only thing I did was to omit some entities/tables properties/columns that don't have to see with the associations - except the field worker (table and Entity) and stage_field_worker table, that are fully presented.

        Maybe is important to notice that the field_worker table has only one column (user_id) that is primary key and foreign key (referencing user.id).

        Thanks for you support

        Show
        Diogo Domanski de Souza added a comment - Hi Marco, The mappings are shown above. The only thing I did was to omit some entities/tables properties/columns that don't have to see with the associations - except the field worker (table and Entity) and stage_field_worker table, that are fully presented. Maybe is important to notice that the field_worker table has only one column (user_id) that is primary key and foreign key (referencing user.id). Thanks for you support
        Hide
        Marco Pivetta added a comment -

        Diogo Domanski de Souza we can't debug this as it is. We'd need a functional test case to be added to https://github.com/doctrine/doctrine2/tree/0650bb954f5e8d05776f99abd04c81948413299f/tests/Doctrine/Tests/ORM/Functional/Ticket first, in order to see the failure

        Show
        Marco Pivetta added a comment - Diogo Domanski de Souza we can't debug this as it is. We'd need a functional test case to be added to https://github.com/doctrine/doctrine2/tree/0650bb954f5e8d05776f99abd04c81948413299f/tests/Doctrine/Tests/ORM/Functional/Ticket first, in order to see the failure
        Hide
        Diogo Domanski de Souza added a comment -

        Marco Pivetta is there any documentation (tutorial, instructions, guide, etc) that I can use to learn how to write the functional test cases that I need?

        Show
        Diogo Domanski de Souza added a comment - Marco Pivetta is there any documentation (tutorial, instructions, guide, etc) that I can use to learn how to write the functional test cases that I need?
        Hide
        Marco Pivetta added a comment -

        Diogo Domanski de Souza you need to look at the existing ones.

        For running the test suite:

        git clone git@github.com:doctrine/doctrine2.git
        cd doctrine2 
        curl -s https://getcomposer.org/installer | php --
        ./composer.phar install
        ./vendor/bin/phpunit
        
        Show
        Marco Pivetta added a comment - Diogo Domanski de Souza you need to look at the existing ones. For running the test suite: git clone git@github.com:doctrine/doctrine2.git cd doctrine2 curl -s https: //getcomposer.org/installer | php -- ./composer.phar install ./vendor/bin/phpunit
        Hide
        Diogo Domanski de Souza added a comment -

        I solved the problem by removing the property $id from FieldWorker entity and add annotation @ORM\Id to $user property (in this same entity).

        I didn't understand why the previous definition of FieldWorker entity was not working. I have another similar relationship scenario, between 3 different entities, and the error does not occur.

        Thanks to all for the support

        Show
        Diogo Domanski de Souza added a comment - I solved the problem by removing the property $id from FieldWorker entity and add annotation @ORM\Id to $user property (in this same entity). I didn't understand why the previous definition of FieldWorker entity was not working. I have another similar relationship scenario, between 3 different entities, and the error does not occur. Thanks to all for the support

          People

          • Assignee:
            Marco Pivetta
            Reporter:
            Diogo Domanski de Souza
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: