mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API CHANGE: Introduce DataModel object, as a representation of the project's entire data model, and tie it to $this->model an all DataObjects, Controllers, and RequestHandlers for easy non-static access.
API CHANGE: Add DataList::newObject(), which creates a new object on that DataList. API CHANGE: RequestHandler::handleRequest() now needs to handle a $model argument, if you override it.
This commit is contained in:
parent
2f79961988
commit
7fbb919ce8
@ -11,10 +11,11 @@ class VersionedRestfulServer extends Controller {
|
||||
'index'
|
||||
);
|
||||
|
||||
function handleRequest($request) {
|
||||
function handleRequest($request, $model) {
|
||||
$this->setModel($model);
|
||||
Versioned::reading_stage('Live');
|
||||
$restfulserver = new RestfulServer();
|
||||
$response = $restfulserver->handleRequest($request);
|
||||
$response = $restfulserver->handleRequest($request, $model);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ if(!$url) {
|
||||
$_SERVER['REQUEST_URI'] = BASE_URL . '/' . $url;
|
||||
|
||||
// Direct away - this is the "main" function, that hands control to the apporopriate controller
|
||||
Director::direct($url);
|
||||
DataModel::set_inst(new DataModel());
|
||||
Director::direct($url, DataModel::inst());
|
||||
|
||||
?>
|
||||
|
@ -115,13 +115,14 @@ class Controller extends RequestHandler {
|
||||
* @return SS_HTTPResponse The response that this controller produces,
|
||||
* including HTTP headers such as redirection info
|
||||
*/
|
||||
function handleRequest(SS_HTTPRequest $request) {
|
||||
function handleRequest(SS_HTTPRequest $request, DataModel $model) {
|
||||
if(!$request) user_error("Controller::handleRequest() not passed a request!", E_USER_ERROR);
|
||||
|
||||
$this->pushCurrent();
|
||||
$this->urlParams = $request->allParams();
|
||||
$this->request = $request;
|
||||
$this->response = new SS_HTTPResponse();
|
||||
$this->setModel($model);
|
||||
|
||||
$this->extend('onBeforeInit');
|
||||
|
||||
@ -138,7 +139,7 @@ class Controller extends RequestHandler {
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
$body = parent::handleRequest($request);
|
||||
$body = parent::handleRequest($request, $model);
|
||||
if($body instanceof SS_HTTPResponse) {
|
||||
if(isset($_REQUEST['debug_request'])) Debug::message("Request handler returned SS_HTTPResponse object to $this->class controller; returning it without modification.");
|
||||
$this->response = $body;
|
||||
|
@ -74,7 +74,7 @@ class Director {
|
||||
* @uses handleRequest() rule-lookup logic is handled by this.
|
||||
* @uses Controller::run() Controller::run() handles the page logic for a Director::direct() call.
|
||||
*/
|
||||
static function direct($url) {
|
||||
static function direct($url, DataModel $model) {
|
||||
// Validate $_FILES array before merging it with $_POST
|
||||
foreach($_FILES as $k => $v) {
|
||||
if(is_array($v['tmp_name'])) {
|
||||
@ -107,7 +107,7 @@ class Director {
|
||||
// Load the session into the controller
|
||||
$session = new Session(isset($_SESSION) ? $_SESSION : null);
|
||||
|
||||
$result = Director::handleRequest($req, $session);
|
||||
$result = Director::handleRequest($req, $session, $model);
|
||||
$session->inst_save();
|
||||
|
||||
// Return code for a redirection request
|
||||
@ -206,7 +206,8 @@ class Director {
|
||||
|
||||
$req = new SS_HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
|
||||
if($headers) foreach($headers as $k => $v) $req->addHeader($k, $v);
|
||||
$result = Director::handleRequest($req, $session);
|
||||
// TODO: Pass in the DataModel
|
||||
$result = Director::handleRequest($req, $session, DataModel::inst());
|
||||
|
||||
// Restore the superglobals
|
||||
$_REQUEST = $existingRequestVars;
|
||||
@ -231,7 +232,7 @@ class Director {
|
||||
*
|
||||
* @return SS_HTTPResponse|string
|
||||
*/
|
||||
protected static function handleRequest(SS_HTTPRequest $request, Session $session) {
|
||||
protected static function handleRequest(SS_HTTPRequest $request, Session $session, DataModel $model) {
|
||||
krsort(Director::$rules);
|
||||
|
||||
if(isset($_REQUEST['debug'])) Debug::show(Director::$rules);
|
||||
@ -264,7 +265,7 @@ class Director {
|
||||
$controllerObj->setSession($session);
|
||||
|
||||
try {
|
||||
$result = $controllerObj->handleRequest($request);
|
||||
$result = $controllerObj->handleRequest($request, $model);
|
||||
} catch(SS_HTTPResponse_Exception $responseException) {
|
||||
$result = $responseException->getResponse();
|
||||
}
|
||||
|
@ -36,6 +36,11 @@ class RequestHandler extends ViewableData {
|
||||
*/
|
||||
protected $request = null;
|
||||
|
||||
/**
|
||||
* The DataModel for this request
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* This variable records whether RequestHandler::__construct()
|
||||
* was called or not. Useful for checking if subclasses have
|
||||
@ -86,9 +91,19 @@ class RequestHandler extends ViewableData {
|
||||
// Check necessary to avoid class conflicts before manifest is rebuilt
|
||||
if(class_exists('NullHTTPRequest')) $this->request = new NullHTTPRequest();
|
||||
|
||||
// This will prevent bugs if setModel() isn't called.
|
||||
$this->model = DataModel::inst();
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DataModel for this request.
|
||||
*/
|
||||
public function setModel($model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles URL requests.
|
||||
*
|
||||
@ -110,7 +125,7 @@ class RequestHandler extends ViewableData {
|
||||
* @uses SS_HTTPRequest->match()
|
||||
* @return SS_HTTPResponse|RequestHandler|string|array
|
||||
*/
|
||||
function handleRequest(SS_HTTPRequest $request) {
|
||||
function handleRequest(SS_HTTPRequest $request, DataModel $model) {
|
||||
// $handlerClass is used to step up the class hierarchy to implement url_handlers inheritance
|
||||
$handlerClass = ($this->class) ? $this->class : get_class($this);
|
||||
|
||||
@ -119,6 +134,7 @@ class RequestHandler extends ViewableData {
|
||||
}
|
||||
|
||||
$this->request = $request;
|
||||
$this->setModel($model);
|
||||
|
||||
// We stop after RequestHandler; in other words, at ViewableData
|
||||
while($handlerClass && $handlerClass != 'ViewableData') {
|
||||
@ -164,7 +180,7 @@ class RequestHandler extends ViewableData {
|
||||
// to prevent infinite loops. Also prevent further handling of controller actions which return themselves
|
||||
// to avoid infinite loops.
|
||||
if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result) && $result instanceof RequestHandler) {
|
||||
$returnValue = $result->handleRequest($request);
|
||||
$returnValue = $result->handleRequest($request, $model);
|
||||
|
||||
// Array results can be used to handle
|
||||
if(is_array($returnValue)) $returnValue = $this->customise($returnValue);
|
||||
|
@ -134,7 +134,7 @@ class DevelopmentAdmin extends Controller {
|
||||
function build($request) {
|
||||
if(Director::is_cli()) {
|
||||
$da = Object::create('DatabaseAdmin');
|
||||
return $da->handleRequest($request);
|
||||
return $da->handleRequest($request, $this->model);
|
||||
} else {
|
||||
$renderer = Object::create('DebugView');
|
||||
$renderer->writeHeader();
|
||||
@ -143,7 +143,7 @@ class DevelopmentAdmin extends Controller {
|
||||
echo "<div class=\"status pending\"><h2 class='buildProgress'>Database is building.... Check below for any errors</h2><h2 class='buildCompleted'>Database has been built successfully</h2></div>";
|
||||
|
||||
$da = Object::create('DatabaseAdmin');
|
||||
return $da->handleRequest($request);
|
||||
return $da->handleRequest($request, $this->model);
|
||||
|
||||
echo "</div>";
|
||||
$renderer->writeFooter();
|
||||
|
@ -112,6 +112,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
*/
|
||||
protected $fixtures;
|
||||
|
||||
protected $model;
|
||||
|
||||
function setUp() {
|
||||
// Mark test as being run
|
||||
$this->originalIsRunningTest = self::$is_running_test;
|
||||
@ -147,6 +149,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
$fixtureFile = eval("return {$className}::\$fixture_file;");
|
||||
$prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_';
|
||||
|
||||
// Todo: this could be a special test model
|
||||
$this->model = DataModel::inst();
|
||||
|
||||
// Set up fixture
|
||||
if($fixtureFile || $this->usesDatabase || !self::using_temp_db()) {
|
||||
if(substr(DB::getConn()->currentDatabase(), 0, strlen($prefix) + 5) != strtolower(sprintf('%stmpdb', $prefix))) {
|
||||
@ -180,7 +185,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
$fixture = new YamlFixture($fixtureFilePath);
|
||||
$fixture->saveIntoDatabase();
|
||||
$fixture->saveIntoDatabase($this->model);
|
||||
$this->fixtures[] = $fixture;
|
||||
|
||||
// backwards compatibility: Load first fixture into $this->fixture
|
||||
|
@ -154,7 +154,7 @@ class YamlFixture extends Object {
|
||||
* the record is written twice: first after populating all non-relational fields,
|
||||
* then again after populating all relations (has_one, has_many, many_many).
|
||||
*/
|
||||
public function saveIntoDatabase() {
|
||||
public function saveIntoDatabase(DataModel $model) {
|
||||
// We have to disable validation while we import the fixtures, as the order in
|
||||
// which they are imported doesnt guarantee valid relations until after the
|
||||
// import is complete.
|
||||
@ -167,7 +167,7 @@ class YamlFixture extends Object {
|
||||
$this->fixtureDictionary = array();
|
||||
foreach($fixtureContent as $dataClass => $items) {
|
||||
if(ClassInfo::exists($dataClass)) {
|
||||
$this->writeDataObject($dataClass, $items);
|
||||
$this->writeDataObject($model, $dataClass, $items);
|
||||
} else {
|
||||
$this->writeSQL($dataClass, $items);
|
||||
}
|
||||
@ -182,9 +182,9 @@ class YamlFixture extends Object {
|
||||
* @param string $dataClass
|
||||
* @param array $items
|
||||
*/
|
||||
protected function writeDataObject($dataClass, $items) {
|
||||
protected function writeDataObject($model, $dataClass, $items) {
|
||||
foreach($items as $identifier => $fields) {
|
||||
$obj = new $dataClass();
|
||||
$obj = $model->$dataClass->newObject();
|
||||
|
||||
// If an ID is explicitly passed, then we'll sort out the initial write straight away
|
||||
// This is just in case field setters triggered by the population code in the next block
|
||||
|
4
main.php
4
main.php
@ -123,8 +123,10 @@ if (isset($_GET['debug_profile'])) Profiler::unmark('DB::connect');
|
||||
|
||||
if (isset($_GET['debug_profile'])) Profiler::unmark('main.php init');
|
||||
|
||||
|
||||
// Direct away - this is the "main" function, that hands control to the appropriate controller
|
||||
Director::direct($url);
|
||||
DataModel::set_inst(new DataModel());
|
||||
Director::direct($url, DataModel::inst());
|
||||
|
||||
if (isset($_GET['debug_profile'])) {
|
||||
Profiler::unmark('all_execution');
|
||||
|
@ -15,6 +15,11 @@ class DataList extends DataObjectSet {
|
||||
*/
|
||||
protected $dataQuery;
|
||||
|
||||
/**
|
||||
* The DataModel from which this DataList comes.
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Synonym of the constructor. Can be chained with literate methods.
|
||||
* DataList::create("SiteTree")->sort("Title") is legal, but
|
||||
@ -35,6 +40,10 @@ class DataList extends DataObjectSet {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function setModel(DataModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function dataClass() {
|
||||
return $this->dataClass;
|
||||
}
|
||||
@ -141,8 +150,10 @@ class DataList extends DataObjectSet {
|
||||
if(empty($row['RecordClassName'])) $row['RecordClassName'] = $row['ClassName'];
|
||||
|
||||
// Instantiate the class mentioned in RecordClassName only if it exists, otherwise default to $this->dataClass
|
||||
if(class_exists($row['RecordClassName'])) return new $row['RecordClassName']($row);
|
||||
else return new $defaultClass($row);
|
||||
if(class_exists($row['RecordClassName'])) $item = new $row['RecordClassName']($row, false, $this->model);
|
||||
else $item = new $defaultClass($row, false, $this->model);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -363,6 +374,15 @@ class DataList extends DataObjectSet {
|
||||
// TO DO: If a filter is given to this data list then
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new item to add to this DataList.
|
||||
* @todo This doesn't factor in filters.
|
||||
*/
|
||||
function newObject($initialFields = null) {
|
||||
$class = $this->dataClass;
|
||||
return new $class($initialFields, false, $this->model);
|
||||
}
|
||||
|
||||
function remove($item) {
|
||||
// TO DO: Allow for amendment of this behaviour - for exmaple, we can remove an item from
|
||||
// an "ActiveItems" DataList by chaning the status to inactive.
|
||||
|
49
model/DataModel.php
Normal file
49
model/DataModel.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Representation of a DataModel - a collection of DataLists for each different data type.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* $model = new DataModel;
|
||||
* $mainMenu = $model->SiteTree->where('"ParentID" = 0 AND "ShowInMenus" = 1');
|
||||
*/
|
||||
class DataModel {
|
||||
protected static $inst;
|
||||
|
||||
/**
|
||||
* Get the global DataModel.
|
||||
*/
|
||||
static function inst() {
|
||||
if(!self::$inst) self::$inst = new self;
|
||||
return self::$inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the global DataModel, used when data is requested from static methods.
|
||||
*/
|
||||
static function set_inst(DataModel $inst) {
|
||||
self::$inst = $inst;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected $customDataLists = array();
|
||||
|
||||
function __get($class) {
|
||||
if(isset($this->customDataLists[$class])) {
|
||||
return clone $this->customDataLists[$class];
|
||||
} else {
|
||||
$list = DataList::create($class);
|
||||
$list->setModel($this);
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
function __set($class, $item) {
|
||||
$item = clone $item;
|
||||
$item->setModel($this);
|
||||
$this->customDataLists[$class] = $item;
|
||||
}
|
||||
|
||||
}
|
@ -96,6 +96,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public $destroyed = false;
|
||||
|
||||
/**
|
||||
* The DataModel from this this object comes
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Data stored in this objects database record. An array indexed by fieldname.
|
||||
*
|
||||
@ -289,7 +294,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods. Singletons
|
||||
* don't have their defaults set.
|
||||
*/
|
||||
function __construct($record = null, $isSingleton = false) {
|
||||
function __construct($record = null, $isSingleton = false, $model = null) {
|
||||
// Set the fields data.
|
||||
if(!$record) {
|
||||
$record = array(
|
||||
@ -374,6 +379,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// prevent populateDefaults() and setField() from marking overwritten defaults as changed
|
||||
$this->changed = array();
|
||||
|
||||
$this->model = $model ? $model : DataModel::inst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DataModel
|
||||
*/
|
||||
function setModel(DataModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -399,7 +413,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
function duplicate($doWrite = true) {
|
||||
$className = $this->class;
|
||||
$clone = new $className( $this->record );
|
||||
$clone = new $className( $this->record, false, $this->model );
|
||||
$clone->ID = 0;
|
||||
|
||||
if($doWrite) {
|
||||
@ -497,7 +511,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
'ClassName' => $originalClass,
|
||||
'RecordClassName' => $originalClass,
|
||||
)
|
||||
));
|
||||
), false, $this->model);
|
||||
|
||||
if($newClassName != $originalClass) {
|
||||
$newInstance->setClassName($newClassName);
|
||||
@ -1199,7 +1213,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// - move the details of the delete code in the DataQuery system
|
||||
// - update the code to just delete the base table, and rely on cascading deletes in the DB to do the rest
|
||||
// obviously, that means getting requireTable() to configure cascading deletes ;-)
|
||||
$srcQuery = DataList::create($this->class)->where("ID = $this->ID")->dataQuery()->query();
|
||||
$srcQuery = DataList::create($this->class, $this->model)->where("ID = $this->ID")->dataQuery()->query();
|
||||
foreach($srcQuery->queriedTables() as $table) {
|
||||
$query = new SQLQuery("*", array('"'.$table.'"'));
|
||||
$query->where("\"ID\" = $this->ID");
|
||||
@ -1274,11 +1288,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$joinID = $this->getField($joinField);
|
||||
|
||||
if($joinID) {
|
||||
$component = DataObject::get_by_id($class, $joinID);
|
||||
$component = $this->model->$class->byID($joinID);
|
||||
}
|
||||
|
||||
if(!isset($component) || !$component) {
|
||||
$component = new $class();
|
||||
$component = $this->model->$class->newObject();
|
||||
}
|
||||
} elseif($class = $this->belongs_to($componentName)) {
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to');
|
||||
@ -1289,7 +1303,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
if(!isset($component) || !$component) {
|
||||
$component = new $class();
|
||||
$component = $this->model->$class->newObject();
|
||||
$component->$joinField = $this->ID;
|
||||
}
|
||||
} else {
|
||||
@ -1327,6 +1341,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'has_many');
|
||||
|
||||
$result = new HasManyList($componentClass, $joinField);
|
||||
if($this->model) $result->setModel($this->model);
|
||||
if($this->ID) $result->setForeignID($this->ID);
|
||||
|
||||
$result = $result->where($filter)->limit($limit)->sort($sort)->join($join);
|
||||
@ -1414,6 +1429,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
$result = new ManyManyList($componentClass, $table, $componentField, $parentField,
|
||||
$this->many_many_extraFields($componentName));
|
||||
if($this->model) $result->setModel($this->model);
|
||||
|
||||
// If this is called on a singleton, then we return an 'orphaned relation' that can have the
|
||||
// foreignID set elsewhere.
|
||||
@ -2488,6 +2504,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Todo: Make the $containerClass method redundant
|
||||
if($containerClass != "DataList") user_error("The DataObject::get() \$containerClass argument has been deprecated", E_USER_NOTICE);
|
||||
$result = DataList::create($callerClass)->where($filter)->sort($sort)->join($join)->limit($limit);
|
||||
$result->setModel(DataModel::inst());
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -2495,9 +2512,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @deprecated
|
||||
*/
|
||||
public function Aggregate($class = null) {
|
||||
if($class) return new DataList($class);
|
||||
else if(isset($this)) return new DataList(get_class($this));
|
||||
if($class) {
|
||||
$list = new DataList($class);
|
||||
$list->setModel(DataModel::inst());
|
||||
} else if(isset($this)) {
|
||||
$list = new DataList(get_class($this));
|
||||
$list->setModel($this->model);
|
||||
}
|
||||
else throw new InvalidArgumentException("DataObject::aggregate() must be called as an instance method or passed a classname");
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2596,6 +2619,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
if(!$cache || !isset(DataObject::$cache_get_one[$callerClass][$cacheKey])) {
|
||||
$dl = DataList::create($callerClass)->where($filter)->sort($orderby);
|
||||
$dl->setModel(DataModel::inst());
|
||||
$item = $dl->First();
|
||||
|
||||
if($cache) {
|
||||
@ -2794,7 +2818,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(!$hasData) {
|
||||
$className = $this->class;
|
||||
foreach($defaultRecords as $record) {
|
||||
$obj = new $className($record);
|
||||
$obj = $this->model->$className->newObject($record);
|
||||
$obj->write();
|
||||
}
|
||||
DB::alteration_message("Added default records to $className table","created");
|
||||
|
@ -349,6 +349,7 @@ class Security extends Controller {
|
||||
$tmpPage->ID = -1 * rand(1,10000000);
|
||||
|
||||
$controller = new Page_Controller($tmpPage);
|
||||
$controller->setModel($this->model);
|
||||
$controller->init();
|
||||
//Controller::$currentController = $controller;
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user