(merged from branches/roa. use "svn log -c <changeset> -g <module-svn-path>" for detailed commit message)

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60207 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-08-09 03:54:55 +00:00
parent 6bd5da7e6e
commit b5776e0438
18 changed files with 384 additions and 176 deletions

View File

@ -54,20 +54,14 @@ class RestfulServer extends Controller {
*/
function index() {
ContentNegotiator::disable();
$requestMethod = $_SERVER['REQUEST_METHOD'];
if(!isset($this->urlParams['ClassName'])) return $this->notFound();
$className = $this->urlParams['ClassName'];
$id = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : null;
$relation = (isset($this->urlParams['Relation'])) ? $this->urlParams['Relation'] : null;
// This is a little clumsy and should be improved with the new TokenisedURL that's coming
if(strpos($relation,'.') !== false) list($relation, $extension) = explode('.', $relation, 2);
else if(strpos($id,'.') !== false) list($id, $extension) = explode('.', $id, 2);
else if(strpos($className,'.') !== false) list($className, $extension) = explode('.', $className, 2);
else $extension = null;
$extension = $this->request->getExtension();
// Determine mime-type from extension
$contentMap = array(
'xml' => 'text/xml',
@ -77,7 +71,7 @@ class RestfulServer extends Controller {
'html' => 'text/html',
);
$contentType = isset($contentMap[$extension]) ? $contentMap[$extension] : 'text/xml';
switch($requestMethod) {
case 'GET':
return $this->getHandler($className, $id, $relation, $contentType);
@ -142,6 +136,8 @@ class RestfulServer extends Controller {
} else {
$obj = DataObject::get($className, "");
// show empty serialized result when no records are present
if(!$obj) $obj = new DataObjectSet();
if(!singleton($className)->stat('api_access')) {
return $this->permissionFailure();
}

View File

@ -442,6 +442,21 @@ class Controller extends RequestHandlingData {
);
}
/**
* Joins two link segments together, putting a slash between them if necessary.
* Use this for building the results of Link() methods.
*/
static function join_links() {
$args = func_get_args();
$result = array_shift($args);
foreach($args as $arg) {
if(substr($result,-1) != '/' && $arg[0] != '/') $result .= "/$arg";
else $result .= $arg;
}
return $result;
}
}
?>

View File

@ -32,6 +32,22 @@ class HTTPRequest extends Object implements ArrayAccess {
protected $unshiftedButParsedParts = 0;
function isGET() {
return $this->httpMethod == 'GET';
}
function isPOST() {
return $this->httpMethod == 'POST';
}
function isPUT() {
return $this->httpMethod == 'PUT';
}
function isDELETE() {
return $this->httpMethod == 'DELETE';
}
function getVars() {
return $this->getVars;
}
@ -53,6 +69,10 @@ class HTTPRequest extends Object implements ArrayAccess {
if(isset($this->getVars[$name])) return $this->getVars[$name];
}
function getExtension() {
return $this->extension;
}
/**
* Enables the existence of a key-value pair in the request to be checked using
* array syntax, so isset($request['title']) will check for $_POST['title'] and $_GET['title]
@ -195,6 +215,7 @@ class HTTPRequest extends Object implements ArrayAccess {
if($v || !isset($this->allParams[$k])) $this->allParams[$k] = $v;
}
if($arguments === array()) $arguments['_matched'] = true;
return $arguments;
}
@ -222,9 +243,8 @@ class HTTPRequest extends Object implements ArrayAccess {
}
/**
* Returns true if the give pattern is an empty pattern - that is, one that only matches completely parsed
* URLs. It will also return true if this is a completely parsed URL and the pattern contains only variable
* references.
* Returns true if this is a URL that will match without shifting off any of the URL.
* This is used by the request handler to prevent infinite parsing loops.
*/
function isEmptyPattern($pattern) {
if(preg_match('/^([A-Za-z]+) +(.*)$/', $pattern, $matches)) {
@ -232,10 +252,6 @@ class HTTPRequest extends Object implements ArrayAccess {
}
if(trim($pattern) == "") return true;
if(!$this->dirParts) {
return preg_replace('/\$[A-Za-z][A-Za-z0-9]*(\/|$)/','',$pattern) == "";
}
}
/**
@ -253,6 +269,5 @@ class HTTPRequest extends Object implements ArrayAccess {
*/
function allParsed() {
return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
}
}

View File

@ -23,6 +23,8 @@
* {@link RequestHandlingData::handleRequest()} is where this behaviour is implemented.
*/
class RequestHandlingData extends ViewableData {
protected $request = null;
/**
* The default URL handling rules. This specifies that the next component of the URL corresponds to a method to
* be called on this RequestHandlingData object.
@ -68,10 +70,12 @@ class RequestHandlingData extends ViewableData {
* @uses HTTPRequest
*/
function handleRequest($request) {
$this->request = $request;
foreach($this->stat('url_handlers') as $rule => $action) {
if(isset($_GET['debug_request'])) Debug::message("Testing '$rule' with '" . $request->remaining() . "' on $this->class");
if(isset($_REQUEST['debug_request'])) Debug::message("Testing '$rule' with '" . $request->remaining() . "' on $this->class");
if($params = $request->match($rule, true)) {
if(isset($_GET['debug_request'])) Debug::message("Rule '$rule' matched on $this->class");
if(isset($_REQUEST['debug_request'])) Debug::message("Rule '$rule' matched on $this->class");
// Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action',
if($action[0] == '$') $action = $params[substr($action,1)];

View File

@ -616,9 +616,14 @@ class DataObject extends ViewableData implements DataObjectInterface {
foreach($this->record as $fieldName => $fieldValue) {
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->fieldExists($fieldName)) {
$fieldObj = $this->obj($fieldName);
$fieldObj->setValue($this->record[$fieldName], $this->record);
if(!isset($manipulation[$class])) $manipulation[$class] = array();
if($fieldObj) $fieldObj->writeToManipulation($manipulation[$class]);
// if database column doesn't correlate to a DBField instance, set up a default Varchar DBField
// (used mainly for has_one/has_many)
if(!$fieldObj) $fieldObj = DBField::create('Varchar', $this->record[$fieldName], $fieldName);
$fieldObj->setValue($this->record[$fieldName], $this->record);
$fieldObj->writeToManipulation($manipulation[$class]);
}
}
@ -1051,7 +1056,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
return $candidate;
}
} else {
eval("\$items = array_merge((array){$class}::\$db, (array)\$items);");
eval("\$items = array_merge((array)\$items, (array){$class}::\$db);");
}
}
@ -1192,18 +1197,28 @@ class DataObject extends ViewableData implements DataObjectInterface {
*/
public function scaffoldFormFields() {
$fields = new FieldSet();
$fields->push(new HeaderField($this->singular_name()));
foreach($this->db() as $fieldName => $fieldType) {
// @todo Pass localized title
// commented out, to be less of a pain in the ass
//$fields->addFieldToTab('Root.Main', $this->dbObject($fieldName)->scaffoldFormField());
$fields->push($this->dbObject($fieldName)->scaffoldFormField());
}
// @todo Add relation tabs
}
return $fields;
}
/**
* Add the scaffold-generated relation fields to the given field set
*/
protected function addScaffoldRelationFields($fieldSet) {
foreach($this->has_many() as $relationship => $component) {
$relationshipFields = array_keys($this->searchableFields());
$fieldSet->push(new ComplexTableField($this, $relationship, $component, $relationshipFields));
}
return $fieldSet;
}
/**
* Centerpiece of every data administration interface in Silverstripe,
@ -1228,7 +1243,12 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return FieldSet
*/
public function getCMSFields() {
return $this->scaffoldFormFields();
$fields = $this->scaffoldFormFields();
// If we don't have an ID, then relation fields don't work
if($this->ID) {
$this->addScaffoldRelationFields($fields);
}
return $fields;
}
/**

View File

@ -53,9 +53,6 @@ class ComplexTableField extends TableListField {
*/
protected $permissions = array(
"add",
"edit",
"show",
"delete",
//"export",
);
@ -114,6 +111,7 @@ class ComplexTableField extends TableListField {
static $url_handlers = array(
'item/$ID' => 'handleItem',
'$Action!' => '$Action',
);
function handleItem($request) {
@ -387,6 +385,124 @@ JS;
function setTemplatePopup($template) {
$this->templatePopup = $template;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
function getFieldsFor($childData) {
// Add the relation value to related records
if(!$childData->ID && $this->getParentClass()) {
// make sure the relation-link is existing, even if we just add the sourceClass and didn't save it
$parentIDName = $this->getParentIdName( $this->getParentClass(), $this->sourceClass() );
$childData->$parentIDName = $childData->ID;
}
// If the fieldset is passed, use it
if(is_a($this->detailFormFields,"Fieldset")) {
$detailFields = $this->detailFormFields;
// Else use the formfields returned from the object via a string method call.
} else {
if(!is_string($this->detailFormFields)) $this->detailFormFields = "getCMSFields";
$functioncall = $this->detailFormFields;
if(!$childData->hasMethod($functioncall)) $functioncall = "getCMSFields";
$detailFields = $childData->$functioncall();
}
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
$detailFields->removeByName('ID');
// only add childID if we're not adding a record
if($childData->ID) {
$detailFields->push(new HiddenField("ctf[childID]","",$childData->ID));
}
// add a namespaced ID instead thats "converted" by saveComplexTableField()
$detailFields->push(new HiddenField("ctf[ClassName]","",$this->sourceClass()));
if($this->getParentClass()) {
$parentIdName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
/*
if(!$parentIdName) {
user_error("ComplexTableField::DetailForm() Cannot automatically
determine 'has-one'-relationship to parent class " . $this->ctf->getParentClass() . ",
please use setParentClass() to set it manually",
E_USER_WARNING);
return;
}
*/
if($parentIdName) {
// add relational fields
$detailFields->push(new HiddenField("ctf[parentClass]"," ",$this->getParentClass()));
if( $this->relationAutoSetting )
$detailFields->push(new HiddenField("$parentIdName"," ",$childData->ID));
}
}
return $detailFields;
}
function getValidatorFor($childData) {
// if no custom validator is set, and there's on present on the object (e.g. Member), use it
if(!isset($this->detailFormValidator) && $childData->hasMethod('getValidator')) {
$this->detailFormValidator = $childData->getValidator();
}
return $this->detailFormValidator;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
function add() {
if(!$this->can('add')) return;
return $this->customise(array(
'DetailForm' => $this->AddForm(),
))->renderWith($this->templatePopup);
}
function AddForm($childID = null) {
$className = $this->sourceClass();
$childData = new $className();
$fields = $this->getFieldsFor($childData);
$validator = $this->getValidatorFor($childData);
$form = Object::create(
$this->popupClass,
$this, "AddForm",
$fields, $validator, false, $childData);
return $form;
}
/**
* Use the URL-Parameter "action_saveComplexTableField"
* to provide a clue to the main controller if the main form has to be rendered,
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
* which in turn saves the record.
*
* @see {Form::ReferencedField}).
*/
function saveComplexTableField($params) {
$className = $this->sourceClass();
$childData = new $className();
$this->saveInto($childData);
$childData->write();
// if ajax-call in an iframe, update window
if(Director::is_ajax()) {
// Newly saved objects need their ID reflected in the reloaded form to avoid double saving
$form = $this->controller->DetailForm();
//$form->loadDataFrom($this->dataObject);
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
return FormResponse::respond();
} else {
Director::redirectBack();
}
}
}
/**
@ -463,21 +579,6 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
echo $this->renderWith($this->ctf->templatePopup);
}
/**
* Just a hook, processed in {DetailForm()}
*
* @return String
*/
function add() {
if($this->Can('add') !== true) {
return false;
}
$this->methodName = "add";
echo $this->renderWith($this->templatePopup);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/**
@ -505,89 +606,46 @@ class ComplexTableField_ItemRequest extends RequestHandlingData {
*/
function DetailForm($childID = null) {
$childData = $this->obj();
// If the fieldset is passed, use it, else use the formfields returned
// from the object via a string method call.
if(is_a($this->ctf->detailFormFields,"Fieldset")){
$detailFields = $this->detailFormFields;
} else if( isset( $childData ) && is_string($this->ctf->detailFormFields)){
$functioncall = $this->ctf->detailFormFields;
if($childData->hasMethod($functioncall)){
$detailFields = $childData->$functioncall();
}
} elseif(! isset( $childData ) || $this->methodName == 'add') {
$SNG_sourceClass = singleton($this->ctf->sourceClass());
if($childData && is_numeric($childData->ID) && $this->ctf->getParentClass()) {
// make sure the relation-link is existing, even if we just add the sourceClass
// and didn't save it
$parentIDName = $this->ctf->getParentIdName( $this->getParentClass(), $this->sourceClass() );
$SNG_sourceClass->$parentIDName = $childData->ID;
}
$functioncall = $this->detailFormFields;
if($SNG_sourceClass->hasMethod($functioncall)){
$detailFields = $SNG_sourceClass->$functioncall();
}
else
$detailFields = $SNG_sourceClass->getCMSFields();
} else {
$detailFields = $childData->getCMSFields();
}
if($this->ctf->getParentClass()) {
$parentIdName = $this->ctf->getParentIdName($this->ctf->getParentClass(), $this->ctf->sourceClass());
/*
if(!$parentIdName) {
user_error("ComplexTableField::DetailForm() Cannot automatically
determine 'has-one'-relationship to parent class " . $this->ctf->getParentClass() . ",
please use setParentClass() to set it manually",
E_USER_WARNING);
return;
}
*/
if($parentIdName) {
// add relational fields
$detailFields->push(new HiddenField("ctf[parentClass]"," ",$this->ctf->getParentClass()));
if( $this->relationAutoSetting )
$detailFields->push(new HiddenField("$parentIdName"," ",$childData->ID));
}
}
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
$detailFields->removeByName('ID');
// only add childID if we're not adding a record
if($this->methodName != 'add') {
$detailFields->push(new HiddenField("ctf[childID]","",$childData->ID));
}
// add a namespaced ID instead thats "converted" by saveComplexTableField()
$detailFields->push(new HiddenField("ctf[ClassName]","",$this->ctf->sourceClass()));
$fields = $this->ctf->getFieldsFor($childData);
$validator = $this->ctf->getValidatorFor($childData);
$readonly = ($this->methodName == "show");
// if no custom validator is set, and there's on present on the object (e.g. Member), use it
if(!isset($this->ctf->detailFormValidator) && singleton($this->ctf->sourceClass())->hasMethod('getValidator')) {
$this->ctf->detailFormValidator = singleton($this->ctf->sourceClass())->getValidator();
}
$form = Object::create($this->ctf->popupClass,$this, "DetailForm", $detailFields, $this->ctf->detailFormValidator, $readonly, $childData);
$form = Object::create(
$this->ctf->popupClass,
$this, "DetailForm",
$fields, $validator, $readonly, $childData);
if (is_numeric($childData->ID)) {
if ($this->methodName == "show" || $this->methodName == "edit") {
$form->loadDataFrom($childData);
}
}
if ($this->methodName == "show") {
$form->makeReadonly();
}
$form->loadDataFrom($childData);
if ($readonly) $form->makeReadonly();
return $form;
}
/**
* Use the URL-Parameter "action_saveComplexTableField"
* to provide a clue to the main controller if the main form has to be rendered,
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
* which in turn saves the record.
*
* @see {Form::ReferencedField}).
*/
function saveComplexTableField($data, $form, $request) {
$form->saveInto($this->obj());
$this->obj()->write();
// if ajax-call in an iframe, update window
if(Director::is_ajax()) {
// Newly saved objects need their ID reflected in the reloaded form to avoid double saving
$form = $this->controller->DetailForm();
//$form->loadDataFrom($this->dataObject);
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
return FormResponse::respond();
} else {
Director::redirectBack();
}
}
function PopupBaseLink() {
$link = $this->FormAction() . "&action_callfieldmethod&fieldName={$this->Name()}";
@ -790,7 +848,6 @@ class ComplexTableField_Popup extends Form {
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
* Some have special requirements.
*/
Requirements::clear();
//Requirements::css('cms/css/layout.css');
Requirements::css('jsparty/tabstrip/tabstrip.css');
Requirements::css('sapphire/css/Form.css');
@ -812,7 +869,7 @@ class ComplexTableField_Popup extends Form {
Requirements::javascript("sapphire/javascript/ComplexTableField_popup.js");
if($this->dataObject->hasMethod('getRequirementsForPopup')) {
$this->data->getRequirementsForPopup();
$this->dataObject->getRequirementsForPopup();
}
$actions = new FieldSet();
@ -833,31 +890,6 @@ class ComplexTableField_Popup extends Form {
function ShowPagination() {
return $this->controller->ShowPagination();
}
/**
* Use the URL-Parameter "action_saveComplexTableField"
* to provide a clue to the main controller if the main form has to be rendered,
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
* which in turn saves the record.
*
* @see {Form::ReferencedField}).
*/
function saveComplexTableField($params) {
$this->saveInto($this->dataObject);
$this->dataObject->write();
// if ajax-call in an iframe, update window
if(Director::is_ajax()) {
// Newly saved objects need their ID reflected in the reloaded form to avoid double saving
$form = $this->controller->DetailForm();
//$form->loadDataFrom($this->dataObject);
FormResponse::update_dom_id($form->FormName(), $form->formHtmlContent(), true, 'update');
return FormResponse::respond();
} else {
Director::redirectBack();
}
}
}
?>

View File

@ -182,11 +182,11 @@ class Form extends RequestHandlingData {
// First, try a handler method on the controller
if($this->controller->hasMethod($funcName)) {
return $this->controller->$funcName($vars, $this);
return $this->controller->$funcName($vars, $this, $request);
// Otherwise, try a handler method on the form object
} else {
return $this->$funcName($vars, $this);
return $this->$funcName($vars, $this, $request);
}
}
@ -456,7 +456,6 @@ class Form extends RequestHandlingData {
*/
function setFormMethod($method) {
$this->formMethod = strtolower($method);
if($this->formMethod == 'get') $this->fields->push(new HiddenField('executeForm', '', $this->name));
}
/**
@ -466,20 +465,46 @@ class Form extends RequestHandlingData {
* @return string
*/
function FormAction() {
if($this->controller->hasMethod("FormObjectLink")) {
if ($this->formActionPath) {
return $this->formActionPath;
} elseif($this->controller->hasMethod("FormObjectLink")) {
return $this->controller->FormObjectLink($this->name);
} else {
$link = $this->controller->Link();
if(substr($link,-1) != '/') $link .= '/';
return $link . $this->name;
} else {
return Controller::join_links($this->controller->Link(), $this->name);
}
}
/** @ignore */
private $formActionPath = false;
/**
* Set the form action attribute to a custom URL.
*
* Note: For "normal" forms, you shouldn't need to use this method. It is recommended only for situations where you have
* two relatively distinct parts of the system trying to communicate via a form post.
*/
function setFormAction($path) {
$this->formActionPath = $path;
}
/**
* @ignore
*/
private $htmlID = null;
/**
* Returns the name of the form
*/
function FormName() {
return $this->class . '_' . str_replace('.', '', $this->name);
if($this->htmlID) return $this->htmlID;
else return $this->class . '_' . str_replace('.','',$this->name);
}
/**
* Set the HTML ID attribute of the form
*/
function setHTMLID($id) {
$this->htmlID = $id;
}
/**
@ -920,7 +945,6 @@ class Form extends RequestHandlingData {
*/
function testSubmission($action, $data) {
$data['action_' . $action] = true;
$data['executeForm'] = $this->name;
return Director::test($this->FormAction(), $data, Controller::curr()->getSession());

View File

@ -465,16 +465,6 @@ HTML;
user_error('FormField::setExtraClass() is deprecated. Use FormField::addExtraClass() instead.', E_USER_NOTICE);
$this->extraClasses[] = $extraClass;
}
/**
* This returns a link used to specify a controller in a form action.
* This lets us use FormFields as controllers.
*/
function FormObjectLink($formName) {
return $this->form->Controller()->Link() . "?executeController=" . $this->form->Name() . ".FieldMap." . $this->Name() . "&executeForm=" . $formName;
}
}
?>

View File

@ -72,8 +72,6 @@ ComplexTableField.prototype = {
popupLink = _popupLink;
table = _table;
} else {
alert(this.innerHTML);
// if clicked item is an input-element, don't trigger popup
var el = Event.element(e);
var input = Event.findElement(e,"input");

View File

@ -75,6 +75,12 @@ class SearchContext extends Object {
parent::__construct();
}
/**
* Returns scaffolded search fields for UI.
*
* @todo is this necessary in the SearchContext? - ModelAdmin could unwrap this and just use DataObject::scaffoldSearchFields
* @return FieldSet
*/
public function getSearchFields() {
return ($this->fields) ? $this->fields : singleton($this->modelClass)->scaffoldSearchFields();
}
@ -140,7 +146,7 @@ class SearchContext extends Object {
}
public function getFilters() {
return $this->fields;
return $this->fields;
}
public function setFilters($filters) {

View File

@ -8,7 +8,7 @@
* @package sapphire
* @subpackage search
*/
class ExactMatchSearchFilter extends SearchFilter {
class ExactMatchFilter extends SearchFilter {
}
?>

View File

@ -5,7 +5,7 @@
* @package sapphire
* @subpackage search
*/
class FulltextSearchFilter extends SearchFilter {
class FulltextFilter extends SearchFilter {
}
?>

View File

@ -5,7 +5,7 @@
* @package sapphire
* @subpackage search
*/
class PartialMatchSearchFilter extends SearchFilter {
class PartialMatchFilter extends SearchFilter {
}
?>

View File

@ -0,0 +1,13 @@
<?php
/**
* Uses a substring match against content in column rows.
*
* @package sapphire
* @subpackage search
*/
class SubstringMatchFilter extends SearchFilter {
}
?>

View File

@ -123,6 +123,20 @@ class DataObjectTest extends SapphireTest {
$comment = DataObject::get_one('PageComment', '', true, 'Name DESC');
$this->assertTrue($comment->Name == 'Joe');
}
/**
* Test writing of database columns which don't correlate to a DBField,
* e.g. all relation fields on has_one/has_many like "ParentID".
*
*/
function testWritePropertyWithoutDBField() {
$page = $this->objFromFixture('Page', 'page1');
$page->ParentID = 99;
$page->write();
// reload the page from the database
$savedPage = DataObject::get_by_id('Page', $page->ID);
$this->assertTrue($savedPage->ParentID == 99);
}
/**
* Test has many relationships

18
tests/HTTPRequestTest.php Normal file
View File

@ -0,0 +1,18 @@
<?php
class HTTPRequestTest extends SapphireTest {
static $fixture_file = null;
function testMatch() {
$request = new HTTPRequest("GET", "admin/crm/add");
/* When a rule matches, but has no variables, array("_matched" => true) is returned. */
$this->assertEquals(array("_matched" => true), $request->match('admin/crm', true));
/* Becasue we shifted admin/crm off the stack, just "add" should be remaining */
$this->assertEquals("add", $request->remaining());
$this->assertEquals(array("_matched" => true), $request->match('add', true));
}
}

View File

@ -0,0 +1,35 @@
<?php
class SearchContextTest extends SapphireTest {
static $fixture_file = 'sapphire/tests/SearchContextTest.yml';
function testResultSetFilterReturnsExpectedCount() {
$person = singleton('PersonBubble');
$context = $person->getDefaultSearchContext();
$results = $context->getResultSet(array('Name'=>''));
$this->assertEquals(5, $results->Count());
$results = $context->getResultSet(array('EyeColor'=>'green'));
$this->assertEquals(2, $results->Count());
$results = $context->getResultSet(array('EyeColor'=>'green', 'HairColor'=>'black'));
$this->assertEquals(1, $results->Count());
}
//function
}
class PersonBubble extends DataObject {
static $db = array(
"Name" => "Text",
"Email" => "Text",
"HairColor" => "Text",
"EyeColor" => "Text"
);
}
?>

View File

@ -0,0 +1,28 @@
PersonBubble:
person1:
Name: James
Email: james@example.com
HairColor: brown
EyeColor: brown
person2:
Name: John
Email: john@example.com
HairColor: blond
EyeColor: blue
person3:
Name: Jane
Email: jane@example.com
HairColor: brown
EyeColor: green
person4:
Name: Hemi
Email: hemi@example.com
HairColor: black
EyeColor: brown
person5:
Name: Sara
Email: sara@example.com
HairColor: black
EyeColor: green