在Zend框架2中使用MappedSuperclass进行多对多的Doctrine 2

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?

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:

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.