I have two entities where each Product can have oneToMany Aspect entities associated to it.

As the Products table is very large I am using bigint for it's ID, and consequently, I am trying to build a composite key for Aspect to use Product ID and a smallint (which I am trying to increment with Product#aspectsCount). However, I am getting a ContextErrorException:

Notice: Undefined index: aspect

My entities are as below (I originally tried indexBy="id") in the hope of using Aspect's numeric ID but I can't seem to get that working either so used name below to be more consistent with examples I've read online):

Product Entity

class Product
{
    /**
     * @ORM\Column(type="bigint", options={"unsigned"=true})
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\OneToMany(targetEntity="Aspect", mappedBy="product", cascade={"all"}, indexBy="name")
     */
    private $aspects;

    /**
     * @ORM\Column(name="aspectsCount", type="smallint", options={"unsigned"=true}, nullable=false)
     */
    private $aspectsCount;

    public function __construct()
    {
        $this->aspects = new ArrayCollection();

        $this->setCreateDT(new \Datetime);
        $this->setUpdateDT(new \Datetime);
        $this->aspectsCount = 0;
    }


    /**
     * Add aspect
     *
     * @param \AppBundle\Entity\Aspect $aspect
     *
     * @return product
     */
    public function addAspect($name)
    {
        $aspect = new Aspect($this, $name);
        $this->aspects[$name] = $aspect;

        return $this;
    }

    /**
     * Remove aspect
     *
     * @param \AppBundle\Entity\Aspect $aspect
     */
    public function removeAspect(\AppBundle\Entity\Aspect $aspect)
    {
        $this->aspects->removeElement($aspect);
        $this->setAspectsCount($this->aspectsCount-1);
    }
}

Aspect Entity

class Aspect
{
    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Product", inversedBy="aspects") 
     * @ORM\JoinColumn(name="product_id", referencedColumnName="id")
     */
    private $product;

    /**
     * @ORM\Id
     * @ORM\Column(type="smallint", options={"unsigned"=true}, nullable=false)
     */
    private $id;

    /**
     * @ORM\Column(name="name", type="text")
     */
    private $name;

    public function __construct($product, $name)
    {
        $product->setAspectsCount($product->getAspectsCount()+1);

        $this->product = $product;
        $this->id = $product->getAspectsCount();
        $this->name = $name;
    }

 }

By extension, if another table should exist "underneath" Aspect, how would such an association be made? Would Doctrine handle the composite key internally or would I need to do something such as:

class Aspect_subtype
{
    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Product")   
     * @ORM\JoinColumn(name="product_id", referencedColumnName="id")
     */
    private $product;

    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Aspect")    
     * @ORM\JoinColumn(name="aspect_id", referencedColumnName="id")
     */
    private $aspect;

    /**
     * @ORM\Id
     * @ORM\Column(type="smallint", options={"unsigned"=true}, nullable=false)
     */
    private $id;

    /**
     * @ORM\Column(name="name", type="text")
     */
    private $name;

// etc...
}
2

There are 2 answers

1
G. Mansour On BEST ANSWER

in product Entity change the anotations of $Aspets to this

     /**
     *@ORM\ManyToOne(targetEntity="Aspect")
     *@ORM\JoinColumn(name="Client", referencedColumnName="product_id",onDelete="CASCADE")
     */
    private $aspects;

Then you need to update your database schema

1
grssn On

I guess the problem occurs when you do $this->aspects[$name] = $aspect; It is an ArrayCollection, not an array and even though in most cases it works like one, you cannot set a name for an item in the collection. Besides ArrayCollections are more complex objects and also allow querying and ordering them. Simply call $this->aspects->add($aspect);

As per your last question, you can define multiple join columns, when specifying a manyToOne relationship, so you would just add definitions for multiple columns, instead of one in the case, you needed to relate to an entity which has a composite key.

Lastly, I would like to note one thing form my personal experience: many features, that are built into programming languages and programs as well, often add unnecessary complexity to the software you are writing. If you have the ability (and most of the time you do) you should shy away from things like composite keys/inheritance/etc... . Often times they prove to provide no real benefit, yet might become a nightmare to work with in the future. There are cases when you cannot do without them, but those are few and far between.

In your case you are already wasting time contemplating how would this composite key thingy work when you need to add relations (also composite keys are a bit less performant when it comes to querying) yet even if you didn't use the composite key (just the regular id on the aspect entity instead), calling the getAspects() method would still provide you with an arraycollection that is ordered by ID(by default. this can be changed), so you would still be able to number the items and the ordering will stay the same. Even if you had to implement something like ability for users to change the order of elements, you could still simply add another order field to the entity, and not spend a second thinking how composite key here is going to interact with the rest of the system. And again, these problems do not disappear, just because you find solutions now, they will also add to the complexity of the program in the future too.