Page 11 of 15
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Sun Apr 27, 2008 7:54 am
by matthijs
Aha, so what you're saying is that
Code: Select all
$dataStore = new DataStore(new XmlModel('file.xml'));
$dataStore->set('whatever', 'xyz');
$dataStore->save();
Will save the whatever/xyz back to the file.xml
and
Code: Select all
$dataStore = new DataStore(new XmlModel('file.xml'));
$dataStore->set('whatever', 'xyz');
$dataStore->save(new YamlModel('file.yml'));
will
1) read the file.xml and load it's data in as an array
2) set the whatever to xyz
3a) write the data to a new file file.yml
3b) or write the data to an existing file.yml
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Sun Apr 27, 2008 7:56 am
by arjan.top
I was just writing test for such interface, so I'm for it
Code: Select all
public function testSavingWithModelPassedAsArgument() {
$storageModel = $this->_createStorageModel();
$storageModel->expectOnce('save');
$this->_dataStore->save($storageModel);
}
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Sun Apr 27, 2008 8:58 am
by Chris Corbyn
~matthijs, you're correct, it would:
a) Load data from XmlModel
b) Modify it by setting 'whatever' to 'xyz'
c) Save the new modified data to YamlStore in just the same way it would save to XmlStore if no argument was passed
~arjan.top, that looks like a good first step

Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Sun Apr 27, 2008 9:03 am
by Chris Corbyn
Code to pass your test ~arjan.top:
Code: Select all
public function save(StorageModel $storageModel = null) {
if (!isset($storageModel)) {
$storageModel = $this->_storageModel;
}
return $storageModel->save($this->_store);
}
I'd also like to get some test coverage to ensure that the DataStore doesn't try saving to the StorageModel it was passed in the constructor if an optional one is given. i.e.
Code: Select all
public function testSavingModelPassedAsArgumentDoesntSaveOriginal() {
$this->_storageModel->expectNever('save');
$this->_dataStore->save($this->_createStorageModel());
}
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Sun Apr 27, 2008 9:31 pm
by Chris Corbyn
Does anybody wish to write the test to prove that we're passing a correct array to this optional StorageModel? I think once we're sure about that we'll move onto a new class entirely. We'll limit ourselves to string data types for now for the sake of brevity. We can always expand our options later
Two tests:
1. Ensure a simple 1-dimensional array passed to the optional second StorageModel
2. Ensure a multidimensional array is passed to the optional second StorageModel
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 12:54 am
by matthijs
Maybe for the first?
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->expectOnce('load', array( array('foo' => 'bar') ));
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar', 'zip'=>'button') ));
$dataStore->save($secondStorageModel);
}
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 2:06 am
by matthijs
Ok, that was not right. This one passes;
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->setReturnValue('load', array('foo' => 'bar') );
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar','zip'=>'button' ) ));
$dataStore->save($secondStorageModel);
}
Somehow these arrays keep giving me headaches
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 2:09 am
by Chris Corbyn
Almost right, but your first expectOnce() should actually just be setReturnValue().
Your expectOnce() there is saying that you expect load() to be called with array('foo' => 'bar') as an argument. What you want is that when load() is called, it will return array('foo' => 'bar') as a value
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->setReturnValue('load', array('foo' => 'bar'));
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar', 'zip'=>'button') ));
$dataStore->save($secondStorageModel);
}
We could also write this test without loading any existing data for brevity:
Code: Select all
public function testSetDataAndPassSimpleArrayToSecondModelSavesCorrectData() {
$this->_dataStore->set('foo', 'bar');
$storageModel = $this->_createStorageModel();
$storageModel->expectOnce('save', array( array('foo' => 'bar') ));
$this->_dataStore->save($storageModel);
}
EDIT | Damn, I'm too slow

Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 2:11 am
by matthijs
Yeah I discovered that

([edit]meaning I discovered my error, not that you are slow!)
Setting deeper also works, it seems:
Code: Select all
public function testSetDataAndPassComplexArrayToSecondModelSavesCorrectData(){
$storageModel = $this->_createStorageModel();
$storageModel->setReturnValue('load', array('foo' => 'bar') );
$dataStore = $this->_createDataStore($storageModel);
$dataStore->set('bar/zip', 'button');
$secondStorageModel = $this->_createStorageModel();
$secondStorageModel->expectOnce('save', array( array('foo' => 'bar', 'bar'=>array('zip'=>'button') ) ));
$dataStore->save($secondStorageModel);
}
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 3:25 am
by Chris Corbyn
Brilliant, let's document this class and move onto the first StorageModel.
I've attached the files. Anybody wanna write the Doc comments for the DataStore class?

This is also important; although not really anything to with TDD!
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 3:46 am
by matthijs
Cool!
I'd like to do the doc comments. Is there any official documentation on which style we use?
Edit: the one from phpdoc.org?
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 4:19 am
by matthijs
Something like
Code: Select all
<?php
/**
* DataStore package
*
* @package DataStore
*/
/**
* Class DataStore
*
* @package DataStore
*/
require_once dirname(__FILE__) . '/StorageModel.php';
class DataStore {
/**
* The storage model
* @access private
* @var object
*/
private $_storageModel;
/**
* The private store of data
* @access private
* @var array
*/
private $_store = array();
/**
* Constructor sets up {@link $_storageModel} and {@link $_store}
*/
public function __construct(StorageModel $storageModel) {
$this->_storageModel = $storageModel;
$this->_store = (array) $this->_storageModel->load();
}
/**
* Sets a value based on input $key and $value
* @param $key
* @param $value
*/
public function set($key, $value) {
$container =& $this->_getContainer($key, true);
$container[$this->_getEndKey($key)] = $value;
}
/**
* Gets a value based on input $key
* @param $key
* @return $value
*/
public function get($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
return $container[$this->_getEndKey($key)];
}
}
/**
* Returns true or false if input $key exists
* @param $key
* @return boolean
*/
public function has($key) {
$container =& $this->_getContainer($key);
return (is_array($container)
&& array_key_exists($this->_getEndKey($key), $container));
}
/**
* Removes a $value from the store based on input $key
* @param $key
*/
public function remove($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
unset($container[$this->_getEndKey($key)]);
}
}
/**
* Save the current state of the store
* @param $storageModel
* @return boolean
*/
public function save(StorageModel $storageModel = null) {
if (!isset($storageModel)) {
$storageModel = $this->_storageModel;
}
return $storageModel->save($this->_store);
}
// -- Private methods
/**
* @access private
* @param $key
* @return ?
*/
private function _getEndKey($key) {
$keys = explode('/', $key);
return array_pop($keys);
}
/**
* @access private
* @param $key
* @param $creatIfNotFound
* @return $container
*/
private function &_getContainer($key, $createIfNotFound = false) {
$container =& $this->_store;
foreach (array_slice(explode('/', $key), 0, -1) as $nextKey) {
if (!array_key_exists($nextKey, $container)) {
if (!$createIfNotFound) {
return false;
}
$container[$nextKey] = array();
}
$container =& $container[$nextKey];
}
return $container;
}
}
I quickly put this together, there's probably errors in it. I haven't finished the - may I say not so well documented - documentation on phpdocs yet

Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 4:34 am
by Chris Corbyn
Very good

I'd throw the types in there too:
i.e.
@param string $key
or
@param StorageModel $storageModel
I don't believe {@link} will work for things that are declared private too so I'd probably not mention the private members in the documentation for the constructor

I'm a picky bugger eh? Nah, but if you're reading an API you don't really want to know about private members. I'd change the constructor comment to:
Code: Select all
/**
* Constructor takes a {@link StorageModel} for reading and writing.
* @param StorageModel $storageModel
*/
public function __construct(StorageModel $storageModel) {
$this->_storageModel = $storageModel;
$this->_store = (array) $this->_storageModel->load();
}
_getEndKey() always returns a string (the segment after the last "/").
&_getContainer() always returns an array
get() returns mixed
Okie doke. Where do we want to go from here?
XmlStorageModel, YamlStorageModel or ArrayStorageModel?
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 5:05 am
by matthijs
What would be the easiest step up? ArrayStorageModel? We basically already have that isn't it?
Re: TDD workshop. Anybody welcome; Data storage abstraction.
Posted: Mon Apr 28, 2008 5:09 am
by matthijs
Here's the file after suggested changes
Code: Select all
<?php
/**
* DataStore package
*
* @package DataStore
*/
/**
* Class DataStore
*
* @package DataStore
*/
require_once dirname(__FILE__) . '/StorageModel.php';
class DataStore {
/**
* The storage model
* @access private
* @var object
*/
private $_storageModel;
/**
* The private store of data
* @access private
* @var array
*/
private $_store = array();
/**
* Constructor takes a {@link StorageModel} for reading and writing.
* @param StorageModel $storageModel
*/
public function __construct(StorageModel $storageModel) {
$this->_storageModel = $storageModel;
$this->_store = (array) $this->_storageModel->load();
}
/**
* Sets a value based on input $key and $value
* @param string $key
* @param string $value
*/
public function set($key, $value) {
$container =& $this->_getContainer($key, true);
$container[$this->_getEndKey($key)] = $value;
}
/**
* Gets a value based on input $key
* @param string $key
* @return mixed $value
*/
public function get($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
return $container[$this->_getEndKey($key)];
}
}
/**
* Returns true or false if input $key exists
* @param string $key
* @return boolean
*/
public function has($key) {
$container =& $this->_getContainer($key);
return (is_array($container)
&& array_key_exists($this->_getEndKey($key), $container));
}
/**
* Removes a $value from the store based on input $key
* @param string $key
*/
public function remove($key) {
if ($this->has($key)) {
$container =& $this->_getContainer($key);
unset($container[$this->_getEndKey($key)]);
}
}
/**
* Save the current state of the store
* @param StorageModel $storageModel
* @return boolean
*/
public function save(StorageModel $storageModel = null) {
if (!isset($storageModel)) {
$storageModel = $this->_storageModel;
}
return $storageModel->save($this->_store);
}
// -- Private methods
/**
* @access private
* @param string $key
* @return string
*/
private function _getEndKey($key) {
$keys = explode('/', $key);
return array_pop($keys);
}
/**
* @access private
* @param $key
* @param $creatIfNotFound
* @return array
*/
private function &_getContainer($key, $createIfNotFound = false) {
$container =& $this->_store;
foreach (array_slice(explode('/', $key), 0, -1) as $nextKey) {
if (!array_key_exists($nextKey, $container)) {
if (!$createIfNotFound) {
return false;
}
$container[$nextKey] = array();
}
$container =& $container[$nextKey];
}
return $container;
}
}