This commit is contained in:
James Cocker 2013-03-12 23:08:48 +00:00
commit df6d1564a1
11 changed files with 759 additions and 80 deletions

View File

@ -298,6 +298,8 @@ class RestfulService extends ViewableData {
protected function extractResponse($ch, $rawResponse) {
//get the status code
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
//get a curl error if there is one
$curlError = curl_error($ch);
//normalise the status code
if(curl_error($ch) !== '' || $statusCode == 0) $statusCode = 500;
//calculate the length of the header and extract it
@ -588,7 +590,7 @@ class RestfulService_Response extends SS_HTTPResponse {
$this->cachedResponse = new RestfulService_Response($content);
}
else {
$this->cachedResponse->setBody = $content;
$this->cachedResponse->setBody($content);
}
}

View File

@ -164,12 +164,43 @@ class Config {
}
/**
* Empty construction, otherwise calling singleton('Config') (not the right way to get the current active config
* instance, but people might) gives an error
* Make the newly active Config be a copy of the current active Config instance.
*
* You can then make changes to the configuration by calling update and remove on the new
* value returned by Config::inst(), and then discard those changes later by calling unnest
*/
static public function nest() {
$current = self::$instance;
$new = clone $current;
$new->nestedFrom = $current;
self::set_instance($new);
}
/**
* Change the active Config back to the Config instance the current active Config object
* was copied from
*/
static public function unnest() {
self::set_instance(self::$instance->nestedFrom);
}
protected $cache;
/**
* Each copy of the Config object need's it's own cache, so changes don't leak through to other instances
*/
public function __construct() {
$this->cache = new Config_LRU();
}
public function __clone() {
$this->cache = clone $this->cache;
}
/** @var Config - The config instance this one was copied from when Config::nest() was called */
protected $nestedFrom = null;
/** @var [array] - Array of arrays. Each member is an nested array keyed as $class => $name => $value,
* where value is a config value to treat as the highest priority item */
protected $overrides = array();
@ -178,6 +209,13 @@ class Config {
* where value is a config value suppress from any lower priority item */
protected $suppresses = array();
protected $staticManifests = array();
public function pushConfigStaticManifest(SS_ConfigStaticManifest $manifest) {
array_unshift($this->staticManifests, $manifest);
$this->cache->clean();
}
/** @var [array] - The list of settings pulled from config files to search through */
protected $manifests = array();
@ -187,8 +225,9 @@ class Config {
* WARNING: Config manifests to not merge entries, and do not solve before/after rules inter-manifest -
* instead, the last manifest to be added always wins
*/
public function pushConfigManifest(SS_ConfigManifest $manifest) {
public function pushConfigYamlManifest(SS_ConfigManifest $manifest) {
array_unshift($this->manifests, $manifest->yamlConfig);
$this->cache->clean();
// @todo: Do anything with these. They're for caching after config.php has executed
$this->collectConfigPHPSettings = true;
@ -342,6 +381,89 @@ class Config {
return $res;
}
protected $extraConfigSources = array();
public function extraConfigSourcesChanged($class) {
unset($this->extraConfigSources[$class]);
$this->cache->clean("__{$class}");
}
protected function getUncached($class, $name, $sourceOptions, &$result, $suppress, &$tags) {
$tags[] = "__{$class}";
$tags[] = "__{$class}__{$name}";
// If result is already not something to merge into, just return it
if ($result !== null && !is_array($result)) return $result;
// First, look through the override values
foreach($this->overrides as $k => $overrides) {
if (isset($overrides[$class][$name])) {
$value = $overrides[$class][$name];
self::merge_low_into_high($result, $value, $suppress);
if ($result !== null && !is_array($result)) return $result;
}
if (isset($this->suppresses[$k][$class][$name])) {
$suppress = $suppress
? array_merge($suppress, $this->suppresses[$k][$class][$name])
: $this->suppresses[$k][$class][$name];
}
}
// Then the manifest values
foreach($this->manifests as $manifest) {
if (isset($manifest[$class][$name])) {
self::merge_low_into_high($result, $manifest[$class][$name], $suppress);
if ($result !== null && !is_array($result)) return $result;
}
}
$sources = array($class);
// Include extensions only if not flagged not to, and some have been set
if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) {
// If we don't have a fresh list of extra sources, get it from the class itself
if (!array_key_exists($class, $this->extraConfigSources)) {
$this->extraConfigSources[$class] = Object::get_extra_config_sources($class);
}
// Update $sources with any extra sources
$extraSources = $this->extraConfigSources[$class];
if ($extraSources) $sources = array_merge($sources, $extraSources);
}
$value = $nothing = null;
foreach ($sources as $staticSource) {
if (is_array($staticSource)) {
$value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
}
else {
foreach ($this->staticManifests as $i => $statics) {
$value = $statics->get($staticSource, $name, $nothing);
if ($value !== $nothing) break;
}
}
if ($value !== $nothing) {
self::merge_low_into_high($result, $value, $suppress);
if ($result !== null && !is_array($result)) return $result;
}
}
// Finally, merge in the values from the parent class
if (
($sourceOptions & self::UNINHERITED) != self::UNINHERITED &&
(($sourceOptions & self::FIRST_SET) != self::FIRST_SET || $result === null)
) {
$parent = get_parent_class($class);
if ($parent) $this->getUncached($parent, $name, $sourceOptions, $result, $suppress, $tags);
}
return $result;
}
/**
* Get the config value associated for a given class and property
*
@ -370,62 +492,14 @@ class Config {
* sequential array or scalar depending on value (see class docblock)
*/
public function get($class, $name, $sourceOptions = 0, &$result = null, $suppress = null) {
// If result is already not something to merge into, just return it
if ($result !== null && !is_array($result)) return $result;
// Have we got a cached value? Use it if so
$key = $class.$name.$sourceOptions;
// First, look through the override values
foreach($this->overrides as $k => $overrides) {
if (isset($overrides[$class][$name])) {
$value = $overrides[$class][$name];
self::merge_low_into_high($result, $value, $suppress);
if ($result !== null && !is_array($result)) return $result;
}
if (isset($this->suppresses[$k][$class][$name])) {
$suppress = $suppress
? array_merge($suppress, $this->suppresses[$k][$class][$name])
: $this->suppresses[$k][$class][$name];
}
}
// Then the manifest values
foreach($this->manifests as $manifest) {
if (isset($manifest[$class][$name])) {
self::merge_low_into_high($result, $manifest[$class][$name], $suppress);
if ($result !== null && !is_array($result)) return $result;
}
}
// Then look at the static variables
$nothing = new stdClass();
$sources = array($class);
// Include extensions only if not flagged not to, and some have been set
if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) {
$extraSources = Object::get_extra_config_sources($class);
if ($extraSources) $sources = array_merge($sources, $extraSources);
}
foreach ($sources as $staticSource) {
if (is_array($staticSource)) $value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
else $value = Object::static_lookup($staticSource, $name, $nothing);
if ($value !== $nothing) {
self::merge_low_into_high($result, $value, $suppress);
if ($result !== null && !is_array($result)) return $result;
}
}
// Finally, merge in the values from the parent class
if (($sourceOptions & self::UNINHERITED) != self::UNINHERITED
&& (($sourceOptions & self::FIRST_SET) != self::FIRST_SET || $result === null)) {
$parent = get_parent_class($class);
if ($parent) $this->get($parent, $name, $sourceOptions, $result, $suppress);
}
if ($name == 'routes') {
print_r($result); die;
if (($result = $this->cache->get($key)) === false) {
$tags = array();
$result = null;
$this->getUncached($class, $name, $sourceOptions, $result, $suppress, $tags);
$this->cache->set($key, $result, $tags);
}
return $result;
@ -452,6 +526,8 @@ class Config {
if (!isset($this->overrides[0][$class][$name])) $this->overrides[0][$class][$name] = $val;
else self::merge_high_into_low($this->overrides[0][$class][$name], $val);
$this->cache->clean("__{$class}__{$name}");
}
/**
@ -512,6 +588,91 @@ class Config {
}
class Config_LRU {
const SIZE = 1000;
protected $cache;
protected $indexing;
protected $i = 0;
protected $c = 0;
public function __construct() {
$this->cache = new SplFixedArray(self::SIZE);
// Pre-fill with stdClass instances. By reusing we avoid object-thrashing
for ($i = 0; $i < self::SIZE; $i++) {
$this->cache[$i] = new stdClass();
$this->cache[$i]->key = null;
}
$this->indexing = array();
}
public function set($key, $val, $tags = array()) {
// Find an index to set at
$replacing = null;
// Target count - not always the lowest, but guaranteed to exist (or hit an empty item)
$target = $this->c - self::SIZE + 1;
$i = $stop = $this->i;
do {
if (!($i--)) $i = self::SIZE-1;
$item = $this->cache[$i];
if ($item->key === null) { $replacing = null; break; }
else if ($item->c <= $target) { $replacing = $item; break; }
}
while ($i != $stop);
if ($replacing) unset($this->indexing[$replacing->key]);
$this->indexing[$key] = $this->i = $i;
$obj = $this->cache[$i];
$obj->key = $key;
$obj->value = $val;
$obj->tags = $tags;
$obj->c = ++$this->c;
}
private $hit = 0;
private $miss = 0;
public function stats() {
return $this->miss ? ($this->hit / $this->miss) : 0;
}
public function get($key) {
if (isset($this->indexing[$key])) {
$this->hit++;
$res = $this->cache[$this->indexing[$key]];
$res->c = ++$this->c;
return $res->value;
}
$this->miss++;
return false;
}
public function clean($tag = null) {
if ($tag) {
foreach ($this->cache as $i => $v) {
if ($v->key !== null && in_array($tag, $v->tags)) {
unset($this->indexing[$v->key]);
$this->cache[$i]->key = null;
}
}
}
else {
for ($i = 0; $i < self::SIZE; $i++) $this->cache[$i]->key = null;
$this->indexing = array();
}
}
}
class Config_ForClass {
protected $class;

View File

@ -288,9 +288,13 @@ if(file_exists(BASE_PATH . '/vendor/autoload.php')) {
require_once BASE_PATH . '/vendor/autoload.php';
}
// Now that the class manifest is up, load the configuration
$configManifest = new SS_ConfigStaticManifest(BASE_PATH, false, $flush);
Config::inst()->pushConfigStaticManifest($configManifest);
// Now that the class manifest is up, load the configuration
$configManifest = new SS_ConfigManifest(BASE_PATH, false, $flush);
Config::inst()->pushConfigManifest($configManifest);
Config::inst()->pushConfigYamlManifest($configManifest);
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
BASE_PATH, project(), false, isset($_GET['flush'])

View File

@ -52,23 +52,12 @@ abstract class Object {
*/
public $class;
/**
* @todo Set this via dependancy injection? Can't call it $config, because too many clashes with form elements etc
* @var Config_ForClass
*/
private $_config_forclass = null;
/**
* Get a configuration accessor for this class. Short hand for Config::inst()->get($this->class, .....).
* @return Config_ForClass|null
*/
public function config() {
if (!$this->_config_forclass) {
$this->_config_forclass = Config::inst()->forClass($this->class);
}
return $this->_config_forclass;
static public function config() {
return Config::inst()->forClass(get_called_class());
}
/**
@ -494,10 +483,11 @@ abstract class Object {
if($subclasses) foreach($subclasses as $subclass) {
unset(self::$classes_constructed[$subclass]);
unset(self::$extra_methods[$subclass]);
unset(self::$extension_sources[$subclass]);
}
Config::inst()->update($class, 'extensions', array($extension));
Config::inst()->extraConfigSourcesChanged($class);
Injector::inst()->unregisterAllObjects();
// load statics now for DataObject classes
@ -534,6 +524,7 @@ abstract class Object {
}
Config::inst()->remove($class, 'extensions', Config::anything(), $extension);
Config::inst()->extraConfigSourcesChanged($class);
// unset singletons to avoid side-effects
Injector::inst()->unregisterAllObjects();
@ -544,7 +535,6 @@ abstract class Object {
if($subclasses) foreach($subclasses as $subclass) {
unset(self::$classes_constructed[$subclass]);
unset(self::$extra_methods[$subclass]);
unset(self::$extension_sources[$subclass]);
}
}
@ -571,9 +561,6 @@ abstract class Object {
// --------------------------------------------------------------------------------------------------------------
private static $extension_sources = array();
// Don't bother checking some classes that should never be extended
private static $unextendable_classes = array('Object', 'ViewableData', 'RequestHandler');
static public function get_extra_config_sources($class = null) {
@ -582,9 +569,6 @@ abstract class Object {
// If this class is unextendable, NOP
if(in_array($class, self::$unextendable_classes)) return;
// If we have a pre-cached version, use that
if(array_key_exists($class, self::$extension_sources)) return self::$extension_sources[$class];
// Variable to hold sources in
$sources = null;
@ -615,7 +599,7 @@ abstract class Object {
}
}
return self::$extension_sources[$class] = $sources;
return $sources;
}
public function __construct() {

View File

@ -0,0 +1,325 @@
<?php
/**
* A utility class which builds a manifest of the statics defined in all classes, along with their
* access levels and values
*
* We use this to make the statics that the Config system uses as default values be truely immutable.
*
* It has the side effect of allowing Config to avoid private-level access restrictions, so we can
* optionally catch attempts to modify the config statics (otherwise the modification will appear
* to work, but won't actually have any effect - the equvilent of failing silently)
*
* @subpackage manifest
*/
class SS_ConfigStaticManifest {
protected $base;
protected $tests;
protected $cache;
protected $key;
protected $index;
protected $statics;
static protected $initial_classes = array(
'Object', 'ViewableData', 'Injector', 'Director'
);
/**
* Constructs and initialises a new config static manifest, either loading the data
* from the cache or re-scanning for classes.
*
* @param string $base The manifest base path.
* @param bool $includeTests Include the contents of "tests" directories.
* @param bool $forceRegen Force the manifest to be regenerated.
* @param bool $cache If the manifest is regenerated, cache it.
*/
public function __construct($base, $includeTests = false, $forceRegen = false, $cache = true) {
$this->base = $base;
$this->tests = $includeTests;
$this->cache = SS_Cache::factory('SS_ConfigStatics', 'Core', array(
'automatic_serialization' => true,
'lifetime' => null
));
$this->key = 'sc_'.sha1($base . ($includeTests ? '!tests' : ''));
if(!$forceRegen) {
$this->index = $this->cache->load($this->key);
}
if($this->index) {
$this->statics = $this->index['$statics'];
}
else {
$this->regenerate($cache);
}
}
public function get($class, $name, $default) {
if (!isset($this->statics[$class])) {
if (isset($this->index[$class])) {
$info = $this->index[$class];
if (isset($info['key']) && $details = $this->cache->load($this->key.'_'.$info['key'])) {
$this->statics += $details;
}
if (!isset($this->statics[$class])) {
$this->handleFile(null, $info['path'], null);
}
}
else {
$this->statics[$class] = false;
}
}
if (isset($this->statics[$class][$name])) {
$static = $this->statics[$class][$name];
if ($static['access'] != T_PRIVATE) {
Deprecation::notice('3.1.0', "Config static $class::\$$name must be marked as private", Deprecation::SCOPE_GLOBAL);
// Don't warn more than once per static
$static['access'] = T_PRIVATE;
}
return $static['value'];
}
return $default;
}
/**
* Completely regenerates the manifest file.
*/
public function regenerate($cache = true) {
$this->index = array('$statics' => array());
$this->statics = array();
$finder = new ManifestFileFinder();
$finder->setOptions(array(
'name_regex' => '/^([^_].*\.php)$/',
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
'ignore_tests' => !$this->tests,
'file_callback' => array($this, 'handleFile')
));
$finder->find($this->base);
if($cache) {
$keysets = array();
foreach ($this->statics as $class => $details) {
if (in_array($class, self::$initial_classes)) {
$this->index['$statics'][$class] = $details;
}
else {
$key = sha1($class);
$this->index[$class]['key'] = $key;
$keysets[$key][$class] = $details;
}
}
foreach ($keysets as $key => $details) {
$this->cache->save($details, $this->key.'_'.$key);
}
$this->cache->save($this->index, $this->key);
}
}
public function handleFile($basename, $pathname, $depth) {
$parser = new SS_ConfigStaticManifest_Parser($pathname);
$parser->parse();
$this->index = array_merge($this->index, $parser->getInfo());
$this->statics = array_merge($this->statics, $parser->getStatics());
}
public function getStatics() {
return $this->statics;
}
}
/**
* A parser that processes a PHP file, using PHP's built in parser to get a string of tokens,
* then processing them to find the static class variables, their access levels & values
*
* We can't do this using TokenisedRegularExpression because we need to keep track of state
* as we process the token list (when we enter and leave a namespace or class, when we see
* an access level keyword, etc)
*/
class SS_ConfigStaticManifest_Parser {
protected $info = array();
protected $statics = array();
protected $path;
protected $tokens;
protected $length;
protected $pos;
function __construct($path) {
$this->path = $path;
$file = file_get_contents($path);
$this->tokens = token_get_all($file);
$this->length = count($this->tokens);
$this->pos = 0;
}
function getInfo() {
return $this->info;
}
function getStatics() {
return $this->statics;
}
/**
* Get the next token to process, incrementing the pointer
*
* @param bool $ignoreWhitespace - if true will skip any whitespace tokens & only return non-whitespace ones
* @return null | int - Either the next token or null if there isn't one
*/
protected function next($ignoreWhitespace = true) {
do {
if($this->pos >= $this->length) return null;
$next = $this->tokens[$this->pos++];
}
while($ignoreWhitespace && is_array($next) && $next[0] == T_WHITESPACE);
return $next;
}
/**
* Parse the given file to find the static variables declared in it, along with their access & values
*/
function parse() {
$depth = 0; $namespace = null; $class = null; $clsdepth = null; $access = 0;
while($token = $this->next()) {
$type = is_array($token) ? $token[0] : $token;
if($type == T_CLASS) {
$next = $this->next();
if($next[0] != T_STRING) {
user_error("Couldn\'t parse {$this->path} when building config static manifest", E_USER_ERROR);
}
$class = $next[1];
}
else if($type == T_NAMESPACE) {
$next = $this->next();
if($next[0] != T_STRING) {
user_error("Couldn\'t parse {$this->path} when building config static manifest", E_USER_ERROR);
}
$namespace = $next[1];
}
else if($type == '{' || $type == T_CURLY_OPEN || $type == T_DOLLAR_OPEN_CURLY_BRACES){
$depth += 1;
if($class && !$clsdepth) $clsdepth = $depth;
}
else if($type == '}') {
$depth -= 1;
if($depth < $clsdepth) $class = $clsdepth = null;
if($depth < 0) user_error("Hmm - depth calc wrong, hit negatives", E_USER_ERROR);
}
else if($type == T_PUBLIC || $type == T_PRIVATE || $type == T_PROTECTED) {
$access = $type;
}
else if($type == T_STATIC) {
if($class && $depth == $clsdepth) $this->parseStatic($access, $namespace ? $namespace.'\\'.$class : $class);
}
else {
$access = '';
}
}
}
/**
* During parsing we've found a "static" keyword. Parse out the variable names and value
* assignments that follow.
*
* Seperated out from parse partially so that we can recurse if there are multiple statics
* being declared in a comma seperated list
*/
function parseStatic($access, $class) {
$variable = null;
$value = '';
while($token = $this->next()) {
$type = is_array($token) ? $token[0] : $token;
if($type == T_PUBLIC || $type == T_PRIVATE || $type == T_PROTECTED) {
$access = $type;
}
else if($type == T_FUNCTION) {
return;
}
else if($type == T_VARIABLE) {
$variable = substr($token[1], 1); // Cut off initial "$"
}
else if($type == ';' || $type == ',' || $type == '=') {
break;
}
else {
user_error('Unexpected token when building static manifest: '.print_r($token, true), E_USER_ERROR);
}
}
if($token == '=') {
$depth = 0;
while($token = $this->next(false)){
$type = is_array($token) ? $token[0] : $token;
// Track array nesting depth
if($type == T_ARRAY) {
$depth += 1;
}
else if($type == ')') {
$depth -= 1;
}
// Parse out the assignment side of a static declaration, ending on either a ';' or a ',' outside an array
if($type == T_WHITESPACE) {
$value .= ' ';
}
else if($type == ';' || ($type == ',' && !$depth)) {
break;
}
// Statics can reference class constants with self:: (and that won't work in eval)
else if($type == T_STRING && $token[1] == 'self') {
$value .= $class;
}
else {
$value .= is_array($token) ? $token[1] : $token;
}
}
}
if (!isset($this->info[$class])) {
$this->info[$class] = array(
'path' => $this->path,
'mtime' => filemtime($this->path),
);
}
if(!isset($this->statics[$class])) {
$this->statics[$class] = array();
}
$this->statics[$class][$variable] = array(
'access' => $access,
'value' => eval('return '.$value.';')
);
if($token == ',') $this->parseStatic($access, $class);
}
}

View File

@ -88,6 +88,10 @@ class TestRunner extends Controller {
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
BASE_PATH, project(), true, isset($_GET['flush'])
));
Config::inst()->pushConfigStaticManifest(new SS_ConfigStaticManifest(
BASE_PATH, true, isset($_GET['flush'])
));
}
public function init() {

View File

@ -30,6 +30,23 @@
## Upgrading
### Static configuration properties are now immutable, you must use Config API.
A common SilverStripe pattern is to use a static variable on a class to define a configuration parameter.
The configuration system added in SilverStripe 3.0 builds on this by using this static variable as a way
of defining the default value.
In SilverStripe 3.0, it was possible to edit this value at run-time and have the change propagate into the
configuration system. This is no longer the case, for performance reasons.
Many of the configuration variables have been change to "private" so that attempts to change them throw an
error, but if you do have a configuration static that is able to be changed, and you change it, then the
configuration system will silently ignore it.
Please change all run-time manipulation of configuration to use `Config::inst()->update()` or
`$this->config()->update()`. For more information about how to use the config system, see the
["Configuration" topic](/topic/configuration).
### Deny URL access if `Controller::$allowed_actions` is undefined or empty array
In order to make controller access checks more consistent and easier to

View File

@ -7,7 +7,7 @@
* @package framework
* @subpackage email
*/
class Mailer {
class Mailer extends Object {
/**
* Send a plain-text email.

View File

@ -164,4 +164,54 @@ class ConfigTest extends SapphireTest {
$this->markTestIncomplete();
}
public function testLRUDiscarding() {
$cache = new ConfigTest_Config_LRU();
for ($i = 0; $i < Config_LRU::SIZE*2; $i++) $cache->set($i, $i);
$this->assertEquals(
Config_LRU::SIZE, count($cache->indexing),
'Homogenous usage gives exact discarding'
);
$cache = new ConfigTest_Config_LRU();
for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set($i, $i);
for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set(-1, -1);
$this->assertLessThan(
Config_LRU::SIZE, count($cache->indexing),
'Heterogenous usage gives sufficient discarding'
);
}
public function testLRUCleaning() {
$cache = new ConfigTest_Config_LRU();
for ($i = 0; $i < Config_LRU::SIZE; $i++) $cache->set($i, $i);
$this->assertEquals(Config_LRU::SIZE, count($cache->indexing));
$cache->clean();
$this->assertEquals(0, count($cache->indexing), 'Clean clears all items');
$this->assertFalse($cache->get(1), 'Clean clears all items');
$cache->set(1, 1, array('Foo'));
$this->assertEquals(1, count($cache->indexing));
$cache->clean('Foo');
$this->assertEquals(0, count($cache->indexing), 'Clean items with matching tag');
$this->assertFalse($cache->get(1), 'Clean items with matching tag');
$cache->set(1, 1, array('Foo', 'Bar'));
$this->assertEquals(1, count($cache->indexing));
$cache->clean('Bar');
$this->assertEquals(0, count($cache->indexing), 'Clean items with any single matching tag');
$this->assertFalse($cache->get(1), 'Clean items with any single matching tag');
}
}
class ConfigTest_Config_LRU extends Config_LRU {
public $cache;
public $indexing;
}

View File

@ -0,0 +1,123 @@
<?php
class ConfigStaticManifestTest extends SapphireTest {
/* Example statics */
// Different access levels
static $nolevel;
public static $public;
protected static $protected;
private static $private;
static public $public2;
static protected $protected2;
static private $private2;
// Assigning values
static $snone;
static $snull = null;
static $sint = 1;
static $sfloat = 2.5;
static $sstring = 'string';
static $sarray = array(1, 2, array(3, 4), 5);
// Assigning multiple values
static $onone, $onull = null, $oint = 1, $ofloat = 2.5, $ostring = 'string', $oarray = array(1, 2, array(3, 4), 5);
static
$mnone,
$mnull = null,
$mint = 1,
$mfloat = 2.5,
$mstring = 'string',
$marray = array(
1, 2,
array(3, 4),
5
);
// Should ignore static methpds
static function static_method() {}
// Should ignore method statics
function instanceMethod() {
static $method_static;
}
/* The tests */
protected function parseSelf() {
static $statics = null;
if ($statics === null) {
$parser = new SS_ConfigStaticManifest_Parser(__FILE__);
$parser->parse();
}
return $parser;
}
public function testParsingAccessLevels() {
$statics = $this->parseSelf()->getStatics();
$levels = array(
'nolevel' => null,
'public' => T_PUBLIC,
'public2' => T_PUBLIC,
'protected' => T_PROTECTED,
'protected2' => T_PROTECTED,
'private' => T_PRIVATE,
'private2' => T_PRIVATE
);
foreach($levels as $var => $level) {
$this->assertEquals(
$level,
$statics[__CLASS__][$var]['access'],
'Variable '.$var.' has '.($level ? token_name($level) : 'no').' access level'
);
}
}
public function testParsingValues() {
$statics = $this->parseSelf()->getStatics();
// Check assigning values
$values = array(
'none',
'null',
'int',
'float',
'string',
'array',
);
$prepends = array(
's', // Each on it's own
'o', // All on one line
'm' // All in on static statement, but each on own line
);
foreach ($values as $value) {
foreach ($prepends as $prepend) {
$var = "$prepend$value";
$this->assertEquals(
self::$$var,
$statics[__CLASS__][$var]['value'],
'Variable '.$var.' value is extracted properly'
);
}
}
}
public function testIgnoresMethodStatics() {
$statics = $this->parseSelf()->getStatics();
$this->assertNull(@$statics[__CLASS__]['method_static']);
}
public function testIgnoresStaticMethods() {
$statics = $this->parseSelf()->getStatics();
$this->assertNull(@$statics[__CLASS__]['static_method']);
}
}

View File

@ -43,8 +43,10 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
// Table will have been initially created by the $extraDataObjects setting
// Let's insert a new field here
$oldDB = DataObjectSchemaGenerationTest_DO::$db;
DataObjectSchemaGenerationTest_DO::$db['SecretField'] = 'Varchar(100)';
Config::nest();
Config::inst()->update('DataObjectSchemaGenerationTest_DO', 'db', array(
'SecretField' => 'Varchar(100)'
));
// Verify that the above extra field triggered a schema update
$db->beginSchemaUpdate();
@ -55,7 +57,7 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
$this->assertTrue($needsUpdating);
// Restore db configuration
DataObjectSchemaGenerationTest_DO::$db = $oldDB;
Config::unnest();
}
/**
@ -76,8 +78,11 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
$this->assertFalse($needsUpdating);
// Test with alternate index format, although these indexes are the same
$oldIndexes = DataObjectSchemaGenerationTest_IndexDO::$indexes;
DataObjectSchemaGenerationTest_IndexDO::$indexes = DataObjectSchemaGenerationTest_IndexDO::$indexes_alt;
Config::nest();
Config::inst()->remove('DataObjectSchemaGenerationTest_IndexDO', 'indexes');
Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes',
Config::inst()->get('DataObjectSchemaGenerationTest_IndexDO', 'indexes_alt')
);
// Verify that it still doesn't need to be recreated
$db->beginSchemaUpdate();
@ -88,7 +93,7 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
$this->assertFalse($needsUpdating);
// Restore old index format
DataObjectSchemaGenerationTest_IndexDO::$indexes = $oldIndexes;
Config::unnest();
}
/**
@ -101,8 +106,12 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
// Table will have been initially created by the $extraDataObjects setting
// Update the SearchFields index here
$oldIndexes = DataObjectSchemaGenerationTest_IndexDO::$indexes;
DataObjectSchemaGenerationTest_IndexDO::$indexes['SearchFields']['value'] = '"Title"';
Config::nest();
Config::inst()->update('DataObjectSchemaGenerationTest_IndexDO', 'indexes', array(
'SearchFields' => array(
'value' => 'Title'
)
));
// Verify that the above index change triggered a schema update
$db->beginSchemaUpdate();
@ -113,7 +122,7 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
$this->assertTrue($needsUpdating);
// Restore old indexes
DataObjectSchemaGenerationTest_IndexDO::$indexes = $oldIndexes;
Config::unnest();
}
}