API CHANGE: Removed manifest's dependency on database, by removing hastable information [17:47:04]

dquote> API CHANGE: Deprecate ClassInfo::ready() in favour of Security::database_is_ready()
dquote> API CHANGE: Create DataObject::has_own_table() to determine whether a DataObject has a table without looking at it.
dquote> API CHANGE: Exclude /tests/ directories from the manifest entirely except when tests are being run.
dquote> API CHANGE: Added ?usetestmanifest=1 flag to access the test manifest outside of test execution.
dquote> API CHANGE: Simplified Core.php manifest include to just call ManifestBuilder::include_manifest() - manifest takes care of its own cache file

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@65385 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2008-11-06 04:51:25 +00:00
parent 728e691a1a
commit c1d6e82248
12 changed files with 188 additions and 233 deletions

View File

@ -11,10 +11,6 @@ abstract class CliController extends Controller {
}
function index() {
// Always re-compile the manifest (?flush=1)
ManifestBuilder::update_db_tables(DB::getConn()->tableList(), $_ALL_CLASSES);
ManifestBuilder::write_manifest();
foreach( ClassInfo::subclassesFor( $this->class ) as $subclass ) {
echo $subclass;

View File

@ -8,15 +8,7 @@
* @subpackage core
*/
class ClassInfo {
/**
* Returns true if the manifest has actually been built.
*/
static function ready() {
global $_ALL_CLASSES;
return $_ALL_CLASSES && $_ALL_CLASSES['hastable'];
}
/**
/**
* @todo Improve documentation
*/
static function allClasses() {
@ -33,11 +25,11 @@ class ClassInfo {
}
/**
* @todo Improve documentation
* @todo Move this to Database or DB
*/
static function hasTable($class) {
global $_ALL_CLASSES;
return isset($_ALL_CLASSES['hastable'][$class]) ? $_ALL_CLASSES['hastable'][$class] : null;
$SQL_table = Convert::raw2sql($class);
return (bool)(DB::query("SHOW TABLES LIKE '$SQL_table'")->value());
}
/**
@ -60,6 +52,7 @@ class ClassInfo {
* to those with DB tables
*
* @param mixed $class string of the classname or instance of the class
* @todo Move this into data object
* @return array
*/
static function dataClassesFor($class) {
@ -70,20 +63,15 @@ class ClassInfo {
if(!$_ALL_CLASSES['parents'][$class]) user_error("ClassInfo::dataClassesFor() no parents for $class", E_USER_WARNING);
foreach($_ALL_CLASSES['parents'][$class] as $subclass) {
if(isset($_ALL_CLASSES['hastable'][$subclass])){
$dataClasses[] = $subclass;
}
if(DataObject::has_own_table($subclass)) $dataClasses[] = $subclass;
}
if(isset($_ALL_CLASSES['hastable'][$class])) $dataClasses[] = $class;
if(DataObject::has_own_table($class)) $dataClasses[] = $class;
if(isset($_ALL_CLASSES['children'][$class]))
foreach($_ALL_CLASSES['children'][$class] as $subclass)
{
if(isset($_ALL_CLASSES['hastable'][$subclass]))
{
$dataClasses[] = $subclass;
}
if(DataObject::has_own_table($subclass)) $dataClasses[] = $subclass;
}
return $dataClasses;
@ -134,7 +122,7 @@ class ClassInfo {
$items = $_ALL_CLASSES['parents'][$class];
$items[$class] = $class;
if($onlyWithTables) foreach($items as $item) {
if(!isset($_ALL_CLASSES['hastable'][$item]) || !$_ALL_CLASSES['hastable'][$item]) unset($items[$item]);
if(!DataObject::has_own_table($item)) unset($items[$item]);
}
return $items;
}
@ -174,5 +162,17 @@ class ClassInfo {
return $matchedClasses;
}
/////////////////////////////////////////////////////////////////////////////
// DEPRECATED
/**
* @deprecated Use Security::database_is_ready() instead.
*/
static function ready() {
user_error("ClassInfo::ready() deprectaed - use Security::database_is_ready()", E_USER_NOTICE);
return Security::database_is_ready();
}
}
?>
?>

View File

@ -158,13 +158,9 @@ require_once("core/Session.php");
// MANIFEST
/**
* Build the manifest
* Include the manifest
*/
if(ManifestBuilder::staleManifest()){
ManifestBuilder::compileManifest();
}
require_once(MANIFEST_FILE);
ManifestBuilder::include_manifest();
/**
* ?debugmanifest=1 hook

View File

@ -41,7 +41,7 @@ class ManifestBuilder {
'install.php',
'index.php',
'check-php.php',
'rewritetest.php'
'rewritetest.php',
);
/**
@ -56,54 +56,44 @@ class ManifestBuilder {
);
/**
* @var int $cache_expiry_mins Specifies the time (in minutes) until a rebuild
* of the manifest cache is forced.
* @usedby self::staleManifest()
* Include the manifest, regenerating it if necessary
*/
public static $cache_expiry_mins = 60;
/**
* Returns true if the manifest file should be regenerated,
* by asserting one of the following conditions:
* - Manifest cache file doesn't exist
* - The modification time of the webroot folder is newer than the cache file
* - The cache file is older than {@link self::$cache_expiry_mins}
* - A cache rebuild is forced by the "?flush=1" URL parameter
*
* Checked on every request handled by SilverStripe in main.php or cli-script.php.
*
* @return bool Returns TRUE if the manifest file should be regenerated, otherwise FALSE.
*/
static function staleManifest() {
$lastEdited = filemtime(BASE_PATH);
return (
!file_exists(MANIFEST_FILE)
|| (filemtime(MANIFEST_FILE) < $lastEdited)
|| (filemtime(MANIFEST_FILE) < time() - 60 * self::$cache_expiry_mins)
|| isset($_GET['flush'])
);
static function include_manifest() {
if(isset($_REQUEST['usetestmanifest'])) {
self::load_test_manifest();
} else {
if(!file_exists(MANIFEST_FILE) || (filemtime(MANIFEST_FILE) < filemtime(BASE_PATH)) || isset($_GET['flush'])) {
self::create_manifest_file();
}
require_once(MANIFEST_FILE);
}
}
/**
* Load a copy of the manifest with tests/ folders included.
* Only loads the ClassInfo and __autoload() globals; this assumes that _config.php files are already included.
*/
static function load_test_manifest() {
// Build the complete manifest
$manifestInfo = self::get_manifest_info(BASE_PATH);
// Load it into the current session.
self::process_manifest($manifestInfo);
}
/**
* Loads all PHP class files - actually opening them and executing them.
*/
static function load_all_classes() {
global $_CLASS_MANIFEST;
foreach($_CLASS_MANIFEST as $classFile) require_once($classFile);
}
/**
* Generates a new manifest file and saves it to {@link MANIFEST_FILE}.
*/
static function compileManifest() {
// Config manifest
$baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/..";
$baseDir = ereg_replace("/[^/]+/\\.\\.", "", $baseDir);
$baseDir = preg_replace("/\\\\/", "/", $baseDir);
$manifestInfo = self::get_manifest_info($baseDir);
// Connect to the database and get the database config
global $databaseConfig;
DB::connect($databaseConfig);
if(DB::isActive()) {
$tableList = DB::getConn()->tableList();
self::update_db_tables($tableList, $manifestInfo['globals']['_ALL_CLASSES']);
}
private static function create_manifest_file() {
// Build the manifest, ignoring the tests/ folders
$manifestInfo = self::get_manifest_info(BASE_PATH, array("tests"));
$manifest = self::generate_php_file($manifestInfo);
if($fh = fopen(MANIFEST_FILE, "w")) {
@ -122,7 +112,7 @@ class ManifestBuilder {
$output = "<?php\n";
foreach($manifestInfo['globals'] as $globalName => $globalVal) {
$output .= "\$$globalName = " . var_export($globalVal, true) . ";\n\n";
$output .= "global \$$globalName;\n\$$globalName = " . var_export($globalVal, true) . ";\n\n";
}
foreach($manifestInfo['require_once'] as $requireItem) {
$output .= "require_once(\"$requireItem\");\n";
@ -130,12 +120,28 @@ class ManifestBuilder {
return $output;
}
/**
* Parse the $manifestInfo array, updating the appropriate globals and loading the appropriate _config files.
*/
static function process_manifest($manifestInfo) {
foreach($manifestInfo['globals'] as $globalName => $globalVal) {
global $$globalName;
$$globalName = $globalVal;
}
foreach($manifestInfo['require_once'] as $requireItem) {
require_once("$requireItem");
}
}
/**
* Return an array containing information for the manifest
* @param $baseDir A
* @param $baseDir The root directory to analyse
* @param $excludedFolders An array folder names to exclude. These don't care about where the
* folder appears in the hierarchy, so be careful
*/
static function get_manifest_info($baseDir, $tableList = null) {
static function get_manifest_info($baseDir, $excludedFolders = array()) {
// locate and include the exclude files
$topLevel = scandir($baseDir);
foreach($topLevel as $file) {
@ -148,73 +154,49 @@ class ManifestBuilder {
require_once($fullPath . '/_exclude.php');
}
}
// Project - used to give precedence to template files
$project = null;
// Class manifest
// Class, CSS, template manifest
$classManifest = array();
$templateManifest = array();
$cssManifest = array();
if(is_array(self::$restrict_to_modules) && count(self::$restrict_to_modules)) {
// $restrict_to_modules is set, so we include only those specified
// modules
foreach(self::$restrict_to_modules as $module)
ManifestBuilder::getClassManifest($baseDir . '/' . $module,
$classManifest);
ManifestBuilder::getClassManifest($baseDir . '/' . $module, $excludedFolders, $classManifest);
} else {
// Include all directories which have an _config.php file but don't
// have an _manifest_exclude file
$topLevel = scandir($baseDir);
foreach($topLevel as $filename) {
if($filename[0] == '.') continue;
if($filename == 'themes') continue;
if(in_array($filename, $excludedFolders)) continue;
if(@is_dir("$baseDir/$filename") &&
file_exists("$baseDir/$filename/_config.php") &&
!file_exists("$baseDir/$filename/_manifest_exclude")) {
ManifestBuilder::getClassManifest("$baseDir/$filename",
$classManifest);
// Get classes, templates, and CSS files
ManifestBuilder::getClassManifest("$baseDir/$filename", $excludedFolders, $classManifest);
ManifestBuilder::getTemplateManifest($baseDir, $filename, $excludedFolders, $templateManifest, $cssManifest);
// List the _config.php files
$manifestInfo["require_once"][] = "$baseDir/$filename/_config.php";
// Find the $project variable in the relevant config file without having to execute the config file
if(preg_match("/\\\$project\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("$baseDir/$filename/_config.php"), $parts)) {
eval($parts[0]);
}
}
}
}
$manifestInfo["globals"]["_CLASS_MANIFEST"] = $classManifest;
// Load the manifest in, so that the autoloader works
global $_CLASS_MANIFEST;
$_CLASS_MANIFEST = $classManifest;
// Load in a temporary all-classes array for using while building the manifest
// @todo Manifestbuilder is tightly convoluted and gets really hard to debug. We have catch-22s betweeen
// db connection, value of project(), and ClassInfo responses... It needs to be untangled.
global $_ALL_CLASSES;
$allClasses = ManifestBuilder::allClasses($classManifest, array());
$_ALL_CLASSES = $allClasses;
// _config.php manifest
$topLevel = scandir($baseDir);
foreach($topLevel as $filename) {
if($filename[0] == '.') continue;
if(@is_dir("$baseDir/$filename/") &&
file_exists("$baseDir/$filename/_config.php") &&
!file_exists("$baseDir/$filename/_manifest_exclude")) {
$manifestInfo["require_once"][] = "$baseDir/$filename/_config.php";
// Include this so that we're set up for connecting to the database
// in the rest of the manifest builder
require_once("$baseDir/$filename/_config.php");
}
}
if(!project())
user_error("\$project isn't set", E_USER_WARNING);
// Template & CSS manifest
$templateManifest = array();
$cssManifest = array();
// Only include directories if they have an _config.php file
$topLevel = scandir($baseDir);
foreach($topLevel as $filename) {
if($filename[0] == '.') continue;
if($filename != 'themes' && @is_dir("$baseDir/$filename") && file_exists("$baseDir/$filename/_config.php")) {
ManifestBuilder::getTemplateManifest($baseDir, $filename, $templateManifest, $cssManifest);
}
}
// Get themes
if(file_exists("$baseDir/themes")) {
$themeDirs = scandir("$baseDir/themes");
@ -222,24 +204,22 @@ class ManifestBuilder {
if(substr($themeDir,0,1) == '.') continue;
// The theme something_forum is understood as being a part of the theme something
$themeName = strtok($themeDir, '_');
ManifestBuilder::getTemplateManifest($baseDir, "themes/$themeDir", $templateManifest, $cssManifest, $themeName);
ManifestBuilder::getTemplateManifest($baseDir, "themes/$themeDir", $excludedFolders, $templateManifest, $cssManifest, $themeName);
}
}
// Ensure that any custom templates get favoured
ManifestBuilder::getTemplateManifest($baseDir, project(), $templateManifest, $cssManifest);
// Build class-info array from class manifest
$allClasses = ManifestBuilder::allClasses($classManifest);
// Ensure that any custom templates get favoured
if(!$project) user_error("\$project isn't set", E_USER_WARNING);
ManifestBuilder::getTemplateManifest($baseDir, $project, $excludedFolders, $templateManifest, $cssManifest);
$manifestInfo["globals"]["_CLASS_MANIFEST"] = $classManifest;
$manifestInfo["globals"]["_ALL_CLASSES"] = $allClasses;
$manifestInfo["globals"]["_TEMPLATE_MANIFEST"] = $templateManifest;
$manifestInfo["globals"]["_CSS_MANIFEST"] = $cssManifest;
// Database manifest
$allClasses = ManifestBuilder::allClasses($classManifest, $tableList);
$manifestInfo["globals"]["_ALL_CLASSES"] = $allClasses;
global $_ALL_CLASSES;
$_ALL_CLASSES = $allClasses;
return $manifestInfo;
}
@ -251,7 +231,7 @@ class ManifestBuilder {
* @param string $folder The folder to traverse (recursively)
* @param array $classMap The already built class map
*/
private static function getClassManifest($folder, &$classMap) {
private static function getClassManifest($folder, $excludedFolders, &$classMap) {
$items = scandir($folder);
if($items) foreach($items as $item) {
// Skip some specific PHP files
@ -276,8 +256,11 @@ class ManifestBuilder {
if($item == 'lang' && @is_dir("$folder/$item") && ereg_replace("/[^/]+/\\.\\.","",$folder.'/..') == Director::baseFolder()) continue;
if(@is_dir("$folder/$item")) {
// Folder exclusion - used to skip over tests/ folders
if(in_array($item, $excludedFolders)) continue;
// recurse into directories (if not in $ignore_folders)
ManifestBuilder::getClassManifest("$folder/$item", $classMap);
ManifestBuilder::getClassManifest("$folder/$item", $excludedFolders, $classMap);
} else {
// include item in the manifest
$itemCode = substr($item,0,-4);
@ -307,7 +290,7 @@ class ManifestBuilder {
* Generates the template manifest - a list of all the .SS files in the
* application
*/
private static function getTemplateManifest($baseDir, $folder, &$templateManifest, &$cssManifest, $themeName = null) {
private static function getTemplateManifest($baseDir, $folder, $excludedFolders, &$templateManifest, &$cssManifest, $themeName = null) {
$items = scandir("$baseDir/$folder");
if($items) foreach($items as $item) {
if(substr($item,0,1) == '.') continue;
@ -334,7 +317,10 @@ class ManifestBuilder {
} else if(@is_dir("$baseDir/$folder/$item")) {
ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $templateManifest, $cssManifest, $themeName);
// Folder exclusion - used to skip over tests/ folders
if(in_array($item, $excludedFolders)) continue;
ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $excludedFolders, $templateManifest, $cssManifest, $themeName);
}
}
}
@ -342,16 +328,14 @@ class ManifestBuilder {
/**
* Include everything, so that actually *all* classes are available and
* build a map of classes and their subclasses and the information if
* the class has a database table
* build a map of classes and their subclasses
*
* @param $classManifest An array of all Sapphire classes; keys are class names and values are filenames
* @param $tables An array of the tables that exist in the database
*
* @return array Returns an array that holds all class relevant
* information.
*/
private static function allClasses($classManifest, $tables = null) {
private static function allClasses($classManifest) {
self::$classArray = array();
self::$extendsArray = array();
self::$implementsArray = array();
@ -369,7 +353,6 @@ class ManifestBuilder {
foreach(self::$classArray as $class => $info) {
$allClasses['exists'][$class] = $class;
if(isset($tables[strtolower($class)])) $allClasses['hastable'][$class] = $class;
}
// Build a map of classes and their subclasses
@ -377,7 +360,6 @@ class ManifestBuilder {
foreach($_classes as $class) {
$allClasses['exists'][$class] = $class;
if(isset($tables[strtolower($class)])) $allClasses['hastable'][$class] = $class;
foreach($_classes as $subclass) {
if(is_subclass_of($class, $subclass)) $allClasses['parents'][$class][$subclass] = $subclass;
if(is_subclass_of($subclass, $class)) $allClasses['children'][$class][$subclass] = $subclass;
@ -387,7 +369,7 @@ class ManifestBuilder {
return $allClasses;
}
/**
/**
* Parses a php file and adds any class or interface information into self::$classArray
*
* @param string $filename
@ -449,6 +431,8 @@ class ManifestBuilder {
* @return TokenisedRegularExpression
*/
public static function getClassDefParser() {
require_once('core/TokenisedRegularExpression.php');
return new TokenisedRegularExpression(array(
0 => T_CLASS,
1 => T_WHITESPACE,
@ -474,6 +458,8 @@ class ManifestBuilder {
* @return TokenisedRegularExpression
*/
public static function getInterfaceDefParser() {
require_once('core/TokenisedRegularExpression.php');
return new TokenisedRegularExpression(array(
0 => T_INTERFACE,
1 => T_WHITESPACE,
@ -550,68 +536,6 @@ class ManifestBuilder {
}
return $results;;
}
/**
* Updates the active table list in the class info in the manifest, but leaves everything else as-is.
* Much quicker to run than compileManifest :-)
*
* @param $tableList The list of tables to load into the manifest
* @param $allClassesArray The $_ALL_CLASSES array that should be updated
*/
static function update_db_tables($tableList, &$allClassesArray) {
if(!isset($allClassesArray['exists'])) return;
$allClassesArray['hastable'] = array();
$tables = array();
foreach($tableList as $table) $tables[$table] = true;
// We need to iterate through the full class lists, because the table names come out in lowercase
foreach($allClassesArray['exists'] as $class) {
if(isset($tables[strtolower($class)])) $allClassesArray['hastable'][$class] = $class;
}
}
/**
* Write the manifest file, containing the updated values in the applicable globals
*/
static function write_manifest() {
global $_CLASS_MANIFEST, $_TEMPLATE_MANIFEST, $_CSS_MANIFEST, $_ALL_CLASSES;
$manifest = "\$_CLASS_MANIFEST = " . var_export($_CLASS_MANIFEST, true) . ";\n";
// Config manifest
$baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/..";
$baseDir = ereg_replace("/[^/]+/\\.\\.","",$baseDir);
$baseDir = preg_replace("/\\\\/", "/", $baseDir);
$topLevel = scandir($baseDir);
foreach($topLevel as $filename) {
if($filename[0] == '.') continue;
if(@is_dir("$baseDir/$filename/") && file_exists("$baseDir/$filename/_config.php")) {
$manifest .= "require_once(\"$baseDir/$filename/_config.php\");\n";
}
}
$manifest .= "\$_TEMPLATE_MANIFEST = " . var_export($_TEMPLATE_MANIFEST, true) . ";\n";
$manifest .= "\$_CSS_MANIFEST = " . var_export($_CSS_MANIFEST, true) . ";\n";
$manifest .= "\$_ALL_CLASSES = " . var_export($_ALL_CLASSES, true) . ";\n";
$manifest = "<?php\n$manifest\n?>";
if($fh = fopen(MANIFEST_FILE,"w")) {
fwrite($fh, $manifest);
fclose($fh);
} else {
die("Cannot write manifest file! Check permissions of " . MANIFEST_FILE);
}
}
static function includeEverything() {
global $_CLASS_MANIFEST;
foreach($_CLASS_MANIFEST as $classFile) require_once($classFile);
}
}

View File

@ -76,15 +76,16 @@ class Controller extends RequestHandler {
*/
function init() {
// Test and development sites should be secured, via basic-auth
if(ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && Director::isTest() && $this->basicAuthEnabled) {
if(Director::isTest() && $this->basicAuthEnabled && Security::database_is_ready()) {
BasicAuth::requireLogin("SilverStripe test website. Use your CMS login", "ADMIN");
}
//
Cookie::set("PastVisitor", true);
// ClassInfo::hasTable() called to ensure that we're not in a very-first-setup stage
if(ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && ($member = Member::currentUser())) {
// Directly access the session variable just in case the Group or Member tables don't yet exist
if(Session::get('loggedInAs')) {
$member = Member::currentUser();
Cookie::set("PastMember", true);
DB::query("UPDATE Member SET LastVisited = NOW() WHERE ID = $member->ID", null);
}

View File

@ -546,7 +546,8 @@ class Director {
* @return boolean
*/
public static function is_cli() {
return preg_match('/cli-script\.php/', $_SERVER['SCRIPT_NAME']);
return (!isset($_SERVER['HTTP_HOST']) && preg_match('/install\.php/', $_SERVER['SCRIPT_NAME']))
|| preg_match('/cli-script\.php/', $_SERVER['SCRIPT_NAME']);
}
////////////////////////////////////////////////////////////////////////////////////////////
@ -680,7 +681,7 @@ class Director {
// Use ?isDev=1 to get development access on the live server
if(isset($_GET['isDev'])) {
if(ClassInfo::ready()) {
if(Security::database_is_ready()) {
BasicAuth::requireLogin("SilverStripe developer access. Use your CMS login", "ADMIN");
$_SESSION['isDev'] = $_GET['isDev'];
} else {

View File

@ -693,7 +693,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
// No changes made
if($this->changed) {
foreach($this->getClassAncestry() as $ancestor) {
if(ClassInfo::hasTable($ancestor))
if(self::has_own_table($ancestor))
$ancestry[] = $ancestor;
}
@ -758,7 +758,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
}
// In cases where there are no fields, this 'stub' will get picked up on
if(ClassInfo::hasTable($class)) {
if(self::has_own_table($class)) {
$manipulation[$class]['command'] = $dbCommand;
$manipulation[$class]['id'] = $this->record['ID'];
} else {
@ -846,7 +846,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
user_error("$this->class has a broken onBeforeDelete() function. Make sure that you call parent::onBeforeDelete().", E_USER_ERROR);
}
foreach($this->getClassAncestry() as $ancestor) {
if(ClassInfo::hastable($ancestor)) {
if(self::has_own_table($ancestor)) {
$sql = new SQLQuery();
$sql->delete = true;
$sql->from[$ancestor] = "`$ancestor`";
@ -1748,6 +1748,26 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
return isset($fieldMap[$field]) ? strtok($fieldMap[$field],'(') : null;
}
/**
* Returns true if given class has its own table.
* Uses the rules for whether the table should exist rather than actually looking in the database.
*/
public function has_own_table($dataClass) {
if(!is_subclass_of($dataClass,'DataObject')) return false;
if(!isset(self::$cache_has_own_table[$dataClass])) {
if(get_parent_class($dataClass) == 'DataObject') {
self::$cache_has_own_table[$dataClass] = true;
} else {
$sng = singleton($dataClass);
self::$cache_has_own_table[$dataClass] = $sng->uninherited('db',true) || $sng->uninherited('has_one',true);
}
}
return self::$cache_has_own_table[$dataClass];
}
private static $cache_has_own_table = array();
/**
* Returns true if the member is allowed to do the given action.
*

View File

@ -162,10 +162,6 @@ class DatabaseAdmin extends Controller {
}
}
$conn->endSchemaUpdate();
global $_ALL_CLASSES;
ManifestBuilder::update_db_tables(DB::getConn()->tableList(), $_ALL_CLASSES);
if(!$testMode) ManifestBuilder::write_manifest();
if($populate) {
if(!$quiet) {

View File

@ -51,6 +51,7 @@ class TestRunner extends Controller {
function init() {
parent::init();
ManifestBuilder::load_test_manifest();
if (!self::$default_reporter) self::set_reporter(Director::is_cli() ? 'CliDebugView' : 'DebugView');
}
@ -95,7 +96,7 @@ class TestRunner extends Controller {
function coverage() {
if(hasPhpUnit()) {
ManifestBuilder::includeEverything();
ManifestBuilder::load_all_classes();
$tests = ClassInfo::subclassesFor('SapphireTest');
array_shift($tests);
unset($tests['FunctionalTest']);

View File

@ -525,6 +525,24 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals("1001", DB::query("SELECT ID FROM DataObjectTest_SubTeam WHERE SubclassDatabaseField = 'asdfasdf'")->value());
$this->assertEquals("1001", DB::query("SELECT ID FROM DataObjectTest_Team WHERE Title = 'asdfasdf'")->value());
}
public function TestHasOwnTable() {
/* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
$this->assertTrue(DataObject::has_own_table("DataObjectTest_Player"));
$this->assertTrue(DataObject::has_own_table("DataObjectTest_Team"));
$this->assertTrue(DataObject::has_own_table("DataObjectTest_FunnyFieldNames"));
/* Root DataObject that always have a table, even if they lack both $db and $has_one */
$this->assertTrue(DataObject::has_own_table("DataObjectTest_FieldlessTable"));
/* Subclasses without $db or $has_one don't have a table */
$this->assertFalse(DataObject::has_own_table("DataObjectTest_FieldlessSubTable"));
/* Return false if you don't pass it a subclass of DataObject */
$this->assertFalse(DataObject::has_own_table("DataObject"));
$this->assertFalse(DataObject::has_own_table("ViewableData"));
$this->assertFalse(DataObject::has_own_table("ThisIsntADataObject"));
}
}
class DataObjectTest_Player extends Member implements TestOnly {
@ -574,6 +592,13 @@ class DataObjectTest_SubTeam extends DataObjectTest_Team implements TestOnly {
);
}
class DataObjectTest_FieldlessTable extends DataObject implements TestOnly {
}
class DataObjectTest_FieldlessSubTable extends DataObjectTest_Team implements TestOnly {
}
class DataObjectTest_Team_Decorator extends DataObjectDecorator implements TestOnly {
function extraStatics() {

View File

@ -6,7 +6,7 @@
class ManifestBuilderTest extends SapphireTest {
function testManifest() {
$baseFolder = TEMP_FOLDER . '/manifest-test';
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder, DB::getConn()->tableList());
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
global $project;
$this->assertEquals("$baseFolder/sapphire/MyClass.php", $manifestInfo['globals']['_CLASS_MANIFEST']['MyClass']);
@ -36,42 +36,37 @@ class ManifestBuilderTest extends SapphireTest {
$baseFolder = TEMP_FOLDER . '/manifest-test';
global $project;
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder, DB::getConn()->tableList());
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
/* Our fixture defines the class MyClass_InComment inside a comment, so it shouldn't be included in the class manifest. */
$this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
$this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
$this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable']));
/* Our fixture defines the class MyClass_InSlashSlashComment inside a //-style comment, so it shouldn't be included in the class manifest. */
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable']));
}
function testManifestIgnoresClassesInStrings() {
$baseFolder = TEMP_FOLDER . '/manifest-test';
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder, DB::getConn()->tableList());
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
/* If a class defintion is listed in a single quote string, then it shouldn't be inlcuded. Here we have put a class definition for MyClass_InSingleQuoteString inside a single-quoted string */
$this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
$this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
$this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable']));
/* Ditto for double quotes. Here we have put a class definition for MyClass_InDoubleQuoteString inside a double-quoted string. */
$this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
$this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
$this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable']));
/* Finally, we need to ensure that class definitions inside heredoc strings aren't included. Here, we have defined the class MyClass_InHeredocString inside a heredoc string. */
$this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
$this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
$this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable']));
}

View File

@ -9,7 +9,7 @@ class RestfulServiceTest extends SapphireTest {
'test1c' => 'And now for a string test' // string test
);
$connection->setQueryString($test1params);
$test1 = $connection->request('RestfulServiceTest_Controller')->getBody();
$test1 = $connection->request('RestfulServiceTest_Controller?usetestmanifest=1')->getBody();
foreach ($test1params as $key => $value) {
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $test1);
$this->assertContains("<get_item name=\"$key\">$value</get_item>", $test1);
@ -20,7 +20,7 @@ class RestfulServiceTest extends SapphireTest {
'test2b' => '%\'"@?=;:/,$', // special character checks
'test2c' => 'And now for a string test', // string test
);
$test2suburl = 'RestfulServiceTest_Controller/?';
$test2suburl = 'RestfulServiceTest_Controller/?usetestmanifest=1&';
foreach ($test2params as $key=>$value) {
$test2suburl .= "$key=$value&";
}
@ -46,7 +46,7 @@ class RestfulServiceTest extends SapphireTest {
'test1b' => mt_rand(),
'test1c' => 'And now for a string test'
);
$test1 = $connection->request('RestfulServiceTest_Controller', 'POST', $test1params)->getBody();
$test1 = $connection->request('RestfulServiceTest_Controller/?usetestmanifest=1', 'POST', $test1params)->getBody();
foreach ($test1params as $key => $value) {
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $test1);
$this->assertContains("<post_item name=\"$key\">$value</post_item>", $test1);