ENHANCMEMENT: Added SS_Map to replace SQLMap.

API CHANGE: Deprecate SQLMap.
This commit is contained in:
Sam Minnee 2011-10-29 14:04:17 +13:00
parent 0d0ad0e572
commit a4ee0f4dad
5 changed files with 343 additions and 51 deletions

View File

@ -471,6 +471,28 @@ the described relations).
} }
} }
## Maps
A map is an array where the array indexes contain data as well as the values. You can build a map
from any DataList like this:
:::php
$members = DataList::create('Member')->map('ID', 'FirstName');
This will return a map where the keys are Member IDs, and the values are the corresponding FirstName
values. Like everything else in the ORM, these maps are lazy loaded, so the following code will only
query a single record from the database:
:::php
$members = DataList::create('Member')->map('ID', 'FirstName');
echo $member[5];
This functionality is provided by the `SS_Map` class, which can be used to build a map around any `SS_List`.
:::php
$members = DataList::create('Member');
$map = new SS_Map($members, 'ID', 'FirstName');
## Data Handling ## Data Handling
When saving data through the object model, you don't have to manually escape strings to create SQL-safe commands. When saving data through the object model, you don't have to manually escape strings to create SQL-safe commands.

View File

@ -151,14 +151,8 @@ class DataList extends ViewableData implements SS_List {
return $result; return $result;
} }
public function map($keyfield = 'ID', $titlefield = 'Title') { public function map($keyField = 'ID', $titleField = 'Title') {
$map = array(); return new SS_Map($this, $keyField, $titleField);
foreach ($this as $item) {
$map[$item->$keyfield] = $item->$titlefield;
}
return $map;
} }
/** /**

206
model/Map.php Normal file
View File

@ -0,0 +1,206 @@
<?php
/**
* Creates a map from an SS_List by defining a key column and a value column.
*
* @package sapphire
* @subpackage model
*/
class SS_Map implements ArrayAccess, Countable, IteratorAggregate {
protected $list, $keyField, $valueField;
protected $firstItems = array();
/**
* Construct a new map around an SS_list.
* @param $list The list to build a map from
* @param $keyField The field to use as the key of each map entry
* @param $valueField The field to use as the value of each map entry
*/
function __construct(SS_List $list, $keyField = "ID", $valueField = "Title") {
$this->list = $list;
$this->keyField = $keyField;
$this->valueField = $valueField;
}
/**
* Set the key field for this map
*/
function setKeyField($keyField) {
$this->keyField = $keyField;
}
/**
* Set the value field for this map
*/
function setValueField($valueField) {
$this->valueField = $valueField;
}
/**
* Return an array equivalent to this map
*/
function toArray() {
$array = array();
foreach($this as $k => $v) {
$array[$k] = $v;
}
return $array;
}
/**
* Return all the keys of this map
*/
function keys() {
$array = array();
foreach($this as $k => $v) {
$array[] = $v;
}
return $array;
}
/**
* Return all the values of this map
*/
function values() {
$array = array();
foreach($this as $k => $v) {
$array[] = $v;
}
return $array;
}
/**
* Unshift an item onto the start of the map
*/
function unshift($key, $value) {
$oldItems = $this->firstItems;
$this->firstItems = array($key => $value);
if($oldItems) $this->firstItems = $this->firstItems + $oldItems;
}
// ArrayAccess
function offsetExists($key) {
if(isset($this->firstItems[$key])) return true;
$record = $this->list->find($this->keyField, $key);
return $record != null;
}
function offsetGet($key) {
if(isset($this->firstItems[$key])) return $this->firstItems[$key];
$record = $this->list->find($this->keyField, $key);
if($record) {
$col = $this->valueField;
return $record->$col;
} else {
return null;
}
}
function offsetSet($key, $value) {
if(isset($this->firstItems[$key])) return $this->firstItems[$key] = $value;
user_error("SS_Map is read-only", E_USER_ERROR);
}
function offsetUnset($key) {
if(isset($this->firstItems[$key])) {
unset($this->firstItems[$key]);
return;
}
user_error("SS_Map is read-only", E_USER_ERROR);
}
// IteratorAggreagte
function getIterator() {
return new SS_Map_Iterator($this->list->getIterator(), $this->keyField, $this->valueField, $this->firstItems);
}
// Countable
function count() {
return $this->list->count();
}
}
/**
* Builds a map iterator around an Iterator. Called by SS_Map
* @package sapphire
* @subpackage model
*/
class SS_Map_Iterator implements Iterator {
protected $items;
protected $keyField, $titleField;
protected $firstItemIdx = 0;
protected $firstItems = array();
protected $excludedItems = array();
/**
* @param $items The iterator to build this map from
* @param $keyField The field to use for the keys
* @param $titleField The field to use for the values
* @param $fistItems An optional map of items to show first
*/
function __construct(Iterator $items, $keyField, $titleField, $firstItems = null) {
$this->items = $items;
$this->keyField = $keyField;
$this->titleField = $titleField;
foreach($firstItems as $k => $v) {
$this->firstItems[] = array($k,$v);
$this->excludedItems[] = $k;
}
}
// Iterator functions
public function rewind() {
$this->firstItemIdx = 0;
$rewoundItem = $this->items->rewind();
if(isset($this->firstItems[$this->firstItemIdx])) {
return $this->firstItems[$this->firstItemIdx][1];
} else {
if($rewoundItem) return $rewoundItem->{$this->titleField};
}
}
public function current() {
if(isset($this->firstItems[$this->firstItemIdx])) {
return $this->firstItems[$this->firstItemIdx][1];
} else {
return $this->items->current()->{$this->titleField};
}
}
public function key() {
if(isset($this->firstItems[$this->firstItemIdx])) {
return $this->firstItems[$this->firstItemIdx][0];
} else {
return $this->items->current()->{$this->keyField};
}
}
public function next() {
$this->firstItemIdx++;
if(isset($this->firstItems[$this->firstItemIdx])) {
return $this->firstItems[$this->firstItemIdx][1];
} else {
if(!isset($this->firstItems[$this->firstItemIdx-1])) $this->items->next();
if($this->excludedItems) while(($c = $this->items->current()) && in_array($c->{$this->keyField}, $this->excludedItems, true)) {
$this->items->next();
}
}
}
public function valid() {
return $this->items->valid();
}
}

View File

@ -19,6 +19,8 @@ class SQLMap extends Object implements IteratorAggregate {
* @param SQLQuery $query The query to generate this map. THis isn't executed until it's needed. * @param SQLQuery $query The query to generate this map. THis isn't executed until it's needed.
*/ */
public function __construct(SQLQuery $query, $keyField = "ID", $titleField = "Title") { public function __construct(SQLQuery $query, $keyField = "ID", $titleField = "Title") {
Deprecation::notice('3.0', 'Use SS_Map or DataList::map() instead.');
if(!$query) { if(!$query) {
user_error('SQLMap constructed with null query.', E_USER_ERROR); user_error('SQLMap constructed with null query.', E_USER_ERROR);
} }
@ -52,7 +54,7 @@ class SQLMap extends Object implements IteratorAggregate {
public function getIterator() { public function getIterator() {
$this->genItems(); $this->genItems();
return new SQLMap_Iterator($this->items->getIterator(), $this->keyField, $this->titleField); return new SS_Map_Iterator($this->items->getIterator(), $this->keyField, $this->titleField);
} }
/** /**
@ -85,45 +87,3 @@ class SQLMap extends Object implements IteratorAggregate {
} }
} }
} }
/**
* @package sapphire
* @subpackage model
*/
class SQLMap_Iterator extends Object implements Iterator {
protected $items;
protected $keyField, $titleField;
function __construct(Iterator $items, $keyField, $titleField) {
$this->items = $items;
$this->keyField = $keyField;
$this->titleField = $titleField;
}
/*
* Iterator functions - necessary for foreach to work
*/
public function rewind() {
return $this->items->rewind() ? $this->items->rewind()->{$this->titleField} : null;
}
public function current() {
return $this->items->current()->{$this->titleField};
}
public function key() {
return $this->items->current()->{$this->keyField};
}
public function next() {
$next = $this->items->next();
return isset($next->{$this->titleField}) ? $next->{$this->titleField} : null;
}
public function valid() {
return $this->items->valid();
}
}
?>

110
tests/model/MapTest.php Normal file
View File

@ -0,0 +1,110 @@
<?php
class SS_MapTest extends SapphireTest {
// Borrow the model from DataObjectTest
static $fixture_file = 'DataObjectTest.yml';
protected $extraDataObjects = array(
'DataObjectTest_Team',
'DataObjectTest_Fixture',
'DataObjectTest_SubTeam',
'OtherSubclassWithSameField',
'DataObjectTest_FieldlessTable',
'DataObjectTest_FieldlessSubTable',
'DataObjectTest_ValidatedObject',
'DataObjectTest_Player',
'DataObjectTest_TeamComment'
);
function testArrayAccess() {
$list = DataList::create("DataObjectTest_TeamComment");
$map = new SS_Map($list, 'Name', 'Comment');
$this->assertEquals('This is a team comment by Joe', $map['Joe']);
$this->assertNull($map['DoesntExist']);
}
function testIteration() {
$list = DataList::create("DataObjectTest_TeamComment");
$map = new SS_Map($list, 'Name', 'Comment');
$text = "";
foreach($map as $k => $v) {
$text .= "$k: $v\n";
}
$this->assertEquals("Joe: This is a team comment by Joe\n"
. "Bob: This is a team comment by Bob\n"
. "Phil: Phil is a unique guy, and comments on team2\n", $text);
}
function testDefaultConfigIsIDAndTitle() {
$list = DataList::create("DataObjectTest_Team");
$map = new SS_Map($list);
$this->assertEquals('Team 1', $map[$this->idFromFixture('DataObjectTest_Team', 'team1')]);
}
function testSetKeyFieldAndValueField() {
$list = DataList::create("DataObjectTest_TeamComment");
$map = new SS_Map($list);
$map->setKeyField('Name');
$map->setValueField('Comment');
$this->assertEquals('This is a team comment by Joe', $map['Joe']);
}
function testToArray() {
$list = DataList::create("DataObjectTest_TeamComment");
$map = new SS_Map($list, 'Name', 'Comment');
$this->assertEquals(array("Joe" => "This is a team comment by Joe",
"Bob" => "This is a team comment by Bob",
"Phil" => "Phil is a unique guy, and comments on team2"), $map->toArray());
}
function testUnshift() {
$list = DataList::create("DataObjectTest_TeamComment");
$map = new SS_Map($list, 'Name', 'Comment');
$map->unshift(-1, '(All)');
$this->assertEquals(array(
-1 => "(All)",
"Joe" => "This is a team comment by Joe",
"Bob" => "This is a team comment by Bob",
"Phil" => "Phil is a unique guy, and comments on team2"), $map->toArray());
$map->unshift(0, '(Select)');
$this->assertEquals('(All)', $map[-1]);
$this->assertEquals('(Select)', $map[0]);
$this->assertEquals(array(
0 => "(Select)",
-1 => "(All)",
"Joe" => "This is a team comment by Joe",
"Bob" => "This is a team comment by Bob",
"Phil" => "Phil is a unique guy, and comments on team2"), $map->toArray());
$map->unshift("Bob","Replaced");
$this->assertEquals(array(
"Bob" => "Replaced",
0 => "(Select)",
-1 => "(All)",
"Joe" => "This is a team comment by Joe",
"Phil" => "Phil is a unique guy, and comments on team2"), $map->toArray());
$map->unshift("Phil","Replaced as well");
$this->assertEquals(array(
"Phil" => "Replaced as well",
"Bob" => "Replaced",
0 => "(Select)",
-1 => "(All)",
"Joe" => "This is a team comment by Joe"), $map->toArray());
$map->unshift("Joe","Replaced the last one");
$this->assertEquals(array(
"Joe" => "Replaced the last one",
"Phil" => "Replaced as well",
"Bob" => "Replaced",
0 => "(Select)",
-1 => "(All)"), $map->toArray());
}
}