Symfony3 - Showing One to Many relations in a View

2019-08-20 08:57发布

Have these tables, entities (pseudo Code to show the relations).

article 

id
name
author_id
type_id
category_id

author

id 
name

type

id
name

category

id 
name

Now in my controller, I do this

  public function indexAction()
    {
        $articles = $this->getDoctrine()
                ->getRepository('AppBundle:Articles')
                ->findAll();
        $authors = $this->getDoctrine()
                ->getRepository('AppBundle:Author');
        $types = $this->getDoctrine()
                ->getRepository('AppBundle:Type');
        $categories = $this->getDoctrine()
                ->getRepository('AppBundle:Category');

        return $this->render('article/index.html.twig', array(
            'articles' => $articles,
            'authors' => $authors,
            'types' => $types,
            'categories' => $categories
        ));
    }

and in my View, this:

<tbody> 
            {% for exploit in exploits %}
            <tr> 
                <th scope="row">{{ article.id }}</th> 
                <td>{{ article.name }}</td> 

                <td> {{ authors.findOneById(article.author) }} </td>
                <td> {{ types.findOneById(article.type) }} </td>
                <td> {{ categories.findOneById(article.category) }} </td>

                <td>{{ article.date|date('F j, Y, g:i a') }}</td> 
                <td>
                    <a href="/details/{{ article.id }}" class="btn btn-success">View</a>
                    <a href="/edit/{{ article.id }}" class="btn btn-default">Edit</a>
                    <a href="/delete/{{ article.id }}" class="btn btn-danger">Delete</a>
                </td> 
            </tr> 
            {% endfor %}
        </tbody> 

This works, but it executes query for each entry, which is not optimal.

How do I display it in Controller and View easier? Without making so many queries, but just one.

Thanks,

Update 3:

Did as suggested by Salah, but still the same error:

Impossible to access an attribute ("name") on a integer variable ("1").

Instead of Article Entity, I use now Exploit Entity.

::::::::::::::
Author.php
::::::::::::::
<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Exploit;

/**
 * Author
 *
 * @ORM\Table(name="author", indexes={@ORM\Index(name="author_name_id_idx", columns={"id"})})
 * @ORM\Entity
 */
class Author
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $id;

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


/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Exploit", mappedBy="author", cascade={"persist", "remove"})
 * @ORM\JoinColumn(name="exploits", referencedColumnName="id")
 */
private $exploits;

/**
 * Author constructor.
 */
public function __construct()
{
    $this->exploits = new ArrayCollection();
}

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Author
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get id
     *
     */
    public function getId()
    {
        return $this->id;
    }

    public function __toString() 
    {
        return $this->name;
    }


/**
 * @param Exploits $exploit
 *
 * @return Author
 */
public function addExploit($exploit)
{
        $this->exploits->add($exploit);

    return $this;
}

/**
 * @param Collection $exploits
 *
 * @return Author
 */
public function setExploits(Collection $exploits)
{
    $this->exploits->clear();

    foreach ($exploits as $exploit) {
        $exploit->add($this);
    }

    $this->exploits = $exploits;

    return $this;
}
}
::::::::::::::
Category.php
::::::::::::::
<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Exploit;

/**
 * Category
 *
 * @ORM\Table(name="category", indexes={@ORM\Index(name="category_name_id_idx", columns={"id"})})
 * @ORM\Entity
 */
class Category
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $id;

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


/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Exploit", mappedBy="category", cascade={"persist", "remove"})
 * @ORM\JoinColumn(name="exploits", referencedColumnName="id")
 */
private $exploits;

/**
 * Author constructor.
 */
public function __construct()
{
    $this->exploits = new ArrayCollection();
}

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Author
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get id
     *
     */
    public function getId()
    {
        return $this->id;
    }

    public function __toString() 
    {
        return $this->name;
    }


/**
 * @param Exploits $exploit
 *
 * @return Author
 */
public function addExploit($exploit)
{
        $this->exploits->add($exploit);

    return $this;
}

/**
 * @param Collection $exploits
 *
 * @return Author
 */
public function setExploits(Collection $exploits)
{
    $this->exploits->clear();

    foreach ($exploits as $exploit) {
        $exploit->add($this);
    }

    $this->exploits = $exploits;

    return $this;
}
}
::::::::::::::
Exploit.php
::::::::::::::
<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Author;
use AppBundle\Entity\Type;
use AppBundle\Entity\Category;

/**
 * Exploit
 *
 * @ORM\Table(name="exploit", indexes={@ORM\Index(name=exploit_category_idx", columns={"category"}), @ORM\Index(name="exploit_type_idx", columns={"type"}), @ORM\Index(name="exploit_author_idx", columns={"
author"})})
 * @ORM\Entity
 */
class Exploit
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="edb_id", type="string", length=100, nullable=false)
     */
    private $edbId;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="date", type="datetime", nullable=false)
     */
    private $date;

     /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Author", inversedBy="exploits")
     * @ORM\JoinColumn(name="author", referencedColumnName="id")
     */
    private $author;

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

     /**
     * @var integer
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="exploits")
     * @ORM\JoinColumn(name="category", referencedColumnName="id")
     */
    private $category;

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

     /**
     * @var integer
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Type", inversedBy="exploits")
     * @ORM\JoinColumn(name="type", referencedColumnName="id")
     */
    private $type;

    /**
     * @var string
     *
     * @ORM\Column(name="content", type="text", nullable=false)
     */
    private $content;

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

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

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



    /**
     * Set edbId
     *
     * @param integer $edbId
     *
     * @return Exploit
     */
    public function setEdbId($edbId)
    {
        $this->edbId = $edbId;

        return $this;
    }

    /**
     * Get edbId
     *
     * @return integer
     */
    public function getEdbId()
    {
        return $this->edbId;
    }

    /**
     * Set date
     *
     * @param \DateTime $date
     *
     * @return Exploit
     */
    public function setDate($date)
    {
        $this->date = $date;

        return $this;
    }

    /**
     * Get date
     *
     * @return \DateTime
     */
    public function getDate()
    {
        return $this->date;
    }

    /**
     * Set author
     *
     * @param integer $author
     *
     * @return Exploit
     */
    public function setAuthor($author)
    {
        $this->author = $author;

        return $this;
    }

    /**
     * Get author
     *
     * @return integer
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Exploit
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set category
     *
     * @param integer $category
     *
     * @return Exploit
     */
    public function setCategory($category)
    {
        $this->category = $category;

        return $this;
    }

    /**
     * Get category
     *
     * @return integer
     */
    public function getCategory()
    {
        return $this->category;
    }

    /**
     * Set version
     *
     * @param string $version
     *
     * @return Exploit
     */
    public function setVersion($version)
    {
        $this->version = $version;

        return $this;
    }

    /**
     * Get version
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Set type
     *
     * @param integer $type
     *
     * @return Exploit
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * Get type
     *
     * @return integer
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Set content
     *
     * @param string $content
     *
     * @return Exploit
     */
    public function setContent($content)
    {
        $this->content = $content;

        return $this;
    }

    /**
     * Get content
     *
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * Set dork
     *
     * @param string $dork
     *
     * @return Exploit
     */
    public function setDork($dork)
    {
        $this->dork = $dork;

        return $this;
    }

    /**
     * Get dork
     *
     * @return string
     */
    public function getDork()
    {
        return $this->dork;
    }

    /**
     * Set softwareLink
     *
     * @param string $softwareLink
     *
     * @return Exploit
     */
    public function setSoftwareLink($softwareLink)
    {
        $this->softwareLink = $softwareLink;

        return $this;
    }

    /**
     * Get softwareLink
     *
     * @return string
     */
    public function getSoftwareLink()
    {
        return $this->softwareLink;
    }

    /**
     * Set testedOn
     *
     * @param string $testedOn
     *
     * @return Exploit
     */
    public function setTestedOn($testedOn)
    {
        $this->testedOn = $testedOn;

        return $this;
    }

    /**
     * Get testedOn
     *
     * @return string
     */
    public function getTestedOn()
    {
        return $this->testedOn;
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }
}
::::::::::::::
Type.php
::::::::::::::
<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Exploit;

/**
 * Type
 *
 * @ORM\Table(name="type", indexes={@ORM\Index(name="type_name_id_idx", columns={"id"})})
 * @ORM\Entity
 */
class Type
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $id;

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


/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Exploit", mappedBy="type", cascade={"persist", "remove"})
 * @ORM\JoinColumn(name="exploits", referencedColumnName="id")
 */
private $exploits;

/**
 * Type constructor.
 */
public function __construct()
{
    $this->exploits = new ArrayCollection();
}

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Type
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get id
     *
     */
    public function getId()
    {
        return $this->id;
    }

    public function __toString() 
    {
        return $this->name;
    }


/**
 * @param Exploits $exploit
 *
 * @return Type
 */
public function addExploit($exploit)
{
        $this->exploits->add($exploit);

    return $this;
}

/**
 * @param Collection $exploits
 *
 * @return Type
 */
public function setExploits(Collection $exploits)
{
    $this->exploits->clear();

    foreach ($exploits as $exploit) {
        $exploit->add($this);
    }

    $this->exploits = $exploits;

    return $this;
}
}

3条回答
何必那么认真
2楼-- · 2019-08-20 09:36

Create a custom Query With Join into your relations and addSelect(alias of your join).

Doctrine relation are in Lazy fetch mode, that's why it load relation only when you get it. If you make your custom query and you select relation doctrine hydrate them with only one query.

查看更多
等我变得足够好
3楼-- · 2019-08-20 09:44

In your indexAction(), you just have to write:

$articles = $this->getDoctrine()
            ->getRepository('AppBundle:Articles')
            ->findAll();

And in your view:

<th scope="row"> {{ article.id }} </th> 
        <td> {{ article.name }} </td> 
        <td> {{ article.author_id.name }} </td>
        <td> {{ article.type_id.name }} </td>
        <td> {{ article.category_id.name }} </td>
        <td>{{ article.date|date('F j, Y, g:i a') }}</td>  

P.S: Instead of author_id, type_id, category_id, you can name them: author, type, category without the '_id'

EDIT:

Article.php

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Author;
use AppBundle\Entity\Type;
use AppBundle\Entity\Category;

/**
 * Article
 *
 * @ORM\Table(name="Article", indexes={@ORM\Index(name="Article_category_idx", columns={"category"}), @ORM\Index(name="Article_type_idx", columns={"type"}), @ORM\Index(name="Article_author_idx", columns={"author"})})
 * @ORM\Entity
 */
class Article
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="edb_id", type="string", length=100, nullable=false)
     */
    private $edbId;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="date", type="datetime", nullable=false)
     */
    private $date;

     /**
     * @var Author
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Author", inversedBy="articles")
     * @ORM\JoinColumn(name="author", referencedColumnName="id")
     */
    private $author;

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

    /**
     * @var integer
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="articles")
     * @ORM\JoinColumn(name="category", referencedColumnName="id")
     */
    private $category;

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

    /**
     * @var integer
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Type", inversedBy="articles")
     * @ORM\JoinColumn(name="type", referencedColumnName="id")
     */
    private $type;

    /**
     * @var string
     *
     * @ORM\Column(name="content", type="text", nullable=false)
     */
    private $content;

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

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

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



    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set edbId
     *
     * @param string $edbId
     *
     * @return Article
     */
    public function setEdbId($edbId)
    {
        $this->edbId = $edbId;

        return $this;
    }

    /**
     * Get edbId
     *
     * @return string
     */
    public function getEdbId()
    {
        return $this->edbId;
    }

    /**
     * Set date
     *
     * @param \DateTime $date
     *
     * @return Article
     */
    public function setDate($date)
    {
        $this->date = $date;

        return $this;
    }

    /**
     * Get date
     *
     * @return \DateTime
     */
    public function getDate()
    {
        return $this->date;
    }

    /**
     * Set author
     *
     * @param Author $author
     *
     * @return Article
     */
    public function setAuthor($author)
    {
        $this->author = $author;

        return $this;
    }

    /**
     * Get author
     *
     * @return Author
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Article
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set category
     *
     * @param Category $category
     *
     * @return Article
     */
    public function setCategory($category)
    {
        $this->category = $category;

        return $this;
    }

    /**
     * Get category
     *
     * @return Category
     */
    public function getCategory()
    {
        return $this->category;
    }

    /**
     * Set version
     *
     * @param string $version
     *
     * @return Article
     */
    public function setVersion($version)
    {
        $this->version = $version;

        return $this;
    }

    /**
     * Get version
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Set type
     *
     * @param Type $type
     *
     * @return Article
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * Get type
     *
     * @return Type
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Set content
     *
     * @param string $content
     *
     * @return Article
     */
    public function setContent($content)
    {
        $this->content = $content;

        return $this;
    }

    /**
     * Get content
     *
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * Set dork
     *
     * @param string $dork
     *
     * @return Article
     */
    public function setDork($dork)
    {
        $this->dork = $dork;

        return $this;
    }

    /**
     * Get dork
     *
     * @return string
     */
    public function getDork()
    {
        return $this->dork;
    }

    /**
     * Set softwareLink
     *
     * @param string $softwareLink
     *
     * @return Article
     */
    public function setSoftwareLink($softwareLink)
    {
        $this->softwareLink = $softwareLink;

        return $this;
    }

    /**
     * Get softwareLink
     *
     * @return string
     */
    public function getSoftwareLink()
    {
        return $this->softwareLink;
    }

    /**
     * Set testedOn
     *
     * @param string $testedOn
     *
     * @return Article
     */
    public function setTestedOn($testedOn)
    {
        $this->testedOn = $testedOn;

        return $this;
    }

    /**
     * Get testedOn
     *
     * @return string
     */
    public function getTestedOn()
    {
        return $this->testedOn;
    }
}

Type.php:

<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Author;

/**
 * Type
 *
 * @ORM\Table(name="type", indexes={@ORM\Index(name="type_name_id_idx", columns={"id"})})
 * @ORM\Entity
 */
class Type
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $id;

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


/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Article", mappedBy="type", cascade={"persist", "remove"})
 * @ORM\JoinColumn(name="articles", referencedColumnName="id")
 */
private $articles;

/**
 * Type constructor.
 */
public function __construct()
{
    $this->articles = new ArrayCollection();
}

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Type
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get id
     *
     */
    public function getId()
    {
        return $this->id;
    }

    public function __toString() 
    {
        return $this->name;
    }


/**
 * @param Articles $article
 *
 * @return Type
 */
public function addArticle($article)
{
        $this->articles->add($article);

    return $this;
}

/**
 * @param Collection $articles
 *
 * @return Type
 */
public function setArticles(Collection $articles)
{
    $this->articles->clear();

    foreach ($articles as $article) {
        $article->add($this);
    }

    $this->articles = $articles;

    return $this;
}
}

And then you do the same as Type for Category and Author

查看更多
一夜七次
4楼-- · 2019-08-20 09:45

In your Entities other than Exploit

Before

/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Exploit",mappedBy="author", cascade={"persist", "remove"})
 * @ORM\JoinColumn(name="exploits", referencedColumnName="id")
 */
private $exploits;

Change them to

/**
 * @ORM\OneToMany(targetEntity="AppBundle\Entity\Exploit",mappedBy="author", cascade={"persist", "remove"})
 */
private $exploits;

This is because entity Exploit is the owning side of all relations. Run bin/console doctrine:schema:update --force to update database structure.

In Exploit Entity

/**
 * Exploit
 *
 * @ORM\Table(name="exploit", indexes={@ORM\Index(name=exploit_category_idx", columns={"category"}), @ORM\Index(name="exploit_type_idx", columns={"type"}), @ORM\Index(name="exploit_author_idx", columns={"
author"})})
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ExploitRepository")
 */
class Exploit
{
// class properties and methods as is
}

In your controller

 public function indexAction()
    {
        $exploits = $this->getDoctrine()
                ->getRepository('AppBundle:Exploit')
                ->fetchAllExploits();

        return $this->render('article/index.html.twig', array(
            'exploits' => $exploits
        ));
    }

In your repository class

class ExploitRepository extends EntityRepository
{
    public function fetchAllExploits(){
        return $this
        ->createQueryBuilder('e')
        ->leftJoin('e.author','a')
        ->addSelect('a')
        ->leftJoin('e.type','t')
        ->addSelect('t')
        ->leftJoin('e.category','c')
        ->addSelect('c')
        ->getQuery()
        ->getResult();

    }
}

In your view

<tbody> 
            {% for exploit in exploits %}
            <tr> 
                <th scope="row">{{ exploit.id }}</th> 
                <td>{{ exploit.name }}</td> 

                <td> {{ exploit.author.name }} </td>
                <td> {{ exploit.type.name }} </td>
                <td> {{ exploit.category.name }} </td>

                <td>{{ exploit.date|date('F j, Y, g:i a') }}</td> 
                <td>
                    <a href="/details/{{ exploit.id }}" class="btn btn-success">View</a>
                    <a href="/edit/{{ exploit.id }}" class="btn btn-default">Edit</a>
                    <a href="/delete/{{ exploit.id }}" class="btn btn-danger">Delete</a>
                </td> 
            </tr> 
            {% endfor %}
        </tbody> 
查看更多
登录 后发表回答