Pal Chet
Pal Chet

Reputation: 109

Need help getting OneToMany association from ManyToOne

Let me start off by saying I asked a similar question before and got it answered. I tried using those principles here but I'm stuck again am am not sure where to go from here.

I have a page where I list out all the 'products' along with their respected id's, price, and name. On this same page I want to get a description I created for each of them. Description is its own entity and has it's own controller.

In my ProductController, within my indexAction, I'm trying to get the description to appear here.

The problem is, I don't reference an id within indexAction (I use findAll). I have tried to loop through all products and reference using $key but I either get the most recent description entered in description or currently:

Error: Call to a member function getDescriptions() on a non-object.

EDIT: I should mention $prodEnt is null...

I didn't want to come here for help but I have no more thoughts on how to go about getting what I want.

Here is ProductController with indexAction:

namespace Pas\ShopTestBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Pas\ShopTestBundle\Entity\Product;
use Pas\ShopTestBundle\Form\ProductType;

/**
 * Product controller.
 *
 * @Route("/product")
 */
class ProductController extends Controller
{
    /**
     * Lists all Product entities.
     *
     * @Route("/", name="product")
     * @Method("GET")
     * @Template()
     */
    public function indexAction()
    {
        $em = $this->getDoctrine()->getManager();

        $entities = $em->getRepository('PasShopTestBundle:Product')->findAll();

        //$entities = $em->getRepository('PasShopTestBundle:Product')->find($id);
        //var_dump($entities);
        //dump($entities); die;

        if (!$entities) {
            throw $this->createNotFoundException('Error Nothing in Entities.');
        } 
        else {
            //dump($entities); die;
            foreach ($entities as $key => $entity) {
                //dump($entities); die;
                //dump($entity); die;
                //dump($key); die; //needs to be 1
                //$entity = $em->getRepository('PasShopTestBundle:Product')->findAll($key);
                $prodEnt = $em->getRepository('PasShopTestBundle:Product')->find($key);
                //dump($entity); die;
                //dump($prodEnt); die;
                $descriptions = $prodEnt->getDescriptions();
                //dump($entity); die;
            }
            //dump($entities); die;
        }

        return array(
            'descriptions' => $descriptions,
            'entities' => $entities,
            'entity' => $entity,
        );
    }

Here is indexActions Route twig file:

{% extends '::base.html.twig' %}

{% block body -%}
    <h1>Product List</h1>

    <table class="records_list">
        <thead>
            <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Price</th>
                <th>Quantity</th>
                <th>Description</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
        {% for entity in entities %}
            <tr>
                <td><a href="{{ path('product_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
                <td>{{ entity.name }}</td>
                <td>{{ entity.price }}</td>
                <td>{{ entity.quantity }}</td>

                {% for key, entity in descriptions %}

                    <pre>{{ dump(entity) }}</pre>

                   {# <pre>{{ dump(key) }}</pre> #}

                        <td>{{ entity.productDesciption }}</td>

                    <pre>{{ dump(entity.productDesciption) }}</pre>

                {% endfor %}

                <td>
                    <ul>
                        <li>
                            <a href="{{ path('product_cart', { 'id': entity.id }) }}">Add Product To Cart</a>
                        </li>
                        <li>
                            <a href="{{ path('product_show', { 'id': entity.id }) }}">show</a>
                        </li>
                        <li>
                            <a href="{{ path('product_edit', { 'id': entity.id }) }}">edit</a>
                        </li>
                    </ul>
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>

        <ul>
        <li>
            <a href="{{ path('product_new') }}">
                Create a new entry
            </a>
        </li>
    </ul>
    {% endblock %}

Product Entity:

namespace Pas\ShopTestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Product
 *
 * @ORM\Table(name="products")
 * @ORM\Entity(repositoryClass="Pas\ShopTestBundle\Entity\ProductRepository")
 */
class Product
{
    /**
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="Description", mappedBy="product")
     */
    private $descriptions;

Description Entity:

namespace Pas\ShopTestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collection\ArrayCollection;

/**
 * Description
 *
 * @ORM\Table(name="descriptions")
 * @ORM\Entity(repositoryClass="Pas\ShopTestBundle\Entity\DescriptionRepository")
 */
class Description
{
    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string")
     */
    private $productDescription;

    /**
     * @var Product
     *
     * @ORM\ManyToOne(targetEntity="Product", inversedBy="descriptions")
     * @ORM\JoinColumn(name="product_id", referencedColumnName="id")
     */
    private $product;

Any help is really appreciated, Thanks!

Upvotes: 4

Views: 60

Answers (3)

Jason Roman
Jason Roman

Reputation: 8276

You are way over-complicating the situation. When you retrieve all of your product entities, you don't need to do anything else other than return those entities in your response. Each entity already has its descriptions associated to it in your class definition. Also, in a typical index action you do not necessarily need to throw an exception if no products exist...you probably still want to show the user the index page, and then if there aren't any products they will just see an empty table. Your indexAction() can simply be:

public function indexAction()
{
    $em = $this->getDoctrine()->getManager();

    $entities = $em->getRepository('PasShopTestBundle:Product')->findAll();

    return array(
        'entities' => $entities,
    );
}

Your Twig is also going to cause problems the instant a single product has more than one description, because the number of <td> cells is going to be variable per row. It would be better to do something like display a comma-separated list of descriptions, or perhaps separated by a <br> or <p>, or you could even make them an unordered list via <ul> and <li>:

{% for entity in entities %}
    <tr>
        <td><a href="{{ path('product_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
        <td>{{ entity.name }}</td>
        <td>{{ entity.price }}</td>
        <td>{{ entity.quantity }}</td>

        {% for description in entity.descriptions %}
            {{ description.productDescription }}{% if not loop.last %}, {% endif %}
        {% endfor %}
        {# ... #}
    </tr>
{% endfor %}

That example gives a comma-separated list of descriptions, but you could change that loop however you see fit. Also, you can add a __toString() method to your Description entity to avoid having to call that productDescription field directly:

// class Description
public function __toString()
{
    return $this->getProductDescription();
}

Which would then allow you to do the following for your Twig:

{% for description in entity.descriptions %}
    {{ description }}{% if not loop.last %}, {% endif %}
{% endfor %}

Upvotes: 2

Andre Cardoso
Andre Cardoso

Reputation: 238

I use another answer to put a formatted code.

If you has all products, and the relationship is correct, you just use them in loop.

foreach ($entities as $entity) {
   // To get the descriptions...
   $entity->getDescriptions();
}

By the way your code is very, very strange. First you are retrieving all products, after this, in a loop with this products, you are retrieving each product again and getting their descriptions. What you didn't care is:

In your controller:

$entities is storing all products.

$entity always will be the last product, it are defined in loop

$descriptions always will be from the last product

Now you send these variables to your view.

In your view:

You are doing a loop with the entities and declaring them as entity, this override the entity you passed in controller. By the way pass $entity and $descriptions to view is unnecessary, you are doing the loop with entities, just get these info in loop.

Upvotes: 1

Andre Cardoso
Andre Cardoso

Reputation: 238

Probably you are working with an unitialized object, try to set the fetch level.

In your Product Entity you must to set a fetch level from relationship.

Fixing you relationship:

namespace Pas\ShopTestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Product
 *
 * @ORM\Table(name="products")
 * @ORM\Entity(repositoryClass="Pas\ShopTestBundle\Entity\ProductRepository")
 */
class Product
{
    /**
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="Description", mappedBy="product", fetch="EAGER")
     */
    private $descriptions;

Upvotes: 0

Related Questions