Skip to content

Commit

Permalink
Moved createEntity into EntityFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
smoench committed Jan 22, 2017
1 parent abdaa7d commit 40930c4
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 259 deletions.
24 changes: 22 additions & 2 deletions UPGRADE.md
Expand Up @@ -3,7 +3,7 @@

## BC BREAK: Current user name resolution

Previously the username that was recorded againsts revisions was resolved by `SimpleThings\EntityAudit\Request\CurrentUserListener` (``simplethings_entityaudit.request.current_user_listener` service).
Previously the username that was recorded against revisions was resolved by `SimpleThings\EntityAudit\Request\CurrentUserListener` (``simplethings_entityaudit.request.current_user_listener` service).

This has been removed and replaced with `SimpleThings\EntityAudit\User\TokenStorageUsernameCallable`.

Expand All @@ -23,4 +23,24 @@ simple_things_entity_audit:
username_callable: simplethings_entityaudit.username_callable.token_storage
```

The above after configuration is the default and does not need setting explicitly.
The above after configuration is the default and does not need setting explicitly.

## BC BREAK:
Following methods has been removed:
```php
AuditReader::setLoadAuditedCollections($loadAuditedCollections)
AuditReader::setLoadAuditedEntities($loadAuditedEntities)
AuditReader::setLoadNativeCollections($loadNativeCollections)
AuditReader::setLoadNativeEntities($loadNativeEntities)
```

And with with $options arguments at AuditReader::__c'tor or AuditReader::find.

```php
$auditReader->find(Foo::class, 1, 1, [
AuditReader::LOAD_AUDITED_COLLECTIONS => false,
AuditReader::LOAD_AUDITED_ENTITIES => false,
AuditReader::LOAD_NATIVE_COLLECTIONS => false,
AuditReader::LOAD_NATIVE_ENTITIES => false,
]);
```
264 changes: 7 additions & 257 deletions src/SimpleThings/EntityAudit/AuditReader.php
Expand Up @@ -23,16 +23,13 @@

namespace SimpleThings\EntityAudit;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use SimpleThings\EntityAudit\Collection\AuditedCollection;
use SimpleThings\EntityAudit\Exception\DeletedException;
use SimpleThings\EntityAudit\Exception\InvalidRevisionException;
use SimpleThings\EntityAudit\Exception\NoRevisionFoundException;
Expand Down Expand Up @@ -87,11 +84,9 @@ class AuditReader
private $quoteStrategy;

/**
* Entity cache to prevent circular references
*
* @var array
* @var EntityFactory
*/
private $entityCache;
private $entityFactory;

/**
* @param EntityManagerInterface $em
Expand All @@ -111,53 +106,9 @@ public function __construct(
$this->platform = $this->em->getConnection()->getDatabasePlatform();
$this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();

$this->options = array_merge([
self::LOAD_AUDITED_COLLECTIONS => true,
self::LOAD_AUDITED_ENTITIES => true,
self::LOAD_NATIVE_COLLECTIONS => true,
self::LOAD_NATIVE_ENTITIES => true,
], $options);
}

/**
* @deprecated use $options arguments
*
* @param boolean $loadAuditedCollections
*/
public function setLoadAuditedCollections($loadAuditedCollections)
{
$this->options[self::LOAD_AUDITED_COLLECTIONS] = $loadAuditedCollections;
}

/**
* @deprecated use $options arguments
*
* @param boolean $loadAuditedEntities
*/
public function setLoadAuditedEntities($loadAuditedEntities)
{
$this->options[self::LOAD_AUDITED_ENTITIES] = $loadAuditedEntities;
}

/**
* @deprecated use $options arguments
*
* @param boolean $loadNativeCollections
*/
public function setLoadNativeCollections($loadNativeCollections)
{
$this->options[self::LOAD_NATIVE_COLLECTIONS] = $loadNativeCollections;
$this->entityFactory = new EntityFactory($this, $em, $factory, $options);
}

/**
* @deprecated use $options arguments
*
* @param boolean $loadNativeEntities
*/
public function setLoadNativeEntities($loadNativeEntities)
{
$this->options[self::LOAD_NATIVE_ENTITIES] = $loadNativeEntities;
}

/**
* @return \Doctrine\DBAL\Connection
Expand All @@ -180,7 +131,7 @@ public function getConfiguration()
*/
public function clearEntityCache()
{
$this->entityCache = [];
$this->entityFactory->clearEntityCache();
}

/**
Expand Down Expand Up @@ -319,170 +270,7 @@ public function find($className, $id, $revision, array $options = array())

unset($row[$this->config->getRevisionTypeFieldName()]);

return $this->createEntity($class->name, $columnMap, $row, $revision, $options);
}

/**
* Simplified and stolen code from UnitOfWork::createEntity.
*
* @param string $className
* @param array $columnMap
* @param array $data
* @param int $revision
* @param array $options
*
* @return object
*
* @throws DeletedException
* @throws NoRevisionFoundException
* @throws NotAuditedException
* @throws \Doctrine\DBAL\DBALException
* @throws \Doctrine\ORM\Mapping\MappingException
* @throws \Doctrine\ORM\ORMException
* @throws \Exception
*/
private function createEntity($className, array $columnMap, array $data, $revision, array $options = [])
{
$options = array_merge($this->options, $options);

/** @var ClassMetadataInfo|ClassMetadata $classMetadata */
$classMetadata = $this->em->getClassMetadata($className);
$cacheKey = $this->createEntityCacheKey($classMetadata, $data, $revision);

if (isset($this->entityCache[$cacheKey])) {
return $this->entityCache[$cacheKey];
}

if (! $classMetadata->isInheritanceTypeNone()) {
if (! isset($data[$classMetadata->discriminatorColumn['name']])) {
throw new \RuntimeException('Expecting discriminator value in data set.');
}
$discriminator = $data[$classMetadata->discriminatorColumn['name']];
if (! isset($classMetadata->discriminatorMap[$discriminator])) {
throw new \RuntimeException("No mapping found for [{$discriminator}].");
}

if ($classMetadata->discriminatorValue) {
$entity = $this->em->getClassMetadata($classMetadata->discriminatorMap[$discriminator])->newInstance();
} else {
//a complex case when ToOne binding is against AbstractEntity having no discriminator
$pk = array();

foreach ($classMetadata->identifier as $field) {
$pk[$classMetadata->getColumnName($field)] = $data[$field];
}

return $this->find($classMetadata->discriminatorMap[$discriminator], $pk, $revision);
}
} else {
$entity = $classMetadata->newInstance();
}

//cache the entity to prevent circular references
$this->entityCache[$cacheKey] = $entity;

$connection = $this->getConnection();
foreach ($data as $field => $value) {
if (isset($classMetadata->fieldMappings[$field])) {
$value = $connection->convertToPHPValue($value, $classMetadata->fieldMappings[$field]['type']);
$classMetadata->reflFields[$field]->setValue($entity, $value);
}
}

foreach ($classMetadata->associationMappings as $field => $assoc) {
// Check if the association is not among the fetch-joined associations already.
if (isset($hints['fetched'][$className][$field])) {
continue;
}

/** @var ClassMetadataInfo|ClassMetadata $targetClass */
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
$isAudited = $this->metadataFactory->isAudited($assoc['targetEntity']);

if ($assoc['type'] & ClassMetadata::TO_ONE) {
$value = null;

if ($isAudited && $options[self::LOAD_AUDITED_ENTITIES]) {
// Primary Key. Used for audit tables queries.
$pk = array();
// Primary Field. Used when fallback to Doctrine finder.
$pf = array();

if ($assoc['isOwningSide']) {
foreach ($assoc['targetToSourceKeyColumns'] as $foreign => $local) {
$pk[$foreign] = $pf[$foreign] = $data[$columnMap[$local]];
}
} else {
/** @var ClassMetadataInfo|ClassMetadata $otherEntityMeta */
$otherEntityAssoc = $this->em->getClassMetadata($assoc['targetEntity'])->associationMappings[$assoc['mappedBy']];

foreach ($otherEntityAssoc['targetToSourceKeyColumns'] as $local => $foreign) {
$pk[$foreign] = $pf[$otherEntityAssoc['fieldName']] = $data[$classMetadata->getFieldName($local)];
}
}

$pk = array_filter($pk);

if (! empty($pk)) {
try {
$value = $this->find($targetClass->name, $pk, $revision, array_merge(
$options,
['threatDeletionsAsExceptions' => true]
));
} catch (DeletedException $e) {
$value = null;
} catch (NoRevisionFoundException $e) {
// The entity does not have any revision yet. So let's get the actual state of it.
$value = $this->em->getRepository($targetClass->name)->findOneBy($pf);
}
}
} elseif (! $isAudited && $options[self::LOAD_NATIVE_ENTITIES]) {
if ($assoc['isOwningSide']) {
$associatedId = array();
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = isset($data[$columnMap[$srcColumn]]) ? $data[$columnMap[$srcColumn]] : null;
if ($joinColumnValue !== null) {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
}

if (! empty($associatedId)) {
$value = $this->em->getReference($targetClass->name, $associatedId);
}
} else {
// Inverse side of x-to-one can never be lazy
$value = $this->getEntityPersister($assoc['targetEntity'])
->loadOneToOneEntity($assoc, $entity);
}
}

$classMetadata->reflFields[$field]->setValue($entity, $value);
} elseif ($assoc['type'] & ClassMetadata::ONE_TO_MANY) {
$collection = new ArrayCollection();

if ($isAudited && $options[self::LOAD_AUDITED_COLLECTIONS]) {
$foreignKeys = array();
foreach ($targetClass->associationMappings[$assoc['mappedBy']]['sourceToTargetKeyColumns'] as $local => $foreign) {
$field = $classMetadata->getFieldForColumn($foreign);
$foreignKeys[$local] = $classMetadata->reflFields[$field]->getValue($entity);
}

$collection = new AuditedCollection($this, $targetClass, $assoc, $foreignKeys, $revision);
} elseif (! $isAudited && $options[self::LOAD_NATIVE_COLLECTIONS]) {
$collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection());

$this->getEntityPersister($assoc['targetEntity'])
->loadOneToManyCollection($assoc, $entity, $collection);
}

$classMetadata->reflFields[$assoc['fieldName']]->setValue($entity, $collection);
} else {
// Inject collection
$classMetadata->reflFields[$field]->setValue($entity, new ArrayCollection());
}
}

return $entity;
return $this->entityFactory->createEntity($class->name, $columnMap, $row, $revision, $options);
}

/**
Expand Down Expand Up @@ -597,7 +385,7 @@ public function findEntitiesChangedAtRevision($revision)
$id[$idField] = $row[$idField];
}

$entity = $this->createEntity($className, $columnMap, $row, $revision);
$entity = $this->entityFactory->createEntity($className, $columnMap, $row, $revision);
$changedEntities[] = new ChangedEntity(
$className,
$id,
Expand Down Expand Up @@ -737,16 +525,6 @@ public function getCurrentRevision($className, $id)
return $queryBuilder->execute()->fetchColumn();
}

/**
* @param object $entity
*
* @return \Doctrine\ORM\Persisters\Entity\EntityPersister
*/
protected function getEntityPersister($entity)
{
return $this->em->getUnitOfWork()->getEntityPersister($entity);
}

/**
* Get an array with the differences of between two specific revisions of
* an object with a given id.
Expand Down Expand Up @@ -898,7 +676,7 @@ public function getEntityHistory($className, $id)
$rev = $row[$revisionFieldName];
unset($row[$revisionFieldName]);

$result[] = $this->createEntity($class->name, $columnMap, $row, $rev);
$result[] = $this->entityFactory->createEntity($class->name, $columnMap, $row, $rev);
}

return $result;
Expand All @@ -917,32 +695,4 @@ private function createRevision(array $row)
$row['username']
);
}

/**
* @param ClassMetadata $classMetadata
* @param array $data
* @param int $revision
*
* @return string
*/
private function createEntityCacheKey(ClassMetadata $classMetadata, array $data, $revision)
{
//lookup revisioned entity cache
$keyParts = array();

foreach ($classMetadata->getIdentifierFieldNames() as $name) {
if ($classMetadata->hasAssociation($name)) {
if ($classMetadata->isSingleValuedAssociation($name)) {
$name = $classMetadata->getSingleAssociationJoinColumnName($name);
} else {
// Doctrine should throw a mapping exception if an identifier
// that is an association is not single valued, but just in case.
throw new \RuntimeException('Multiple valued association identifiers not supported');
}
}
$keyParts[] = $data[$name];
}

return $classMetadata->name . '_' . implode('_', $keyParts) . '_' . $revision;
}
}

0 comments on commit 40930c4

Please sign in to comment.