mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCMEMENT: Added SS_Map to replace SQLMap.
API CHANGE: Deprecate SQLMap.
This commit is contained in:
parent
0d0ad0e572
commit
a4ee0f4dad
@ -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.
|
||||||
|
@ -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
206
model/Map.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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
110
tests/model/MapTest.php
Normal 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());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user