0

I am new to Doctrine2 and trying to create entities for the following DB structure: enter image description here

I want to have all machine parts as an array in one attribute of the machine class. I tried this:

class Machine {
    ....
    /**
     * @var array
     * @ORM\OneToMany(targetEntity="MachineHasPart", mappedBy="machine", cascade={"persist", "remove"}, orphanRemoval=TRUE)
     */
    private $parts;
    ....

    public function getParts () {
        return array_map(
            function ($machineHasPart) {
                return $machineHasPart->getPart();
            },
            $this->parts->toArray()
        );
    }
}

Where MachineHasPart is a @MappedSuperclass for the intermediate entities/tables (like machineHasCylinder etc), but it failed with:

An exception occurred while executing 'SELECT FROM machineHasPart t0'.

Should I restructure my database to use ORM here? Or there is a solution for my case?

Wilt
  • 41,477
  • 12
  • 152
  • 203
SBay
  • 23
  • 4

2 Answers2

1

You cannot query a @MappedSuperClass. This is also mentioned in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses:

A mapped superclass cannot be an entity, it is not query-able and persistent

This means you have to either change the target entity to something queryable or you have to make MachineHasPart to a entity and change to single table inheritance.

When I look at your database structure I would suggest changing your Machine entity to have three independent relationships for the parts. One for Belt, one for Cylinder and one for Gear.

Then instead of a generic getParts you will have three methods getBelts, getCylinders and getGears.

If that is really not what you want then you can leave a comment.

UPDATE

You can solve it also with class inheritance. First make a base class Part that is also an entity and use it in the other classes Belt, Cylinder and Gear:

Part:

<?php

namespace Machine\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Part
 *
 * @ORM\Entity
 * @ORM\Table("part")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discriminator", type="string")
 * @ORM\DiscriminatorMap({
 *     "part" = "Part",
 *     "gear" = "Gear",
 *     "cylinder" = "Cylinder",
 *     "belt" = "Belt",
 * })
 * @property int $id
 */
class Part
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var Machine
     * @ORM\ManyToOne(targetEntity="Machine\Entity\Machine", inversedBy="parts")
     * @ORM\JoinColumn(name="machine_id", referencedColumnName="id", nullable=true)
     */
    protected $machine;

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

    /**
     * Set id.
     *
     * @param int $id
     * @return self
     */
    public function setId($id)
    {
        $this->id = $id;

        return  $this;
    }

    //... add setters and getters for machine as normal ...
}

Extend this class in your other parts:

Belt:

<?php

namespace Machine\Entity;

/**
 * Belt
 *
 * @ORM\Entity
 */
class Belt extends Part
{
}

Cylinder:

<?php

namespace Machine\Entity;

/**
 * Cylinder
 *
 * @ORM\Entity
 */
class Cylinder extends Part
{

}

Gear:

<?php

namespace Machine\Entity;

/**
 * Gear
 *
 * @ORM\Entity
 */
class Gear extends Part
{

}

Now in your machine relate to the parts like as follows.

Machine:

<?php

namespace Machine\Entity;

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

/**
 * Machine
 *
 * @ORM\Entity
 * @ORM\Table("machine")
 * @property int $id
 */
class Machine
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

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

    /**
     * Set id.
     *
     * @param int $id
     * @return self
     */
    public function setId($id)
    {
        $this->id = $id;

        return  $this;
    }

    /**
     * @var Collection
     * @ORM\OneToMany(targetEntity="Machine\Entity\Part", mappedBy="machine")
     */
    protected $parts;

    public function __constuct()
    {
        $parts = new ArrayCollection();
    }

    /**
     *
     * @return Collection
     */
    public function getParts()
    {
        return $this->parts;
    }

    //... add setters and getters for parts as normal ...
}

Extend this class in your other parts:

Wilt
  • 41,477
  • 12
  • 152
  • 203
  • Thank you for the reply. The problem is that there is actually more than 3 entities as "part". So it will be not suitable to add a method for each entity of the e.g. 20 parts. I need them also to be sort ordered by the "sort_order" attribute. – SBay Aug 03 '15 at 17:46
  • @SBay I will reply tomorrow with another solution that will suffice your demands. – Wilt Aug 03 '15 at 19:01
  • Thank you @Wilt, looking forward to it. – SBay Aug 05 '15 at 08:58
  • @SBay, check my update. If any issues while implementing it please comment. – Wilt Aug 05 '15 at 09:32
  • Thanks @Wilt for the Update. Question: Which column on which table is the *discriminator* of the line: `@ORM\DiscriminatorColumn(name="discriminator", type="string")` Could you please give an idea how the database tables should look like? Because your solution seems as if you are supposing all parts have similar attributes (which is not the case). Thank you in advance. – SBay Aug 05 '15 at 19:27
  • @SBay You can add fields to the sub-entities. Those fields will be added to the table. For the other sub-entities that don't have these fields the value will simply be set to `null`. Read also [here in the documentation for more information](http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#sql-schema-considerations). – Wilt Aug 06 '15 at 13:42
  • @Sbay If you don't want to have this (the null columns for the other sub-entities) you can also swap to [class table inheritance](http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#class-table-inheritance). – Wilt Aug 07 '15 at 07:40
0

Reading further in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses (referred to by @Wilt):

... Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment...

This means in this case the ORM mapping doesn't help. I cannot gather the data of all three entities MachineHasCylinder, MachineHasBelt and MachineHasGear through a MappedSupperclass at the same time. I think using DQL or Native SQL is the only solution for this problem.

SBay
  • 23
  • 4
  • How does DQL help you with this? DQL is an object query language for querying your database model. The database model itself will not be different when you are using DQL thus it cannot solve your issue. – Wilt Aug 07 '15 at 07:43