Skip to content

Commit

Permalink
WIP: SyncedEntityRepository to avoid duplicate caching stuff needed i…
Browse files Browse the repository at this point in the history
…n every project
  • Loading branch information
pscheit committed Jan 15, 2014
1 parent f52273b commit 35bb458
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 1 deletion.
103 changes: 103 additions & 0 deletions lib/Webforge/Doctrine/SyncedEntityRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace Webforge\Doctrine;

use stdClass;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\UnitOfWork;

class SyncedEntityRepository {

protected $fqn;

protected $uniqueFields;

protected $entityFactory;

protected $cache = array();

protected $em;

protected $repository;

/**
* @param array $uniqueFields every name of the unique field should point to a scalar value
*/
public function __construct($entityFQN, $uniqueFields, EntityManager $em, EntityFactory $entityFactory) {
$this->fqn = $entityFQN;
$this->em = $em;
$this->repository = $this->em->getRepository($this->fqn);

if (is_string($uniqueFields)) {
$uniqueFields = array($uniqueFields);
}

$this->uniqueFields = $uniqueFields;
$this->entityFactory = $entityFactory;
}

/**
* Inserts / Updates an Entity with the given fields in the repository and db
*
* It uses the uniqueFields from the constructor to find an already persisted entity
* (first level cache is a key-value store in memory)
* previous synced entities will be returned or already persisted entities will be loaded
* the loaded / already synced entities will be updated with the other fields provided
* in any case after syncing with a new $fields object the $fields are applied to the returned entity
*
* @return Object<$entityFQN>
*/
public function sync(stdClass $fields) {
$hash = $this->hashEntityFields($fields);

if (array_key_exists($hash, $this->cache)) {
$entity = $this->cache[$hash];

if ($this->em->getUnitOfWork()->getEntityState($entity) === UnitOfWork::STATE_DETACHED) {
// refresh
$this->cache[$hash] = $entity = $this->loadEntity($fields);
}

$this->updateEntity($entity, $fields);
} else {
if ($entity = $this->loadEntity($fields)) {
$this->updateEntity($entity, $fields);
} else {
$entity = $this->cache[$hash] = $this->insertEntity($fields);
}
}
$this->em->persist($entity);

return $entity;
}

protected function loadEntity(stdClass $fields) {
$criterias = array();
foreach ($this->uniqueFields as $field) {
$criterias[$field] = $fields->$field;
}

return $this->repository->findOneBy($criterias);
}

protected function updateEntity($entity, stdClass $fields) {
foreach($fields as $property => $value) {
if (!in_array($property, $this->uniqueFields)) {
$setter = 'set'.ucfirst($property);
$entity->$setter($value);
}
}
}

protected function insertEntity(stdClass $fields) {
return $this->entityFactory->create($fields);
}

protected function hashEntityFields(stdClass $fields) {
$values = array();
foreach ($this->uniqueFields as $field) {
$values[] = $fields->$field;
}
return implode(':', $values);
}
}
3 changes: 2 additions & 1 deletion lib/Webforge/Doctrine/Test/Entities/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
*
* this entity was compiled from Webforge\Doctrine\Compiler
* @ORM\Entity
* @ORM\Table(name="users")
* @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="user_email_unique", columns={"email"})})
*
*/
class User extends CompiledUser {
}
88 changes: 88 additions & 0 deletions tests/Webforge/Doctrine/SyncedEntityRepositoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Webforge\Doctrine;

class SyncedEntityRepositoryTest extends Test\DatabaseTestCase {

public function setUp() {
$this->chainClass = __NAMESPACE__ . '\\SyncedEntityRepository';
parent::setUp();
$this->userClass = 'Webforge\Doctrine\Test\Entities\User';

$this->entityFactory = new EntityFactory($this->userClass);
$this->users = new SyncedEntityRepository($this->userClass, 'email', $this->em, $this->entityFactory);
//$this->reflector = $this->dcc->getEntityReflector(); // look in assertUserEntity: TODO
}

protected function getFixtures() {
return array(new \Webforge\Doctrine\Fixtures\EmptyFixture());
}

public function testAnEntityGetsInsertedWhenNotAvaibleInDB() {
$this->resetDatabaseOnNextTest();
$user = $this->users->sync($fields = (object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v1'));

$this->assertUserEntity($fields, $user);
}

public function testAnEntityThatsPreviouslySyncedWillGetUpdatedOnSecondCall() {
$this->resetDatabaseOnNextTest();
$this->users->sync((object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v1'));

$user = $this->users->sync($fields = (object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v2'));
$this->assertUserEntity($fields, $user);
}

public function testAnEntityGetsPersistedWhenSynced() {
$this->resetDatabaseOnNextTest();
$user = $this->users->sync((object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v1'));

$this->em->flush();

$this->assertGreaterThan(0, $user->getId(), 'user should have now an generated id, because it was persisted');
}

public function testAnPersistedEntityGetsUpdatedWhenSynced() {
$this->resetDatabaseOnNextTest();
$userpre = $this->users->sync((object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v1'));

$this->em->flush();
$this->em->clear();

$user = $this->users->sync($fields = (object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v2'));

$this->em->flush();
$this->em->clear();

$user = $this->em->find($this->userClass, $userpre->getId());

$this->assertUserEntity($fields, $user);
}

public function testAnPersistedAndThenDeletedEntityGetsUpdatedWhenSynced() {
$this->resetDatabaseOnNextTest();
$userpre = $this->users->sync((object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v1'));

$this->em->remove($userpre);
$this->em->flush();
$this->em->clear();
// it is removed but still in the internal memory cache of $users
$user = $this->users->sync($fields = (object) array('email'=>'p.scheit@ps-webforge.com', 'special'=>'v2'));

$this->em->flush();
$this->em->clear();

$user = $this->em->find($this->userClass, $userpre->getId());

$this->assertUserEntity($fields, $user);
}

protected function assertUserEntity($fields, $user) {
$this->assertInstanceOf($this->userClass, $user);

foreach ($fields as $property => $value) {
$getter = 'get'.ucfirst($property);
$this->assertEquals($value, $user->$getter(), $property.' of entity has not the right value');
}
}
}

0 comments on commit 35bb458

Please sign in to comment.