我正在将一个基于ZendDb
的应用程序迁移到条令2。
问题出在实体AuditLog
上。在迁移之前,它被持久化为ZendDb
(参见代码块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
与ZendDb
的持久性
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 DoctrineORMMapping as ORM;
/**
* AuditLog
*
* @ORMTable(
* name="audit_log",
* indexes={
* @ORMIndex(name="fk_audit_log_user_idx", columns={"user_id"})
* }
* )
* @ORMEntity
* @ORMInheritanceType("SINGLE_TABLE")
* @ORMDiscriminatorColumn(name="resource_type", type="string")
* @ORMDiscriminatorMap({
* "" = "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
*
* @ORMColumn(name="id", type="integer", nullable=false)
* @ORMId
* @ORMGeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @var string
*/
protected $resourceType;
/**
* @var string
*
* @ORMColumn(name="resource_id", type="string", length=50, nullable=true)
*/
protected $resourceId;
/**
* @var string
*
* @ORMColumn(name="action", type="string", nullable=true)
*/
protected $action;
/**
* @var DateTime
*
* @ORMColumn(name="datetime", type="datetime", nullable=false)
*/
protected $datetime;
/**
* @var User
*
* @ORMManyToOne(targetEntity="User")
*/
protected $user;
// access methods ...
}
AuditLogOrder
use DoctrineORMMapping as ORM;
/**
* AuditLogOrder
*
* @ORMTable(name="audit_log")
* @ORMEntity
*/
class AuditLogOrder extends AuditLog
{
/**
* @var Order
*
* @ORMManyToOne(targetEntity="Order")
* @ORMJoinColumn(name="resource_id", referencedColumnName="id")
*/
protected $order;
// access methods ...
}
AuditLogServer
use DoctrineORMMapping as ORM;
/**
* AuditLogServer
*
* @ORMTable(name="audit_log")
* @ORMEntity
*/
class AuditLogServer extends AuditLog
{
// ...
}
AuditLogCluster
use DoctrineORMMapping as ORM;
/**
* AuditLogCluster
*
* @ORMTable(name="audit_log")
* @ORMEntity
*/
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
*
* @ORMManyToOne(targetEntity="Order")
* @ORMJoinColumn(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);
}
}