Symfony add data to object on pre persist

2019-03-02 09:59发布

问题:

I have a form to create documents. On the one side I can add names and descriptions and next to that I can select one or several agencies to whom the created document belongs. Each of the agencies is assigned to one specific market (there are 7 markets in total, so one market can have several agencies but one agency belongs only to one market!) What I want to achieve is a "prePersist" function which automatically adds the correct market(s) (depending on the number of agencies selected) to the document.

My document entity has the two entities (markets and agencies) with the according getters and setters:

 /**
* @ORM\ManyToMany(targetEntity="AppBundle\Entity\Market", inversedBy="uploadProfiles", cascade={"persist"})
* @ORM\JoinTable(name="document_uploadprofile_markets",
*   joinColumns={@ORM\JoinColumn(name="uploadprofile_id", referencedColumnName="id")},
*   inverseJoinColumns={@ORM\JoinColumn(name="market_id", referencedColumnName="id")})
**/
private $markets;

        /**
         * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Agency", inversedBy="uploadProfiles", cascade={"persist"})
         * @ORM\JoinTable(name="document_uploadprofile_agencies",
         *   joinColumns={@ORM\JoinColumn(name="uploadprofile_id", referencedColumnName="id")},
         *   inverseJoinColumns={@ORM\JoinColumn(name="iata8", referencedColumnName="iata8")})
         **/
        private $agencies;
  public function __construct()
    {
        $this->agencies = new \Doctrine\Common\Collections\ArrayCollection();
    $this->markets = new \Doctrine\Common\Collections\ArrayCollection();

}

       /**
 * Add market
 *
 * @param \AppBundle\Entity\Market $market
 *
 * @return UploadProfile
 */
public function addMarket(\AppBundle\Entity\Market $market)
{
    $this->markets[] = $market;

    return $this;
}

/**
 * Remove market
 *
 * @param \AppBundle\Entity\Market $market
 */
public function removeMarket(\AppBundle\Entity\Market $market)
{
    $this->markets->removeElement($market);
}

/**
 * Get markets
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getMarkets()
{
    return $this->markets;
}
  /**
     * Add agency
     *
     * @param \AppBundle\Entity\Agency $agency
     *
     * @return UploadProfile
     */
    public function addAgency(\AppBundle\Entity\Agency $agency)
    {
        $this->agencies[] = $agency;

        return $this;
    }

    /**
     * Remove agency
     *
     * @param \AppBundle\Entity\Agency $agency
     */
    public function removeAgency(\AppBundle\Entity\Agency $agency)
    {
        $this->agencies->removeElement($agency);
    }

    /**
     * Get agencies
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getAgencies()
    {
        return $this->agencies;
    }

I know that I can add the prePersist function to my document entity and try to code what I want to achieve but I don't think that's working because I need something like that:

  foreach($document->getAgencies() as $agency) {
      $document->setMarket($em->getRepository('AppBundle:Agency')->getMarket($agency));
     }

I'm not even sure whether that foreach loop is correct since (so far) the result is always null. I've already asked a question to that topic here: Symfony use setter for Arraycollection in CreateController

I've also tried to write an own repository function to get all the distinct markets from my agency entity but so far that's not working either.

Another idea was a POST_SUBMIT event listener in my form class but so far that didn't make sense to me either.

Any ideas? If more code is needed, let me know!

edit I edited and changed my code above in order to have a manytomany relationship between my markets and documents. What I then tried is, adding the prePersist function to my document entity and it actually worked fine while still having the OneToMany relationship (it was just always overwriting the previous market, but that doesn't matter now) I'm now trying to edit that function so that several markets can be added to the document. Two ideas I had, but they both didn't work out:

if(count($this->getAgencies()) > 0){
      foreach($this->getAgencies() as $agency) {
        $this->addMarket($agency->getMarket());
      }
}

--> market is always null

or

if(count($this->getAgencies()) > 0){
      $upId = rtrim($this->getId(),"_up");
      $query = $em->createQuery("SELECT DISTINCT (a.market) FROM UserBundle\Entity\User u JOIN u.agencies a WHERE u.id = $userId");
      $marketIds = $query->getResult();

      $em = $this->getDoctrine ()->getManager ();
      $repository = $this->getDoctrine()
      ->getRepository('AppBundle:Market');
      $markets = $repository->findOneById($marketIds);
      $this->addMarket($markets);
    }
  }

update

here is my prepersist function within my document entity and then the getMarkets() function, that has been suggested in one of the comments. I changed the name to addMarkets instead of getMarkets

/**
    * @ORM\PrePersist
    */
    public function prePersist() {
if(count($this->getAgencies()) > 0){
      foreach($this->getAgencies() as $agency) {
        $this->addMarkets($agency->getMarket());
      }
    }
  }

public function addMarkets(\AppBundle\Entity\Market $market)
  {
      $markets = array();
      foreach($this->agencies as $agency) {
          $market = $agency->getMarket();
          $id     = $market->getId();

          // Skip duplicates
          if (isset($markets['id'])) {
              continue;
          }

          $markets[$id] = $market;
      }

      return $markets;
  }

another approach

so I edited it again, now my function looks like that

$markets = $this->getMarkets();
if(count($this->getAgencies()) > 0){
  foreach($this->getAgencies() as $agency) {
    if(!$this->markets->contains($markets)) {
      $this->addMarket($agency->getMarket());
    }
    return;
    dump($markets);
  }
}

I thought that this might work to eliminate my duplicates but it does not.. any idea why?

回答1:

This is a structural error in logic. And the clue is in your question and code.

automatically adds the correct market(s)

And:

        /**
        * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Market")
        * @ORM\JoinColumn(name="market", referencedColumnName="id")
        * @var \AppBundle\Entity\Market
        **/
        private $market;

Are incompatible.

If a document can have many agencies and agencies can have one market then your document must allow for many markets. For example:

DocumentA has Agency1 and Agency2. 

Agency1 has MarketParis.

Agency2 has MarketMarrakesh.

This necessarily means that DocumentA has (Agency1's) MarketParis and (Agency2's) MarketMarrakesh -- many markets.

The question you're asking is a much larger topic than just setting or getting. If you want ONLY one market per document then you'll have to enforce uniqueness among document agencies. For example:

Your form creates DocumentA.

The user tries to set Agency1 (MarketParis) and Agency2 (MarketMarrakesh).
This throws an error because there can be ONLY ONE market.

Or another example:

Your form creates DocumentA

The user tries to set Agency1 (MarketParis) and Agency3 (MarketParis). 
This is successful because the uniqueness of the Document's Market is enforced. 

There are many strategies for this and is a much larger topic than your question.

EDIT

If your cardinality logic is correct (fixing any issues I described above) AND your doctrine annotations include cascading persist in all your entities, which look correct from the code above. The only thing I can think of that would not be working correctly is the "by_reference" attribute of your form. Setting "by_reference" to false in your form, with cascade-persist set in your entity, should persist all entities associated with the form. See the by_reference doc here: http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference



回答2:

If I understand what you said correctly, I believe Documents should not have a reference to Markets at all. But only references Agency.

A Document will have a ManyToMany relation with Agency and that's it.

Then in Document you can do something like:

public function getMarkets()
{
    $markets = array();
    foreach($this->agencies as $agency) {
        $market = $agency->getMarket();
        $id     = $market->getId();

        // Skip duplicates
        if (isset($markets[id]) {
            continue;
        }

        $markets[$id] = $market;
    }

    return $markets;
}


回答3:

This looks like wrong approach. It makes more sense to have oneToMany relationship between Market and Agency and oneToMany between Agency and Document.