创建包含另一个现有实体的新实体

问题描述 投票:0回答:1

我有以下两个表和相应的两个实体显示在这篇文章的底部。 time_unit只包含几个预设记录,分别是s/second/1m/minute/60h/hour/360等。

我需要创建一个新的时间表。虽然没有显示,但是我有几种类型的调度,它们以不同的方式使用提供的数据,因此希望将setter放在实体内(构造函数或某种接口方法)而不是服务中。要创建新计划,我执行$scheduleService->create(['name'=>'the schedule name', 'other_data'=>123, 'time_unit'=>'h']);

<?php
namespace Michael\App\Service;
use Michael\App\Entity;
class ScheduleService
{
    public function create(array $params):int {
        //validation as applicable
        $schedule=new Entity\Schedule($params);
        $this->em->persist($schedule);
        $this->em->flush();
        return $schedule->getId();
    }
}

然后在Schedule实体中添加以下构造函数:

public function __construct(array $params) {
    $this->setName($params['name']);
    $this->setOtherData($params['other_data']);
    $timeUnit=new TimeUnit();
    $timeUnit->setUnit($params['time_unit']);
    $this->setTimeUnit($timeUnit);
}

但这不起作用,因为我正在创建一个新的TimeUnit实例,而Doctrine会抱怨。

作为替代方案,我可以通过安排实体经理,但我读过的所有内容都表明这样做是不好的做法。

如何创建包含另一个现有实体的新实体?


没有附加逻辑的模式和基本实体如下所示:

enter image description here

 CREATE TABLE schedule (id INT NOT NULL, time_unit VARCHAR(1) NOT NULL, name VARCHAR(45) NOT NULL, other_data VARCHAR(45) NOT NULL, INDEX fk_schedule_time_unit_idx (time_unit), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
 CREATE TABLE time_unit (unit VARCHAR(1) NOT NULL, name VARCHAR(45) NOT NULL, seconds INT NOT NULL, PRIMARY KEY(unit)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
 ALTER TABLE schedule ADD CONSTRAINT FK_5A3811FB7106057E FOREIGN KEY (time_unit) REFERENCES time_unit (unit);

schedule.php

<?php

namespace Michael\App\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
* Schedule
*
* @ORM\Table(name="schedule", indexes={@ORM\Index(name="fk_schedule_time_unit_idx", columns={"time_unit"})})
* @ORM\Entity
*/
class Schedule
{
    /**
    * @var int
    *
    * @ORM\Column(name="id", type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="NONE")
    */
    private $id;

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

    /**
    * @var string
    *
    * @ORM\Column(name="other_data", type="string", length=45)
    */
    private $other_data;

    //Not included since docs state one shouldn't map foreign keys to fields in an entity
    //private $time_unit;

    /**
    * @var \TimeUnit
    *
    * @ORM\ManyToOne(targetEntity="TimeUnit")
    * @ORM\JoinColumns({
    *   @ORM\JoinColumn(name="time_unit", referencedColumnName="unit")
    * })
    */
    private $timeUnit;

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

        return $this;
    }

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

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

        return $this;
    }

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

    /**
    * Set otherData.
    *
    * @param string $otherData
    *
    * @return Schedule
    */
    public function setOtherData($otherData)
    {
        $this->other_data = $otherData;

        return $this;
    }

    /**
    * Get otherData.
    *
    * @return string
    */
    public function getOtherData()
    {
        return $this->other_data;
    }

    /**
    * Set timeUnit.
    *
    * @param TimeUnit $timeUnit (not a string)
    *
    * @return Schedule
    */
    public function setTimeUnit($timeUnit)
    {
        $this->timeUnit = $timeUnit;

        return $this;
    }

    /**
    * Get timeUnit.
    *
    * @return TimeUnit (not a string)
    */
    public function getTimeUnit()
    {
        return $this->timeUnit;
    }

}

time_unit.php

<?php

namespace Michael\App\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
 * TimeUnit
 *
 * @ORM\Table(name="time_unit")
 * @ORM\Entity
 */
class TimeUnit
{
    /**
     * @var string
     *
     * @ORM\Column(name="unit", type="string", length=1)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $unit;

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

    /**
     * @var int
     *
     * @ORM\Column(name="seconds", type="integer")
     */
    private $seconds;


    /**
     * Set unit.
     *
     * @param string $unit
     *
     * @return TimeUnit
     */
    public function setUnit($unit)
    {
        $this->unit = $unit;

        return $this;
    }

    /**
     * Get unit.
     *
     * @return string
     */
    public function getUnit()
    {
        return $this->unit;
    }

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

        return $this;
    }

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

    /**
     * Set seconds.
     *
     * @param int $seconds
     *
     * @return TimeUnit
     */
    public function setSeconds($seconds)
    {
        $this->seconds = $seconds;

        return $this;
    }

    /**
     * Get seconds.
     *
     * @return int
     */
    public function getSeconds()
    {
        return $this->seconds;
    }
}
php orm doctrine-orm datamapper
1个回答
1
投票

EntityManager传递给实体是一种不好的做法,因为Doctrine中的实体被用作数据对象,因此应该包含最少量的逻辑。应将与实体相关的所有应用程序逻辑移动到自定义存储库或分离属于应用程序服务层的类。

在您的情况下,您需要将TimeUnit的实例直接传递给构造函数,而不尝试在实体内构造它或期望通过setter方法设置它。

相反,您需要修改ScheduleService::create()以允许实体创建逻辑可自定义。由于你的ScheduleService基本上实现了Factory method模式,你需要朝着实施Abstract factory模式迈进一步。

抽象工厂基本上依赖于负责构造具体类实例的具体工厂列表,而不是试图在其内部包含所有可能的逻辑。请在下面找到这种模式的实现示例。它可能看起来过于复杂,因为我已经提取了2个接口和抽象类,这个方案可以通过使用2个独立的接口简化,允许抽象和具体工厂共享共同基础,同时保留必要的差异。具体工厂的抽象类用于允许提取基本实体配置逻辑以避免代码重复。

/**
 * Interface for Schedule entity factories
 */
interface AbstractScheduleFactoryInterface
{
    /**
     * Create schedule entity by given params
     *
     * @param array $params
     * @return Schedule
     */
    public function create(array $params = []): Schedule;
}

/**
 * Interface for concrete Schedule entity factories
 */
interface ScheduleFactoryInterface extends AbstractScheduleFactoryInterface
{
    /**
     * Decide if this factory can create schedule entity with given params
     *
     * @param array $params
     * @return bool
     */
    public function canCreate(array $params): bool;
}

/**
 * Implementation of "Abstract Factory" pattern that relies on concrete factories for constructing Schedule entities
 */
class ScheduleFactory implements AbstractScheduleFactoryInterface
{
    /**
     * @var ScheduleFactoryInterface[]
     */
    private $factories;

    /**
     * @param ScheduleFactoryInterface[] $factories
     */
    public function __construct(array $factories)
    {
        $this->factories = $factories;
    }

    /**
     * {@inheritdoc}
     */
    public function create(array $params = []): Schedule
    {
        // Select factory that is able to create Schedule entity by given params
        /** @var ScheduleFactoryInterface $factory */
        $factory = array_reduce($this->factories, function (?ScheduleFactoryInterface $selected, ScheduleFactoryInterface $current) use ($params) {
            if ($selected) {
                return $selected;
            }
            return $current->canCreate($params) ? $current : null;
        });
        if (!$factory) {
            // We have no factory to construct Schedule entity by given params
            throw new \InvalidArgumentException('Unable to construct Schedule entity by given params');
        }
        // Construct entity by using selected concrete factory
        return $factory->create($params);
    }
}

/**
 * Base implementation of concrete Schedule entity factory
 * to allow sharing some common code between factories
 */
abstract class AbstractScheduleFactory implements ScheduleFactoryInterface
{
    /**
     * Basic entity configuration to avoid code duplication in concrete factories
     *
     * @param Schedule $entity
     * @param array $params
     */
    protected function configure(Schedule $entity, array $params = []): void
    {
        // This code is more or less copied from your code snippet
        $entity->setName($params['name'] ?? '');
        $entity->setOtherData($params['other_data'] ?? '');
    }
}

/**
 * Example implementation of Schedule entity factory with Schedules with TimeUnit
 */
class TimeUnitScheduleFactory extends AbstractScheduleFactory
{
    /**
     * @var EntityManager
     */
    private $em;

    /**
     * @param EntityManager $em
     */
    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    /**
     * {@inheritdoc}
     */
    public function canCreate(array $params): bool
    {
        return array_key_exists('time_unit', $params);
    }

    /**
     * Create schedule entity by given params
     *
     * @param array $params
     * @return Schedule
     * @throws \RuntimeException
     */
    public function create(array $params = []): Schedule
    {
        $schedule = new Schedule();
        // Perform basic Schedule configuration using shared base code
        $this->configure($schedule, $params);
        try {
            // Attempt to assign time unit
            $timeUnit = $this->em->find(TimeUnit::class, $params['time_unit']);
            if (!$timeUnit instanceof TimeUnit) {
                // No TimeUnit is available in database - create one
                $timeUnit = new TimeUnit();
                $timeUnit->setUnit($params['time_unit']);
                $this->em->persist($timeUnit);
            }
            $schedule->setTimeUnit($timeUnit);
        } catch (ORMException $e) {
            throw new \RuntimeException('Failed to get TimeUnit entity', 0, $e);
        }

        return $schedule;
    }
}

正如您所看到的 - 此方案允许您为Schedule实体提供任意数量的具体工厂,这些实体需要作为构造函数参数传递给ScheduleFactory。之后,ScheduleFactory::create()可用于创建具有不同构造逻辑的任何类型的Schedule实体。

© www.soinside.com 2019 - 2024. All rights reserved.