Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-173

Collection does not get turned into a PersistentCollection when loaded from a proxy

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Critical Critical
    • Resolution: Invalid
    • Affects Version/s: 2.0-ALPHA3
    • Fix Version/s: None
    • Component/s: ORM
    • Security Level: All
    • Labels:
      None

      Description

      We have two entities, a BugReport and a Reporter. The BugReport holds a reference to a Reporter, and the Reporter holds a reference to all his bug reports:

      /**
       * @Entity
       */
      class Reporter {
      	/**
      	 * @Id @Column(type="integer")
      	 * @GeneratedValue(strategy="AUTO")
      	 */
      	public $id;
      	
      	/**
           * @OneToMany(targetEntity="BugReport", mappedBy="reporter")
           */
      	public $bugReports;
      	
      	public function __construct() {
      		$this->bugReports = new \Doctrine\Common\Collections\ArrayCollection;
      	}
      }
      
      
      /**
       * @Entity
       */
      class BugReport {
      	/**
      	 * @Id @Column(type="integer")
      	 * @GeneratedValue(strategy="AUTO")
      	 */
      	public $id;
      	
      	/**
           * @OneToOne(targetEntity="Reporter")
           * @JoinColumn(name="reporter_id", referencedColumnName="id")
           */
      	public $reporter;
      }
      
      $tool = new \Doctrine\ORM\Tools\SchemaTool($em);
      $classes = array(
        $em->getClassMetadata('Entities\Reporter'),
        $em->getClassMetadata('Entities\BugReport')
      );
      $tool->createSchema($classes);
      
      $reporter = new Reporter();
      $bug = new BugReport();
      $bug->reporter = $reporter;
      
      $bug2 = new BugReport();
      $bug2->reporter = $reporter;
      
      $em->persist($reporter);
      $em->persist($bug);
      $em->persist($bug2);
      
      $em->flush();
      
      $reporterId = $reporter->id;
      $bug1Id = $bug->id;
      $bug2Id = $bug2->id;
      
      $em->clear();
      
      $reporter = $em->find('Entities\Reporter', $reporterId);
      
      echo get_class($reporter->bugReports);
      
      $em->clear();
      
      $bug = $em->find('Entities\BugReport', $bug1Id);
      
      echo get_class($bug->reporter->bugReports);
      

      This outputs "Doctrine\ORM\PersistentCollectionDoctrine\Common\Collections\ArrayCollection"

      It seems that when the Reporter is loaded from a Proxy, the fields don't get turned into "managed fields"

        Activity

        Hide
        Roman S. Borschel added a comment -

        I rather suspect that the $bug->reporter proxy is never initialized as $bug->reporter->bugReports accesses a public property and lazy loading can not work with public properties as there is no way for the proxy to intercept these calls.

        What if you use "echo get_class($bug->reporter->getBugReports());", provided you add a method getBugReports() first ?

        Or did you only make all these fields public for the contrived example shown here and in reality they're not?

        Show
        Roman S. Borschel added a comment - I rather suspect that the $bug->reporter proxy is never initialized as $bug->reporter->bugReports accesses a public property and lazy loading can not work with public properties as there is no way for the proxy to intercept these calls. What if you use "echo get_class($bug->reporter->getBugReports());", provided you add a method getBugReports() first ? Or did you only make all these fields public for the contrived example shown here and in reality they're not?
        Hide
        Roman S. Borschel added a comment -

        Fixed formatting

        Show
        Roman S. Borschel added a comment - Fixed formatting
        Hide
        Avi Block added a comment -

        What I'm asking is that in the case of loading the Reporter directly from the EntityManager, the bugReports property gets turned into a PersistentCollection, but if the Reporter is loaded through a proxy, the fields stay as they are. Can't the proxy, in its constructor convert all the fields into Proxies/PersistenCollections?

        Show
        Avi Block added a comment - What I'm asking is that in the case of loading the Reporter directly from the EntityManager, the bugReports property gets turned into a PersistentCollection, but if the Reporter is loaded through a proxy, the fields stay as they are. Can't the proxy, in its constructor convert all the fields into Proxies/PersistenCollections?
        Hide
        Roman S. Borschel added a comment -

        When loading the reporter the bugReports property gets wrapped in a PersistentCollection because it is directly referenced by a loaded/managed entity, the reporter.

        When the bugreport is loaded directly in the second case only the fields of the bugreport are properly proxied. When you access a public property of the proxy, the proxy cant be initialized. Creating PersistentCollections in an uninitialized proxy as you suggest is a) unnecessary overhead and b) not really a solution to the fact that public properties circumvent lazy loading, i.e. while $bug->reporter->bugReports might work then, other fields, especially simple value fields (strings, integers,...) will remain null or at their default value because they're not loaded.

        If you want to use public properties, you can, but you must be aware that they dont play nice with lazy-loading. In general, public properties in entities are discouraged and this is also stated in the manual: http://www.doctrine-project.org/documentation/manual/2_0/en/architecture:entities#persistent-fields

        So my question comes down to: Does the issue you describe in this ticket here occur without public properties? If yes, it is a bug.

        Show
        Roman S. Borschel added a comment - When loading the reporter the bugReports property gets wrapped in a PersistentCollection because it is directly referenced by a loaded/managed entity, the reporter. When the bugreport is loaded directly in the second case only the fields of the bugreport are properly proxied. When you access a public property of the proxy, the proxy cant be initialized. Creating PersistentCollections in an uninitialized proxy as you suggest is a) unnecessary overhead and b) not really a solution to the fact that public properties circumvent lazy loading, i.e. while $bug->reporter->bugReports might work then, other fields, especially simple value fields (strings, integers,...) will remain null or at their default value because they're not loaded. If you want to use public properties, you can, but you must be aware that they dont play nice with lazy-loading. In general, public properties in entities are discouraged and this is also stated in the manual: http://www.doctrine-project.org/documentation/manual/2_0/en/architecture:entities#persistent-fields So my question comes down to: Does the issue you describe in this ticket here occur without public properties? If yes, it is a bug.
        Hide
        Avi Block added a comment - - edited

        To answer your question, the example in real life is not using public properties, and works totally fine using a getter.

        In this contrived example, I'm using public properties, but what I'm actually doing is writing a "Mapper" which converts my Domain entities into a different layer...similar to the Automapper project which is popular in .NET circles. Since I like to keep all my properties private, and only create a "getter" when absolutely necessary, and also for the sake of automation, I use reflection to get at these properties. This works fine, until I try to map an initialized proxy. So I'm not actually using public properties, but I'm using reflection to get at the properties.

        Show
        Avi Block added a comment - - edited To answer your question, the example in real life is not using public properties, and works totally fine using a getter. In this contrived example, I'm using public properties, but what I'm actually doing is writing a "Mapper" which converts my Domain entities into a different layer...similar to the Automapper project which is popular in .NET circles. Since I like to keep all my properties private, and only create a "getter" when absolutely necessary, and also for the sake of automation, I use reflection to get at these properties. This works fine, until I try to map an initialized proxy. So I'm not actually using public properties, but I'm using reflection to get at the properties.
        Hide
        Avi Block added a comment -

        I've taken a totally different approach to the issue. For some reason it didn't occur to me that fields in proxies would be scalars (and thus there is no proxy for them, forgetting about the fact that it doesn't make sense to lazy load simple values)

        Show
        Avi Block added a comment - I've taken a totally different approach to the issue. For some reason it didn't occur to me that fields in proxies would be scalars (and thus there is no proxy for them, forgetting about the fact that it doesn't make sense to lazy load simple values)

          People

          • Assignee:
            Roman S. Borschel
            Reporter:
            Avi Block
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: