mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Extract statics via code analysis rather than introspection
This commit is contained in:
parent
c98621977c
commit
6b986cb17d
@ -181,6 +181,13 @@ class Config {
|
|||||||
* where value is a config value suppress from any lower priority item */
|
* where value is a config value suppress from any lower priority item */
|
||||||
protected $suppresses = array();
|
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 */
|
/** @var [array] - The list of settings pulled from config files to search through */
|
||||||
protected $manifests = array();
|
protected $manifests = array();
|
||||||
|
|
||||||
@ -190,7 +197,7 @@ class Config {
|
|||||||
* WARNING: Config manifests to not merge entries, and do not solve before/after rules inter-manifest -
|
* 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
|
* 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);
|
array_unshift($this->manifests, $manifest->yamlConfig);
|
||||||
$this->cache->clean();
|
$this->cache->clean();
|
||||||
|
|
||||||
@ -384,9 +391,6 @@ class Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then look at the static variables
|
|
||||||
$nothing = new stdClass();
|
|
||||||
|
|
||||||
$sources = array($class);
|
$sources = array($class);
|
||||||
|
|
||||||
// Include extensions only if not flagged not to, and some have been set
|
// Include extensions only if not flagged not to, and some have been set
|
||||||
@ -401,9 +405,18 @@ class Config {
|
|||||||
if ($extraSources) $sources = array_merge($sources, $extraSources);
|
if ($extraSources) $sources = array_merge($sources, $extraSources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$value = $nothing = null;
|
||||||
|
|
||||||
foreach ($sources as $staticSource) {
|
foreach ($sources as $staticSource) {
|
||||||
if (is_array($staticSource)) $value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
|
if (is_array($staticSource)) {
|
||||||
else $value = Object::static_lookup($staticSource, $name, $nothing);
|
$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) {
|
if ($value !== $nothing) {
|
||||||
self::merge_low_into_high($result, $value, $suppress);
|
self::merge_low_into_high($result, $value, $suppress);
|
||||||
@ -412,8 +425,10 @@ class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally, merge in the values from the parent class
|
// Finally, merge in the values from the parent class
|
||||||
if (($sourceOptions & self::UNINHERITED) != self::UNINHERITED
|
if (
|
||||||
&& (($sourceOptions & self::FIRST_SET) != self::FIRST_SET || $result === null)) {
|
($sourceOptions & self::UNINHERITED) != self::UNINHERITED &&
|
||||||
|
(($sourceOptions & self::FIRST_SET) != self::FIRST_SET || $result === null)
|
||||||
|
) {
|
||||||
$parent = get_parent_class($class);
|
$parent = get_parent_class($class);
|
||||||
if ($parent) $this->getUncached($parent, $name, $sourceOptions, $result, $suppress, $tags);
|
if ($parent) $this->getUncached($parent, $name, $sourceOptions, $result, $suppress, $tags);
|
||||||
}
|
}
|
||||||
|
@ -288,9 +288,13 @@ if(file_exists(BASE_PATH . '/vendor/autoload.php')) {
|
|||||||
require_once 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
|
// Now that the class manifest is up, load the configuration
|
||||||
$configManifest = new SS_ConfigManifest(BASE_PATH, false, $flush);
|
$configManifest = new SS_ConfigManifest(BASE_PATH, false, $flush);
|
||||||
Config::inst()->pushConfigManifest($configManifest);
|
Config::inst()->pushConfigYamlManifest($configManifest);
|
||||||
|
|
||||||
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
|
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
|
||||||
BASE_PATH, project(), false, isset($_GET['flush'])
|
BASE_PATH, project(), false, isset($_GET['flush'])
|
||||||
|
309
core/manifest/ConfigStaticManifest.php
Normal file
309
core/manifest/ConfigStaticManifest.php
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
<?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',
|
||||||
|
'RequestHandler', 'Controller', 'ContentController',
|
||||||
|
'DataObject', 'SiteTree', 'Extension', 'DataExtension', 'Hierarchy', 'Versioned'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
$this->statics[$class] = $this->cache->load($this->key.'_'.sha1($class));
|
||||||
|
|
||||||
|
if (!isset($this->statics[$class])) {
|
||||||
|
$this->handleFile(null, $info['path'], null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->statics[$class] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$statics = $this->statics;
|
||||||
|
|
||||||
|
if (isset($statics[$class]) && $statics[$class] && array_key_exists($name, $statics[$class])) {
|
||||||
|
if ($statics[$class][$name]['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
|
||||||
|
$this->statics[$class][$name]['access'] = T_PRIVATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $statics[$class][$name]['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely regenerates the manifest file.
|
||||||
|
*/
|
||||||
|
public function regenerate($cache = true) {
|
||||||
|
$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);
|
||||||
|
|
||||||
|
$index = array('$statics' => array());
|
||||||
|
|
||||||
|
foreach ($this->statics as $class => $details) {
|
||||||
|
$this->cache->save($details, $this->key.'_'.sha1($class));
|
||||||
|
|
||||||
|
$index[$class] = array(
|
||||||
|
'path' => $details['path'],
|
||||||
|
'mtime' => filemtime($details['path'])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (in_array($class, self::$initial_classes)) {
|
||||||
|
$index['$statics'][$class] = $details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($cache) {
|
||||||
|
$this->cache->save($index, $this->key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleFile($basename, $pathname, $depth) {
|
||||||
|
$parser = new SS_ConfigStaticManifest_Parser($pathname);
|
||||||
|
$this->statics = array_merge($this->statics, $parser->parse());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 $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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->statics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
echo "What's this?\n";
|
||||||
|
print_r($token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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->statics[$class])) {
|
||||||
|
$this->statics[$class] = array(
|
||||||
|
'path' => $this->path,
|
||||||
|
'statics' => array()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->statics[$class][$variable] = array(
|
||||||
|
'access' => $access,
|
||||||
|
'value' => eval('return '.$value.';')
|
||||||
|
);
|
||||||
|
|
||||||
|
if($token == ',') $this->parseStatic($access, $class);
|
||||||
|
}
|
||||||
|
}
|
@ -88,6 +88,10 @@ class TestRunner extends Controller {
|
|||||||
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
|
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
|
||||||
BASE_PATH, project(), true, isset($_GET['flush'])
|
BASE_PATH, project(), true, isset($_GET['flush'])
|
||||||
));
|
));
|
||||||
|
|
||||||
|
Config::inst()->pushConfigStaticManifest(new SS_ConfigStaticManifest(
|
||||||
|
BASE_PATH, true, isset($_GET['flush'])
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function init() {
|
public function init() {
|
||||||
|
Loading…
Reference in New Issue
Block a user