Jump to content

Something odd


448191

Recommended Posts

I've noticed some odd behaviour when testing a Domain Object-Data Mapper/Unit of Work setup with respect to destructors...

 

When I manually call commit() to finalize a transaction, everything works as expected. But if I rely on __destruct() of the Data Mapper (and Unit of Work), the destructor throws an exception. This only happens in tests where Domain Objects are loaded from store, and they in their turn have references to the mapper/unit of work, so I'm guessing this has something to do with circular referencing...

 

Some code fragments:

 

Concrete Data Mapper/Unit of Work

<?php
require_once 'Backbone/DataMapper.php';

class Cms_User_DataMapper_Xml extends Backbone_DataMapper {

private static $fileDirty = false;
private static $xml;

public function __construct(){

	if(self::$xml === null){
		self::$xml = new DOMDocument();
		self::$xml->validateOnParse = true;
		self::$xml->registerNodeClass('DOMElement', 'Backbone_Xml_Element_Dom');	
		self::$xml->load(Backbone_Settings::USER_FILE);
	}
}
public function doCreate(Backbone_DomainObject $user){
	if(!$user instanceof Cms_User){
		throw new Backbone_DataMapper_Exception('Object must be of type Cms_User');
	}
	if($this->isStored($user->getId())){
		throw new Backbone_DataMapper_Exception('User id "'.$user->getId().'" already used in store, create impossible.');
	}
	$element = self::$xml->documentElement->appendElement('user');
	$element->setAttribute('id', 'ID'.$user->getId());
	$element->setAttribute('lastlogin', $user->getLastLogin());
	$element->setAttribute('password', $user->getPassword());
	$element->setAttribute('status', $user->getStatus());
	$element->setAttribute('failedloginattempts', $user->getFailedLoginAttempts());
	$this->setFileDirty(true);
}
....
public function setFileDirty($bool){
	if(!is_bool($bool)){
		throw new Backbone_DataMapper_Exception('Boolean dirty flag expected.');
	}
	self::$fileDirty = $bool;
}
public function fileIsDirty(){
	return self::$fileDirty;
}
public function commit(){
	parent::commit();
	if($this->fileIsDirty()){
		$this->save();
		$this->setFileDirty(false);
	}
}
public function save(){
	self::$xml->save(Backbone_Settings::USER_FILE);
}
}
?>

 

Abstract Data Mapper/Unit of Work

<?php
abstract class Backbone_DataMapper {

private static $flags = array('new', 'clean', 'dirty', 'deleted');
private $objects = array();
public $autoCommit = false;

public static function factory($classPrefix, $method, $args = array()){
	$class = $classPrefix.'_DataMapper_'.$method;
	if(!class_exists($class, true)){
		throw new Backbone_DataMapper_Exception('Invalid class name: '.$class);
	}
	$refl = new ReflectionClass($class);
	return $refl->newInstance($args);
}
...
final public function __destruct(){
	if($this->autoCommit){
		$this->commit();
	}
}
/**
 * Write changes to storage
 *
 */
public function commit(){
	foreach($this->objects as $object){
		switch($object->getDataFlag()){
			case 'new';
				$this->create($object);
			break;
			case 'dirty';
				$this->update($object);
			break;
			case 'deleted';
				$this->delete($object);
			break;
			case 'clean';
			break;
			default:
			throw new Backbone_DataMapper_Exception('Invalid flag.');
		}
	}
}
}
?>

 

Unit test (the one that throws an exception)

<?php
    public function testAutoCommit(){
    	include_once 'Cms\User.php';
    	$mapper = Backbone_DataMapper::factory('Cms_User', 'Xml');
    	$mapper->autoCommit = true;
    	$users = $mapper->loadAll();

	foreach($users as $user){
		$this->assertType('Cms_User', $user);
		$user->setLastLogin('100');
	}
	unset($mapper);
	$mapper = Backbone_DataMapper::factory('Cms_User', 'Xml');
	$users = $mapper->loadAll();

	foreach($users as $user){
		$this->assertSame('100', $user->getLastLogin(), 'auto commit failed');
		$user->setLastLogin('200');
	}
	$this->assertTrue($mapper->documentIsValid(), 'document corrupted');
	$this->assertFalse($mapper->fileIsDirty(),'dirty flag should be FALSE');
    }
?>

 

Concrete Domain Object

<?php
require_once 'Backbone\DomainObject.php';

class Cms_User implements Backbone_DomainObject {

private $status = null;
private $id;
private $lastLogin;
private $psw;
private $failedLoginAttempts;
private $mapper;
private $dataFlag;

public function __construct($id, $psw, $lastLogin, $failedLoginAttempts, Backbone_DataMapper $mapper){
	$this->id = $id;
	$this->lastLogin = $lastLogin;
	$this->psw = $psw;
	$this->failedLoginAttempts = $failedLoginAttempts;
	$this->mapper = $mapper;
}
...
public function resetFailedLoginAttempts(){
	$this->failedloginattempts = 0;
	$this->mapper->registerDirty($this);
}
}
?>

Link to comment
Share on other sites

I know that, obviously. The problem is using __destruct() CAUSES an exception. This only occurs when Cms_User and Backbone_DataMapper are EACHOTHERS aggregate objects. Normally an aggregate object is destructed after the containing object, but in this scenario there is not one definite whole, not one aggregate. I can see how the engine may not know which object to destruct first. I would like it to destruct the Data Mapper first, obviously, otherwise there will be no Domain Objects to store.

 

One solution would be to avoid the Domain Object having a stored reference to the mapper, but that would mean having to pass the mapper object whenever it needs to perform an action that potentially marks it dirty. This adds extra responsibility to the client code, something I wish to avoid.

 

Note that I've combined Unit of Work and Data Mapper, in this case it's mostly the Unit of Work (and the business of letting Domain Objects mark itself dirty) that's causing issues.

 

I'd like to at least use the destructor as a saveguard, to ensure that changes are always committed. But I can't even do that, because the destructor is not executed.  :-\

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.