Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 330 lines (290 sloc) 12.9 KB
<?php
/*
* (c) 2011 SimpleThings GmbH
*
* @package SimpleThings\EntityAudit
* @author Benjamin Eberlei <eberlei@simplethings.de>
* @link http://www.simplethings.de
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
namespace SimpleThings\EntityAudit;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Common\Collections\ArrayCollection;
use SimpleThings\EntityAudit\Metadata\MetadataFactory;
class AuditReader
{
private $em;
private $config;
private $metadataFactory;
/**
* @param EntityManager $em
* @param AuditConfiguration $config
* @param MetadataFactory $factory
*/
public function __construct(EntityManager $em, AuditConfiguration $config, MetadataFactory $factory)
{
$this->em = $em;
$this->config = $config;
$this->metadataFactory = $factory;
$this->platform = $this->em->getConnection()->getDatabasePlatform();
}
/**
* Find a class at the specific revision.
*
* This method does not require the revision to be exact but it also searches for an earlier revision
* of this entity and always returns the latest revision below or equal the given revision
*
* @param string $className
* @param mixed $id
* @param int $revision
* @return object
*/
public function find($className, $id, $revision)
{
if (!$this->metadataFactory->isAudited($className)) {
throw AuditException::notAudited($className);
}
$class = $this->em->getClassMetadata($className);
$tableName = $this->config->getTablePrefix() . $class->table['name'] . $this->config->getTableSuffix();
if (!is_array($id)) {
$id = array($class->identifier[0] => $id);
}
$whereSQL = "e." . $this->config->getHistRevisionFieldName() ." <= ?";
foreach ($class->identifier AS $idField) {
if (isset($class->fieldMappings[$idField])) {
$whereSQL .= " AND " . $class->fieldMappings[$idField]['columnName'] . " = ?";
} else if (isset($class->associationMappings[$idField])) {
$whereSQL .= " AND " . $class->associationMappings[$idField]['joinColumns'][0] . " = ?";
}
}
$columnList = "";
foreach ($class->fieldNames AS $field) {
if ($columnList) {
$columnList .= ', ';
}
$columnList .= $class->getQuotedColumnName($field, $this->platform) .' AS ' . $field;
}
foreach ($class->associationMappings AS $assoc) {
if ( ($assoc['type'] & ClassMetadata::TO_ONE) > 0 && $assoc['isOwningSide']) {
foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
if ($columnList) {
$columnList .= ', ';
}
$columnList .= $sourceCol;
}
}
}
$values = array_merge(array($revision), array_values($id));
$query = "SELECT " . $columnList . " FROM " . $tableName . " e WHERE " . $whereSQL . " ORDER BY e." . $this->config->getHistRevisionFieldName() . " DESC";
$revisionData = $this->em->getConnection()->fetchAll($query, $values);
if ($revisionData) {
return $this->createEntity($class->name, $revisionData[0]);
} else {
throw AuditException::noRevisionFound($class->name, $id, $revision);
}
}
/**
* Simplified and stolen code from UnitOfWork::createEntity.
*
* NOTICE: Creates an old version of the entity, HOWEVER related associations are all managed entities!!
*
* @param string $className
* @param array $data
* @return object
*/
private function createEntity($className, array $data)
{
$class = $this->em->getClassMetadata($className);
$entity = $class->newInstance();
foreach ($data as $field => $value) {
if (isset($class->fieldMappings[$field])) {
$type = Type::getType($class->fieldMappings[$field]['type']);
$value = $type->convertToPHPValue($value, $this->platform);
$class->reflFields[$field]->setValue($entity, $value);
}
}
foreach ($class->associationMappings as $field => $assoc) {
// Check if the association is not among the fetch-joined associations already.
if (isset($hints['fetched'][$className][$field])) {
continue;
}
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if ($assoc['isOwningSide']) {
$associatedId = array();
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
if ($joinColumnValue !== null) {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
}
if ( ! $associatedId) {
// Foreign key is NULL
$class->reflFields[$field]->setValue($entity, null);
} else {
$associatedEntity = $this->em->getReference($targetClass->name, $associatedId);
$class->reflFields[$field]->setValue($entity, $associatedEntity);
}
} else {
// Inverse side of x-to-one can never be lazy
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])
->loadOneToOneEntity($assoc, $entity));
}
} else {
// Inject collection
$reflField = $class->reflFields[$field];
$reflField->setValue($entity, new ArrayCollection);
}
}
return $entity;
}
/**
* Return a list of all revisions.
*
* @param int $limit
* @param int $offset
* @return Revision[]
*/
public function findRevisionHistory($limit = 20, $offset = 0)
{
$this->platform = $this->em->getConnection()->getDatabasePlatform();
$query = $this->platform->modifyLimitQuery(
"SELECT * FROM " . $this->config->getRevisionTableName() . " ORDER BY " . $this->config->getRevisionIdFieldName() . " DESC", $limit, $offset
);
$revisionsData = $this->em->getConnection()->fetchAll($query);
$revisions = array();
foreach ($revisionsData AS $row) {
$revisions[] = new Revision(
$row[$this->config->getRevisionIdFieldName()],
\DateTime::createFromFormat($this->platform->getDateTimeFormatString(), $row[$this->config->getRevisionTimestampFieldName()]),
$row[$this->config->getRevisionUsernameFieldName()]
);
}
return $revisions;
}
/**
* Return a list of ChangedEntity instances created at the given revision.
*
* @param int $revision
* @return ChangedEntity[]
*/
public function findEntitesChangedAtRevision($revision)
{
$auditedEntities = $this->metadataFactory->getAllClassNames();
$changedEntities = array();
foreach ($auditedEntities AS $className) {
$class = $this->em->getClassMetadata($className);
$tableName = $this->config->getTablePrefix() . $class->table['name'] . $this->config->getTableSuffix();
$whereSQL = "e." . $this->config->getHistRevisionFieldName() ." = ?";
$columnList = "e." . $this->config->getHistTypeFieldName();
foreach ($class->fieldNames AS $field) {
$columnList .= ', ' . $class->getQuotedColumnName($field, $this->platform) .' AS ' . $field;
}
foreach ($class->associationMappings AS $assoc) {
if ( ($assoc['type'] & ClassMetadata::TO_ONE) > 0 && $assoc['isOwningSide']) {
foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
$columnList .= ', ' . $sourceCol;
}
}
}
$this->platform = $this->em->getConnection()->getDatabasePlatform();
$query = "SELECT " . $columnList . " FROM " . $tableName . " e WHERE " . $whereSQL;
$revisionsData = $this->em->getConnection()->executeQuery($query, array($revision));
foreach ($revisionsData AS $row) {
$id = array();
foreach ($class->identifier AS $idField) {
// TODO: doesnt work with composite foreign keys yet.
$id[$idField] = $row[$idField];
}
$entity = $this->createEntity($className, $row);
$changedEntities[] = new ChangedEntity($className, $id, $row[$this->config->getHistTypeFieldName()], $entity);
}
}
return $changedEntities;
}
/**
* Return the revision object for a particular revision.
*
* @param int $rev
* @return Revision
*/
public function findRevision($rev)
{
$query = "SELECT * FROM " . $this->config->getRevisionTableName() . " r WHERE r." . $this->config->getRevisionIdFieldName() . " = ?";
$revisionsData = $this->em->getConnection()->fetchAll($query, array($rev));
if (count($revisionsData) == 1) {
return new Revision(
$revisionsData[0][$this->config->getRevisionIdFieldName()],
\DateTime::createFromFormat($this->platform->getDateTimeFormatString(), $revisionsData[0][$this->config->getRevisionTimestampFieldName()]),
$revisionsData[0][$this->config->getRevisionUsernameFieldName()]
);
} else {
throw AuditException::invalidRevision($rev);
}
}
/**
* Find all revisions that were made of entity class with given id.
*
* @param string $className
* @param mixed $id
* @return Revision[]
*/
public function findRevisions($className, $id)
{
if (!$this->metadataFactory->isAudited($className)) {
throw AuditException::notAudited($className);
}
$class = $this->em->getClassMetadata($className);
$tableName = $this->config->getTablePrefix() . $class->table['name'] . $this->config->getTableSuffix();
if (!is_array($id)) {
$id = array($class->identifier[0] => $id);
}
$whereSQL = "";
foreach ($class->identifier AS $idField) {
if (isset($class->fieldMappings[$idField])) {
if ($whereSQL) {
$whereSQL .= " AND ";
}
$whereSQL .= "e." . $class->fieldMappings[$idField]['columnName'] . " = ?";
} else if (isset($class->associationMappings[$idField])) {
if ($whereSQL) {
$whereSQL .= " AND ";
}
$whereSQL .= "e." . $class->associationMappings[$idField]['joinColumns'][0] . " = ?";
}
}
$query = "SELECT r.* FROM " . $this->config->getRevisionTableName() . " r " .
"INNER JOIN " . $tableName . " e ON r." . $this->config->getRevisionIdFieldName() ." = e." . $this->config->getHistRevisionFieldName() . " WHERE " . $whereSQL . " ORDER BY r." . $this->config->getRevisionIdFieldName() ." DESC";
$revisionsData = $this->em->getConnection()->fetchAll($query, array_values($id));
$revisions = array();
$this->platform = $this->em->getConnection()->getDatabasePlatform();
foreach ($revisionsData AS $row) {
$revisions[] = new Revision(
$row[$this->config->getRevisionIdFieldName()],
\DateTime::createFromFormat($this->platform->getDateTimeFormatString(), $row[$this->config->getRevisionTimestampFieldName()]),
$row[$this->config->getRevisionUsernameFieldName()]
);
}
return $revisions;
}
protected function getEntityPersister($entity)
{
$uow = $this->em->getUnitOfWork();
return $uow->getEntityPersister($entity);
}
}