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() { 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 ) { foreach( ClassInfo::subclassesFor( $this->class ) as $subclass ) {
echo $subclass; echo $subclass;

View File

@ -8,15 +8,7 @@
* @subpackage core * @subpackage core
*/ */
class ClassInfo { 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 * @todo Improve documentation
*/ */
static function allClasses() { static function allClasses() {
@ -33,11 +25,11 @@ class ClassInfo {
} }
/** /**
* @todo Improve documentation * @todo Move this to Database or DB
*/ */
static function hasTable($class) { static function hasTable($class) {
global $_ALL_CLASSES; $SQL_table = Convert::raw2sql($class);
return isset($_ALL_CLASSES['hastable'][$class]) ? $_ALL_CLASSES['hastable'][$class] : null; return (bool)(DB::query("SHOW TABLES LIKE '$SQL_table'")->value());
} }
/** /**
@ -60,6 +52,7 @@ class ClassInfo {
* to those with DB tables * to those with DB tables
* *
* @param mixed $class string of the classname or instance of the class * @param mixed $class string of the classname or instance of the class
* @todo Move this into data object
* @return array * @return array
*/ */
static function dataClassesFor($class) { 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); if(!$_ALL_CLASSES['parents'][$class]) user_error("ClassInfo::dataClassesFor() no parents for $class", E_USER_WARNING);
foreach($_ALL_CLASSES['parents'][$class] as $subclass) { foreach($_ALL_CLASSES['parents'][$class] as $subclass) {
if(isset($_ALL_CLASSES['hastable'][$subclass])){ if(DataObject::has_own_table($subclass)) $dataClasses[] = $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])) if(isset($_ALL_CLASSES['children'][$class]))
foreach($_ALL_CLASSES['children'][$class] as $subclass) foreach($_ALL_CLASSES['children'][$class] as $subclass)
{ {
if(isset($_ALL_CLASSES['hastable'][$subclass])) if(DataObject::has_own_table($subclass)) $dataClasses[] = $subclass;
{
$dataClasses[] = $subclass;
}
} }
return $dataClasses; return $dataClasses;
@ -134,7 +122,7 @@ class ClassInfo {
$items = $_ALL_CLASSES['parents'][$class]; $items = $_ALL_CLASSES['parents'][$class];
$items[$class] = $class; $items[$class] = $class;
if($onlyWithTables) foreach($items as $item) { 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; return $items;
} }
@ -174,5 +162,17 @@ class ClassInfo {
return $matchedClasses; 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 // MANIFEST
/** /**
* Build the manifest * Include the manifest
*/ */
if(ManifestBuilder::staleManifest()){ ManifestBuilder::include_manifest();
ManifestBuilder::compileManifest();
}
require_once(MANIFEST_FILE);
/** /**
* ?debugmanifest=1 hook * ?debugmanifest=1 hook

View File

@ -41,7 +41,7 @@ class ManifestBuilder {
'install.php', 'install.php',
'index.php', 'index.php',
'check-php.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 * Include the manifest, regenerating it if necessary
* of the manifest cache is forced.
* @usedby self::staleManifest()
*/ */
public static $cache_expiry_mins = 60; static function include_manifest() {
if(isset($_REQUEST['usetestmanifest'])) {
/** self::load_test_manifest();
* Returns true if the manifest file should be regenerated, } else {
* by asserting one of the following conditions: if(!file_exists(MANIFEST_FILE) || (filemtime(MANIFEST_FILE) < filemtime(BASE_PATH)) || isset($_GET['flush'])) {
* - Manifest cache file doesn't exist self::create_manifest_file();
* - The modification time of the webroot folder is newer than the cache file }
* - The cache file is older than {@link self::$cache_expiry_mins} require_once(MANIFEST_FILE);
* - 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'])
);
} }
/**
* 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}. * Generates a new manifest file and saves it to {@link MANIFEST_FILE}.
*/ */
static function compileManifest() { private static function create_manifest_file() {
// Config manifest // Build the manifest, ignoring the tests/ folders
$baseDir = dirname($_SERVER['SCRIPT_FILENAME']) . "/.."; $manifestInfo = self::get_manifest_info(BASE_PATH, array("tests"));
$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']);
}
$manifest = self::generate_php_file($manifestInfo); $manifest = self::generate_php_file($manifestInfo);
if($fh = fopen(MANIFEST_FILE, "w")) { if($fh = fopen(MANIFEST_FILE, "w")) {
@ -122,7 +112,7 @@ class ManifestBuilder {
$output = "<?php\n"; $output = "<?php\n";
foreach($manifestInfo['globals'] as $globalName => $globalVal) { 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) { foreach($manifestInfo['require_once'] as $requireItem) {
$output .= "require_once(\"$requireItem\");\n"; $output .= "require_once(\"$requireItem\");\n";
@ -130,12 +120,28 @@ class ManifestBuilder {
return $output; 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 * 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 // locate and include the exclude files
$topLevel = scandir($baseDir); $topLevel = scandir($baseDir);
foreach($topLevel as $file) { foreach($topLevel as $file) {
@ -148,73 +154,49 @@ class ManifestBuilder {
require_once($fullPath . '/_exclude.php'); require_once($fullPath . '/_exclude.php');
} }
} }
// Project - used to give precedence to template files
$project = null;
// Class manifest // Class, CSS, template manifest
$classManifest = array(); $classManifest = array();
$templateManifest = array();
$cssManifest = array();
if(is_array(self::$restrict_to_modules) && count(self::$restrict_to_modules)) { if(is_array(self::$restrict_to_modules) && count(self::$restrict_to_modules)) {
// $restrict_to_modules is set, so we include only those specified // $restrict_to_modules is set, so we include only those specified
// modules // modules
foreach(self::$restrict_to_modules as $module) foreach(self::$restrict_to_modules as $module)
ManifestBuilder::getClassManifest($baseDir . '/' . $module, ManifestBuilder::getClassManifest($baseDir . '/' . $module, $excludedFolders, $classManifest);
$classManifest);
} else { } else {
// Include all directories which have an _config.php file but don't // Include all directories which have an _config.php file but don't
// have an _manifest_exclude file // have an _manifest_exclude file
$topLevel = scandir($baseDir); $topLevel = scandir($baseDir);
foreach($topLevel as $filename) { foreach($topLevel as $filename) {
if($filename[0] == '.') continue; if($filename[0] == '.') continue;
if($filename == 'themes') continue;
if(in_array($filename, $excludedFolders)) continue;
if(@is_dir("$baseDir/$filename") && if(@is_dir("$baseDir/$filename") &&
file_exists("$baseDir/$filename/_config.php") && file_exists("$baseDir/$filename/_config.php") &&
!file_exists("$baseDir/$filename/_manifest_exclude")) { !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 // Get themes
if(file_exists("$baseDir/themes")) { if(file_exists("$baseDir/themes")) {
$themeDirs = scandir("$baseDir/themes"); $themeDirs = scandir("$baseDir/themes");
@ -222,24 +204,22 @@ class ManifestBuilder {
if(substr($themeDir,0,1) == '.') continue; if(substr($themeDir,0,1) == '.') continue;
// The theme something_forum is understood as being a part of the theme something // The theme something_forum is understood as being a part of the theme something
$themeName = strtok($themeDir, '_'); $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 // Build class-info array from class manifest
ManifestBuilder::getTemplateManifest($baseDir, project(), $templateManifest, $cssManifest); $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"]["_TEMPLATE_MANIFEST"] = $templateManifest;
$manifestInfo["globals"]["_CSS_MANIFEST"] = $cssManifest; $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; return $manifestInfo;
} }
@ -251,7 +231,7 @@ class ManifestBuilder {
* @param string $folder The folder to traverse (recursively) * @param string $folder The folder to traverse (recursively)
* @param array $classMap The already built class map * @param array $classMap The already built class map
*/ */
private static function getClassManifest($folder, &$classMap) { private static function getClassManifest($folder, $excludedFolders, &$classMap) {
$items = scandir($folder); $items = scandir($folder);
if($items) foreach($items as $item) { if($items) foreach($items as $item) {
// Skip some specific PHP files // 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($item == 'lang' && @is_dir("$folder/$item") && ereg_replace("/[^/]+/\\.\\.","",$folder.'/..') == Director::baseFolder()) continue;
if(@is_dir("$folder/$item")) { 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) // recurse into directories (if not in $ignore_folders)
ManifestBuilder::getClassManifest("$folder/$item", $classMap); ManifestBuilder::getClassManifest("$folder/$item", $excludedFolders, $classMap);
} else { } else {
// include item in the manifest // include item in the manifest
$itemCode = substr($item,0,-4); $itemCode = substr($item,0,-4);
@ -307,7 +290,7 @@ class ManifestBuilder {
* Generates the template manifest - a list of all the .SS files in the * Generates the template manifest - a list of all the .SS files in the
* application * 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"); $items = scandir("$baseDir/$folder");
if($items) foreach($items as $item) { if($items) foreach($items as $item) {
if(substr($item,0,1) == '.') continue; if(substr($item,0,1) == '.') continue;
@ -334,7 +317,10 @@ class ManifestBuilder {
} else if(@is_dir("$baseDir/$folder/$item")) { } 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 * Include everything, so that actually *all* classes are available and
* build a map of classes and their subclasses and the information if * build a map of classes and their subclasses
* the class has a database table
* *
* @param $classManifest An array of all Sapphire classes; keys are class names and values are filenames * @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 * @return array Returns an array that holds all class relevant
* information. * information.
*/ */
private static function allClasses($classManifest, $tables = null) { private static function allClasses($classManifest) {
self::$classArray = array(); self::$classArray = array();
self::$extendsArray = array(); self::$extendsArray = array();
self::$implementsArray = array(); self::$implementsArray = array();
@ -369,7 +353,6 @@ class ManifestBuilder {
foreach(self::$classArray as $class => $info) { foreach(self::$classArray as $class => $info) {
$allClasses['exists'][$class] = $class; $allClasses['exists'][$class] = $class;
if(isset($tables[strtolower($class)])) $allClasses['hastable'][$class] = $class;
} }
// Build a map of classes and their subclasses // Build a map of classes and their subclasses
@ -377,7 +360,6 @@ class ManifestBuilder {
foreach($_classes as $class) { foreach($_classes as $class) {
$allClasses['exists'][$class] = $class; $allClasses['exists'][$class] = $class;
if(isset($tables[strtolower($class)])) $allClasses['hastable'][$class] = $class;
foreach($_classes as $subclass) { foreach($_classes as $subclass) {
if(is_subclass_of($class, $subclass)) $allClasses['parents'][$class][$subclass] = $subclass; if(is_subclass_of($class, $subclass)) $allClasses['parents'][$class][$subclass] = $subclass;
if(is_subclass_of($subclass, $class)) $allClasses['children'][$class][$subclass] = $subclass; if(is_subclass_of($subclass, $class)) $allClasses['children'][$class][$subclass] = $subclass;
@ -387,7 +369,7 @@ class ManifestBuilder {
return $allClasses; return $allClasses;
} }
/** /**
* Parses a php file and adds any class or interface information into self::$classArray * Parses a php file and adds any class or interface information into self::$classArray
* *
* @param string $filename * @param string $filename
@ -449,6 +431,8 @@ class ManifestBuilder {
* @return TokenisedRegularExpression * @return TokenisedRegularExpression
*/ */
public static function getClassDefParser() { public static function getClassDefParser() {
require_once('core/TokenisedRegularExpression.php');
return new TokenisedRegularExpression(array( return new TokenisedRegularExpression(array(
0 => T_CLASS, 0 => T_CLASS,
1 => T_WHITESPACE, 1 => T_WHITESPACE,
@ -474,6 +458,8 @@ class ManifestBuilder {
* @return TokenisedRegularExpression * @return TokenisedRegularExpression
*/ */
public static function getInterfaceDefParser() { public static function getInterfaceDefParser() {
require_once('core/TokenisedRegularExpression.php');
return new TokenisedRegularExpression(array( return new TokenisedRegularExpression(array(
0 => T_INTERFACE, 0 => T_INTERFACE,
1 => T_WHITESPACE, 1 => T_WHITESPACE,
@ -550,68 +536,6 @@ class ManifestBuilder {
} }
return $results;; 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() { function init() {
// Test and development sites should be secured, via basic-auth // 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"); BasicAuth::requireLogin("SilverStripe test website. Use your CMS login", "ADMIN");
} }
// //
Cookie::set("PastVisitor", true); Cookie::set("PastVisitor", true);
// ClassInfo::hasTable() called to ensure that we're not in a very-first-setup stage // Directly access the session variable just in case the Group or Member tables don't yet exist
if(ClassInfo::hasTable("Group") && ClassInfo::hasTable("Member") && ($member = Member::currentUser())) { if(Session::get('loggedInAs')) {
$member = Member::currentUser();
Cookie::set("PastMember", true); Cookie::set("PastMember", true);
DB::query("UPDATE Member SET LastVisited = NOW() WHERE ID = $member->ID", null); DB::query("UPDATE Member SET LastVisited = NOW() WHERE ID = $member->ID", null);
} }

View File

@ -546,7 +546,8 @@ class Director {
* @return boolean * @return boolean
*/ */
public static function is_cli() { 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 // Use ?isDev=1 to get development access on the live server
if(isset($_GET['isDev'])) { if(isset($_GET['isDev'])) {
if(ClassInfo::ready()) { if(Security::database_is_ready()) {
BasicAuth::requireLogin("SilverStripe developer access. Use your CMS login", "ADMIN"); BasicAuth::requireLogin("SilverStripe developer access. Use your CMS login", "ADMIN");
$_SESSION['isDev'] = $_GET['isDev']; $_SESSION['isDev'] = $_GET['isDev'];
} else { } else {

View File

@ -693,7 +693,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
// No changes made // No changes made
if($this->changed) { if($this->changed) {
foreach($this->getClassAncestry() as $ancestor) { foreach($this->getClassAncestry() as $ancestor) {
if(ClassInfo::hasTable($ancestor)) if(self::has_own_table($ancestor))
$ancestry[] = $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 // 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]['command'] = $dbCommand;
$manipulation[$class]['id'] = $this->record['ID']; $manipulation[$class]['id'] = $this->record['ID'];
} else { } 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); user_error("$this->class has a broken onBeforeDelete() function. Make sure that you call parent::onBeforeDelete().", E_USER_ERROR);
} }
foreach($this->getClassAncestry() as $ancestor) { foreach($this->getClassAncestry() as $ancestor) {
if(ClassInfo::hastable($ancestor)) { if(self::has_own_table($ancestor)) {
$sql = new SQLQuery(); $sql = new SQLQuery();
$sql->delete = true; $sql->delete = true;
$sql->from[$ancestor] = "`$ancestor`"; $sql->from[$ancestor] = "`$ancestor`";
@ -1748,6 +1748,26 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
return isset($fieldMap[$field]) ? strtok($fieldMap[$field],'(') : null; 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. * Returns true if the member is allowed to do the given action.
* *

View File

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

View File

@ -51,6 +51,7 @@ class TestRunner extends Controller {
function init() { function init() {
parent::init(); parent::init();
ManifestBuilder::load_test_manifest();
if (!self::$default_reporter) self::set_reporter(Director::is_cli() ? 'CliDebugView' : 'DebugView'); if (!self::$default_reporter) self::set_reporter(Director::is_cli() ? 'CliDebugView' : 'DebugView');
} }
@ -95,7 +96,7 @@ class TestRunner extends Controller {
function coverage() { function coverage() {
if(hasPhpUnit()) { if(hasPhpUnit()) {
ManifestBuilder::includeEverything(); ManifestBuilder::load_all_classes();
$tests = ClassInfo::subclassesFor('SapphireTest'); $tests = ClassInfo::subclassesFor('SapphireTest');
array_shift($tests); array_shift($tests);
unset($tests['FunctionalTest']); 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_SubTeam WHERE SubclassDatabaseField = 'asdfasdf'")->value());
$this->assertEquals("1001", DB::query("SELECT ID FROM DataObjectTest_Team WHERE Title = '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 { 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 { class DataObjectTest_Team_Decorator extends DataObjectDecorator implements TestOnly {
function extraStatics() { function extraStatics() {

View File

@ -6,7 +6,7 @@
class ManifestBuilderTest extends SapphireTest { class ManifestBuilderTest extends SapphireTest {
function testManifest() { function testManifest() {
$baseFolder = TEMP_FOLDER . '/manifest-test'; $baseFolder = TEMP_FOLDER . '/manifest-test';
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder, DB::getConn()->tableList()); $manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
global $project; global $project;
$this->assertEquals("$baseFolder/sapphire/MyClass.php", $manifestInfo['globals']['_CLASS_MANIFEST']['MyClass']); $this->assertEquals("$baseFolder/sapphire/MyClass.php", $manifestInfo['globals']['_CLASS_MANIFEST']['MyClass']);
@ -36,42 +36,37 @@ class ManifestBuilderTest extends SapphireTest {
$baseFolder = TEMP_FOLDER . '/manifest-test'; $baseFolder = TEMP_FOLDER . '/manifest-test';
global $project; 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. */ /* 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']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $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']['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. */ /* 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']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $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']['parents']));
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['hastable']));
} }
function testManifestIgnoresClassesInStrings() { function testManifestIgnoresClassesInStrings() {
$baseFolder = TEMP_FOLDER . '/manifest-test'; $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 */ /* 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']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $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']['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. */ /* 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']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $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']['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. */ /* 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']['_CLASS_MANIFEST']));
$this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists'])); $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']['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 'test1c' => 'And now for a string test' // string test
); );
$connection->setQueryString($test1params); $connection->setQueryString($test1params);
$test1 = $connection->request('RestfulServiceTest_Controller')->getBody(); $test1 = $connection->request('RestfulServiceTest_Controller?usetestmanifest=1')->getBody();
foreach ($test1params as $key => $value) { foreach ($test1params as $key => $value) {
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $test1); $this->assertContains("<request_item name=\"$key\">$value</request_item>", $test1);
$this->assertContains("<get_item name=\"$key\">$value</get_item>", $test1); $this->assertContains("<get_item name=\"$key\">$value</get_item>", $test1);
@ -20,7 +20,7 @@ class RestfulServiceTest extends SapphireTest {
'test2b' => '%\'"@?=;:/,$', // special character checks 'test2b' => '%\'"@?=;:/,$', // special character checks
'test2c' => 'And now for a string test', // string test 'test2c' => 'And now for a string test', // string test
); );
$test2suburl = 'RestfulServiceTest_Controller/?'; $test2suburl = 'RestfulServiceTest_Controller/?usetestmanifest=1&';
foreach ($test2params as $key=>$value) { foreach ($test2params as $key=>$value) {
$test2suburl .= "$key=$value&"; $test2suburl .= "$key=$value&";
} }
@ -46,7 +46,7 @@ class RestfulServiceTest extends SapphireTest {
'test1b' => mt_rand(), 'test1b' => mt_rand(),
'test1c' => 'And now for a string test' '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) { foreach ($test1params as $key => $value) {
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $test1); $this->assertContains("<request_item name=\"$key\">$value</request_item>", $test1);
$this->assertContains("<post_item name=\"$key\">$value</post_item>", $test1); $this->assertContains("<post_item name=\"$key\">$value</post_item>", $test1);