简体   繁体   中英

How to query related entities based on their value

I have Ads on my website. For an Ad to be publicly displayed, it must be paid for. An ad is public or paid for if it has no payment that is unpaid, or all its payments are paid for.

What I want is to retrieve all public ads, but that requires a large number of queries using this method. I'm looking for a better way.

This has more fields, but let's focus on the payments field. It's a OneToMany.

class Ad
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;


    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255,nullable=true)
     */
    private $adTitle;


    /**
     * @var string
     *
     * @ORM\Column(name="description", type="string", length=255,nullable=true)
     */
    private $description;



    /**
     * @ORM\ManyToOne(targetEntity="Application\FOS\UserBundle\Entity\User")
     * @ORM\JoinColumn(name="advertiser")
     */
    private $advertiser;


    /**
     * @ORM\OnetoMany(targetEntity="MyBundle\Entity\Payment",mappedBy="ad",cascade={"persist"})
     * @ORM\JoinColumn(name="payments", referencedColumnName="id",nullable=true)
     */
    private $payments;

}

For payments, I'm using PayumBundle, so the entity looks like so:

use Payum\Core\Model\Payment as BasePayment;
class Payment extends BasePayment
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     *
     * @var integer $id
     */
    protected $id;

    /**
     * @ORM\ManyToOne(targetEntity="MyBundle\Entity\Ad",inversedBy="payments",cascade={"persist"})
     * @ORM\JoinColumn(name="ad", referencedColumnName="id",nullable=true)
     */
    protected $ad;


}

Currently I'm using a service that does something like this:

  public function getAllPublicAds()
    {
        $allAds = $this->em->getRepository('MyBundle:Ad')->findAll();

        $publicAds=array();

        foreach($allAds as $ad){
            if($this->isPublicAd($ad)) array_push($publicAds,$ad);
        }

        return $publicAds;

    }

I'm getting all the ads, and if they are public, I put them into an array which will later go to the twig view.

An Ad is public if all of its payments are paid, therefore:

public function isPublicAd(Ad $ad)
{

    $payments = $ad->getPayments();

    if (!count($payments)) {
        return false;
    } else {
        foreach ($payments as $payment) {
            if (!$this->paidPayment($payment)) return false;
        }

        return true;
    }
 }

And finally, a paid Payment is paid if its details field 'paid' in the table is true. This is how PayumBundle works. It creates a token, then after you use that token to pay for your object, it fills that object's payment field details with details about the payment. You can find the class here . My Payment class from above extends it. So I'm checking whether those details have been filled, or if the 'paid' field is true.

public function paidPayment(Payment $payment)
{
    $details = $payment->getDetails();

    if (array_key_exists("paid", $details) && $details["paid"]) return true;

    else return false;

}

The problem is that this creates a very big and unnecessary number of queries to the database. Is it possible to shorten this out? I'm not sure I can use normal queries without Doctrine, because I need to check the details['paid'] field, whcih I can only do in a controller or service, after I've received the database results. If that is even possible to do inside a query, I don't know either nor seem to find a way to do it.

You should use QueryBuilder instead of findAll method. In such case you can specify conditions which need to be match (just like in plain sql ).

So:

$paidAds =  $this->em->getRepository('MyBundle:Ad')
                ->createQueryBuilder('a')
                ->innerJoin('a.Payments', 'p')
                ->getQuery()
                ->getResult();

I didn't analyze deep your db structure by in fact innerJoin might be only condition which you need since you will get only those Ads which have relation to payments

To read more about QueryBuilder check doc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM