Doctrine 忽略 property/column

Doctrine ignores a property/column

我正在将基于 Zend\Db 的应用程序迁移到 Doctrine 2。

问题出在实体 AuditLog 上。在迁移之前,它一直存在 Zend\Db(请参阅代码块 1)。

现在实体 AuditLog 有多个子 类,它们一起实现了 Single Table Inheritance 模式(参见代码块 2)。

自从我切换到 Doctrine 后,我应该只需要创建一个适当的 AuditLog 对象并简单地 persist(...) 它。事实上,这是我正在尝试的(参见代码块 3)。但是现在我遇到了 AuditLog 的 属性 $resourceId 的问题:Doctrine 似乎忽略了它。 属性 包含正确的值(我在调试器中看到它),但 INSERT 语句没有得到它,看起来像:

INSERT INTO audit_log
    (resource_id, action, datetime, user_id, resource_type)
VALUES
    (NULL, 'order.created', NULL, 1, 'order');

结果是:实体被持久化,但是 resource_id 是空的。

为什么 Doctrine 忽略这个 属性 以及如何保存它?


代码

1 坚持Zend\Db

AuditLogger

class AuditLogger extends AbstractPlugin
{
    ...
    public function log($resourceType = null, $resourceId = null, $action = null, $userId = null)
    {
        $auditLog = new AuditLog();
        $auditLog->setResourceType($resourceType);
        $auditLog->setResourceId($resourceId);
        $auditLog->setAction($action);
        ...
        $auditLog->setUser($this->user);
        $this->auditLogService->create($auditLog);
        // The AuditLogService calls then the AuditLogMapper#create(...).
    }
}

AuditLogMapper

class AuditLogMapper extends AbstractMapper implements AuditLogMapperInterface
{
    ...
    public function create(AuditLog $dataObject)
    {
        $data = [];
        // data retrieved directly from the input
        $data['resource_type'] = $dataObject->getResourceType();
        $data['resource_id'] = $dataObject->getResourceId();
        $data['action'] = $dataObject->getAction();
        $data['datetime'] = $dataObject->getDatetime();
        $data['user_id'] = $dataObject->getUser()->getId();

        $action = new Insert('audit_log');
        unset($data['id']);
        $action->values($data);

        $sql = new Sql($this->dbAdapter);
        $statement = $sql->prepareStatementForSqlObject($action);
        $result = $statement->execute();

        if ($result instanceof ResultInterface) {
            $newId = $result->getGeneratedValue() ?: $dataObject->getId();
            if ($newId) {
                $dataObject->setId($newId);
            }
            return $dataObject;
        }
        throw new \Exception('Database error in ' . __METHOD__);
    }
}

2 实体

AuditLog

use Doctrine\ORM\Mapping as ORM;

/**
 * AuditLog
 *
 * @ORM\Table(
 *     name="audit_log",
 *     indexes={
 *         @ORM\Index(name="fk_audit_log_user_idx", columns={"user_id"})
 *     }
 * )
 * @ORM\Entity
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="resource_type", type="string")
 * @ORM\DiscriminatorMap({
 *     "" = "AuditLog",
 *     "order" = "AuditLogOrder",
 *     "server" = "AuditLogServer",
 *     "cluster" = "AuditLogCluster"
 * })
 */
class AuditLog extends AbstractDataObject
{
    /** @var string */
    const RESSOURCE_TYPE_ORDER = 'order';
    /** @var string */
    const RESSOURCE_TYPE_SERVER = 'server';
    /** @var string */
    const RESSOURCE_TYPE_CLUSTER = 'cluster';
    /** @var string */
    const ACTION_ORDER_CREATED = 'order.created';
    /** @var string */
    const ACTION_ORDER_SUBMITTED = 'order.submitted';
    /** @var string */
    const ACTION_ORDER_EDITING_STARTED = 'order.editing_started';
    /** @var string */
    const ACTION_ORDER_UPDATED = 'order.updated';
    /** @var string */
    const ACTION_ORDER_CANCELED = 'order.canceled';
    /** @var string */
    const ACTION_ORDER_CHECKING_STARTED = 'order.checking_started';
    /** @var string */
    const ACTION_ORDER_ACCEPTED = 'order.accepted';
    /** @var string */
    const ACTION_ORDER_DECLINED = 'order.declined';
    /** @var string */
    const ACTION_ORDER_COMPLETED = 'order.completed';
    /** @var string */
    const ACTION_ORDER_EXPORTED = 'order.exported';
    /** @var string */
    const ACTION_SERVER_VIRTUAL_NODE_NAME_ADDED = 'server.virtual_node_name_added';
    /** @var string */
    const ACTION_CLUSTER_CREATED = 'cluster.created';
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;
    /**
     * @var string
     */
    protected $resourceType;
    /**
     * @var string
     *
     * @ORM\Column(name="resource_id", type="string", length=50, nullable=true)
     */
    protected $resourceId;
    /**
     * @var string
     *
     * @ORM\Column(name="action", type="string", nullable=true)
     */
    protected $action;
    /**
     * @var \DateTime
     *
     * @ORM\Column(name="datetime", type="datetime", nullable=false)
     */
    protected $datetime;
    /**
     * @var User
     *
     * @ORM\ManyToOne(targetEntity="User")
     */
    protected $user;
    // access methods ...
}

AuditLogOrder

use Doctrine\ORM\Mapping as ORM;
/**
 * AuditLogOrder
 *
 * @ORM\Table(name="audit_log")
 * @ORM\Entity
 */
class AuditLogOrder extends AuditLog
{
    /**
     * @var Order
     *
     * @ORM\ManyToOne(targetEntity="Order")
     * @ORM\JoinColumn(name="resource_id", referencedColumnName="id")
     */
    protected $order;
    // access methods ...
}

AuditLogServer

use Doctrine\ORM\Mapping as ORM;
/**
 * AuditLogServer
 *
 * @ORM\Table(name="audit_log")
 * @ORM\Entity
 */
class AuditLogServer extends AuditLog
{
    // ...
}

AuditLogCluster

use Doctrine\ORM\Mapping as ORM;
/**
 * AuditLogCluster
 *
 * @ORM\Table(name="audit_log")
 * @ORM\Entity
 */
class AuditLogCluster extends AuditLog
{
    // ...
}

3坚持教义

AuditLogger

class AuditLogger extends AbstractPlugin
{
    ...
    public function log($resourceType = null, $resourceId = null, $action = null, $userId = null)
    {
        switch ($resourceType) {
            case AuditLog::RESSOURCE_TYPE_ORDER:
                $auditLog = new AuditLogFileTransferRequest();
                break;
            case AuditLog::RESSOURCE_TYPE_SERVER:
                $auditLog = new AuditLogServer();
                break;
            case AuditLog::RESSOURCE_TYPE_CLUSTER:
                $auditLog = new AuditLogCluster();
                break;
            default:
                $auditLog = new AuditLog();
        }
        $auditLog->setResourceType($resourceType);
        $auditLog->setResourceId($resourceId);
        $auditLog->setAction($action);
        ...
        $auditLog->setUser($this->user);
        $this->auditLogService->create($auditLog);
        // The AuditLogService calls then the AuditLogMapper#create(...).
    }
}

AuditLogMapper

class AuditLogMapper extends AbstractMapper implements AuditLogMapperInterface
{
    ...
    public function create(AuditLog $dataObject)
    {
        $currentUser = $this->entityManager->getRepository(User::class)->find(
            $dataObject->getUser()->getId()
        );
        $dataObject->setUser($currentUser);
        $this->entityManager->persist($dataObject);
        $this->entityManager->flush();
        return $dataObject;
    }
}

由于 resource_id 用作 JoinColumn

/**
 * @var Order
 *
 * @ORM\ManyToOne(targetEntity="Order")
 * @ORM\JoinColumn(name="resource_id", referencedColumnName="id")
 */
protected $order;

不是普通的属性了,可以直接设置。需要改为设置相关实体​​:

...

class AuditLogger extends AbstractPlugin
{

    ...

    /**
     * @var User
     */
    protected $user;

    public function __construct(
        AuditLogServiceInterface $auditLogService,
        OrderInterface $order,
        ServerServiceInterface $serverService,
        ClusterServiceInterface $clusterService,
        User $user = null
    ) {
        $this->auditLogService = $auditLogService;
        $this->order = $order;
        $this->serverService = $serverService;
        $this->clusterService = $clusterService;
        $this->user = $user;
    }

    public function log($resourceType = null, $resourceId = null, $action = null, $userId = null)
    {
        switch ($resourceType) {
            case AuditLog::RESSOURCE_TYPE_ORDER:
                $auditLog = new AuditLogFileTransferRequest();
                $resource = $this->order->findOne($resourceId);
                $auditLog->setFileTransferRequest($resource);
                break;
            case AuditLog::RESSOURCE_TYPE_SERVER:
                $auditLog = new AuditLogServer();
                $resource = $this->serverService->findOne($resourceId);
                $auditLog->setServer($resource);
                break;
            case AuditLog::RESSOURCE_TYPE_CLUSTER:
                $auditLog = new AuditLogCluster();
                $resource = $this->clusterService->findOne($resourceId);
                $auditLog->setCluster($resource);
                break;
            default:
                $auditLog = new AuditLog();
        }
        $auditLog->setAction($action);
        if ($userId) {
            $user = new User();
            $user->setId($userId);
            $auditLog->setUser($user);
        } elseif ($this->user) {
            $auditLog->setUser($this->user);
        }
        $this->auditLogService->create($auditLog);
    }

}