title: Doctrine insert/update/modify sub entities on @OneToMany
body: <p>I have a project based on Symfony 2.6. It has the following structure:</p>
  
  <pre><code>Customer-&gt;@OneToMany-&gt;Orders-&gt;@OneToMany-&gt;Domains-&gt;@OneToMany-&gt;SubDomains
  </code></pre>
  
  <p>same in reverse order:</p>
  
  <pre><code>SubDomains-&gt;@ManyToOne-&gt;Domains-&gt;@ManyToOne-&gt;Orders-&gt;@ManyToOne-&gt;Customer
  </code></pre>
  
  <p>Doctrine creates some additional pretty cool "magic" columns like <code>customer_id</code> (in domains), <code>order_id</code> (in domains) and <code>domain_id</code> (in <code>subdomains</code>).</p>
  
  <p>It think it would be perfect, if all these <code>*_id</code> cols are filled with the IDs of the parent table. But in my case, that will not match all the time.
  Only for tables <code>customer</code> and <code>order</code>, the <code>*_id</code> cols are filled properly.</p>
  
  <p>My PHP code creates new Domain Entities. It also sets the ID, if the record exists in DB, and it adds all the <code>subdomains</code> as <code>ArrayCollection</code>.</p>
  
  <p>Here is my <code>storeOrder()</code> method:</p>
  
  <pre class="lang-php prettyprint-override"><code>protected function storeOrder(Order $order, array $domains) {
      if ($order-&gt;getDomains()-&gt;count() === 0) {
          // insert
          foreach ($domains as $domain) {
              $order-&gt;getDomains()-&gt;add($this-&gt;createDomainFromArray($domain));
          }
      } else {
          // remove/add
          $order-&gt;getDomains()-&gt;clear();
          foreach ($domains as $domain) {
              $order-&gt;getDomains()-&gt;add($this-&gt;createDomainFromArray($domain));
          }
      }
      //$updatedOrder = $this-&gt;entityManager-&gt;merge($order);
      // starts the commit process incl. transactions and prepared queries
      $this-&gt;entityManager-&gt;persist($order);
      $this-&gt;entityManager-&gt;flush();
      // clear all cached entities in em. That speeds up processing enormous
      $this-&gt;entityManager-&gt;clear();
      $this-&gt;amountOfDomains += count($domains);
  }
  
  /**
   * create a domain object from delivered data array
   *
   * @param array $data
   * @return Domain
   */
  protected function createDomainFromArray($data) {
      $domain = new Domain();
      $domain-&gt;setOrderNumber($this-&gt;orderNumber);
      $domain-&gt;fromArray(ArrayUtility::removeSubEntriesFromArray($data));
      $this-&gt;addIdToDomainIfFoundInDatabase($domain);
      $this-&gt;addSubDomainsToDomain($domain, $data['subdomain']);
  
      return $domain;
  }
  
  /**
   * To prevent duplicate domains in database we have to set the id,
   * if we have found such domain already in DB
   * @param Domain $domain
   */
  protected function addIdToDomainIfFoundInDatabase(Domain $domain) {
      /** @var Domain|NULL $domainFromDatabase */
      $domainFromDatabase = $this-&gt;findDomainBySeid($domain-&gt;getSeid());
      if ($domainFromDatabase instanceof Domain) {
          $domain-&gt;setId($domainFromDatabase-&gt;getId());
      }
  }
  
  /**
   * find domain by seid
   *
   * @param int $seid
   * @return Domain|NULL
   */
  protected function findDomainBySeid($seid) {
      // do not use empty, because findOneBy can also return NULL as result
      if (!isset($this-&gt;cache['domainFromDatabase']) || (is_object($this-&gt;cache['domainFromDatabase']) &amp;&amp; $this-&gt;cache['domainFromDatabase']-&gt;getSeid() !== $seid)) {
          $this-&gt;cache['domainFromDatabase'] = $this-&gt;domainRepository-&gt;findOneBy(array(
              'seid' =&gt; $seid,
              'orderNumber' =&gt; $this-&gt;orderNumber
          ));
      }
      return $this-&gt;cache['domainFromDatabase'];
  }
  
  /**
   * add/override subdomains to domain object
   *
   * @param Domain $domain
   * @param array $subDomains These are the subDomains from request
   */
  protected function addSubDomainsToDomain(Domain $domain, array $subDomains) {
      $first = TRUE;
      $this-&gt;amountOfSubDomains += count($subDomains);
      $domainFromDatabase = $this-&gt;findDomainBySeid($domain-&gt;getSeid());
      if ($domainFromDatabase instanceof Domain) {
          $subDomainsFromDatabase = $domainFromDatabase-&gt;getSubDomains();
      } else {
          $subDomainsFromDatabase = new ArrayCollection();
      }
  
      foreach ($subDomains as $subDomain) {
          // create new SubDomain
          $subDomainObject = new SubDomain();
          $subDomainObject-&gt;setOrderNumber($this-&gt;orderNumber);
          $subDomainObject-&gt;fromArray($subDomain);
  
          // add ID to subDomain, if we have one already in DB
          // It's not perfect, but as long as we can have fully equal records in subdomain table
          // I don't see any chance to change that
          if ($first) {
              $subDomainFromDatabase = $subDomainsFromDatabase-&gt;first();
              $first = FALSE;
          } else {
              $subDomainFromDatabase = $subDomainsFromDatabase-&gt;next();
          }
          if ($subDomainFromDatabase instanceof SubDomain) {
              $subDomainObject-&gt;setId($subDomainFromDatabase-&gt;getId());
          }
  
          // add Order to Customer
          $domain-&gt;addSubDomain($subDomainObject);
      }
  }
  </code></pre>
  
  <p>Here are the annotation from order entity:</p>
  
  <pre class="lang-php prettyprint-override"><code>/**
   * Order -&gt; domains
   *
   * @var ArrayCollection
   * @ORM\OneToMany(targetEntity="FQCN\Domain", mappedBy="order", cascade={"all"})
   */
  protected $domains;
  </code></pre>
  
  <p>Here are the annotations from subdomain entity:</p>
  
  <pre class="lang-php prettyprint-override"><code>/**
   * relation from domain to order
   *
   * @ORM\ManyToOne(targetEntity="FQCN\Order", inversedBy="domains")
   */
  protected $order = NULL;
  
  /**
   * domain -&gt; subDomains
   *
   * @var ArrayCollection
   * @ORM\OneToMany(targetEntity="FQCN\SubDomain", mappedBy="domain", cascade={"all"})
   */
  protected $subDomains;
  </code></pre>
  
  <p>If I use only <code>flush()</code>, the <code>*_id</code> fields will not be filled. If I call <code>detach($order)</code>, the fields will be filled properly, but now I have a second (duplicate) of <code>$order</code> in my database. If I use <code>merge()</code> everything works on <em>INSERT</em>, but not if I want to update all my <code>ArrayCollections</code>.</p>
  
  <p>What I'm doing wrong? How to <em>UPDATE</em> <code>ArrayCollections</code> the right way <em>without</em> removing and creating them again? How to get the <code>*_id</code> field filled?</p>
  
  <p>Stefan</p>