From 9f91b4782513b703926a9ff90d6248bf4dc3059d Mon Sep 17 00:00:00 2001 From: micmania1 Date: Fri, 1 May 2015 01:04:55 +0000 Subject: [PATCH] NEW Update SS_ConfigStaticManifest to use Reflection --- core/Core.php | 2 +- core/manifest/ConfigStaticManifest.php | 389 ++---------------- model/DataQuery.php | 2 + .../manifest/ConfigStaticManifestTest.php | 269 +----------- 4 files changed, 42 insertions(+), 620 deletions(-) diff --git a/core/Core.php b/core/Core.php index 05c8c7615..aecfe4cd3 100644 --- a/core/Core.php +++ b/core/Core.php @@ -112,7 +112,7 @@ if(file_exists(BASE_PATH . '/vendor/autoload.php')) { } // Now that the class manifest is up, load the static configuration -$configManifest = new SS_ConfigStaticManifest(BASE_PATH, false, $flush); +$configManifest = new SS_ConfigStaticManifest(); Config::inst()->pushConfigStaticManifest($configManifest); // And then the yaml configuration diff --git a/core/manifest/ConfigStaticManifest.php b/core/manifest/ConfigStaticManifest.php index ba488a5b7..817159daa 100644 --- a/core/manifest/ConfigStaticManifest.php +++ b/core/manifest/ConfigStaticManifest.php @@ -1,385 +1,42 @@ base = $base; - $this->tests = $includeTests; + public function get($class, $name, $default = null) { + if(class_exists($class)) { - $cacheClass = defined('SS_MANIFESTCACHE') ? SS_MANIFESTCACHE : 'ManifestCache_File'; + // The config system is case-sensitive so we need to check the exact value + $reflection = new ReflectionClass($class); + if(strcmp($reflection->name, $class) === 0) { - $this->cache = new $cacheClass('staticmanifest'.($includeTests ? '_tests' : '')); - $this->key = sha1($base); - - 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($reflection->hasProperty($name)) { + $property = $reflection->getProperty($name); + if($property->isStatic()) { + if(!$property->isPrivate()) { + Deprecation::notice('4.0', "Config static $class::\$$name must be marked as private", + Deprecation::SCOPE_GLOBAL); + return null; + } + $property->setAccessible(true); + return $property->getValue(); + } } - 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('4.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 $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', 'SSTemplateParser.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) - * - * @package framework - * @subpackage manifest - */ -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 | mixed - 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; - } - - /** - * Get the next set of tokens that form a string to process, - * incrementing the pointer - * - * @param bool $ignoreWhitespace - if true will skip any whitespace tokens - * & only return non-whitespace ones - * @return null|string - Either the next string or null if there isn't one - */ - protected function nextString($ignoreWhitespace = true) { - static $stop = array('{', '}', '(', ')', '[', ']'); - - $string = ''; - while ($this->pos < $this->length) { - $next = $this->tokens[$this->pos]; - if (is_string($next)) { - if (!in_array($next, $stop)) { - $string .= $next; - } else { - break; - } - } else if ($next[0] == T_STRING) { - $string .= $next[1]; - } else if ($next[0] != T_WHITESPACE || !$ignoreWhitespace) { - break; - } - $this->pos++; - } - if ($string === '') { - return null; - } else { - return $string; - } - } - - /** - * 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->nextString(); - if($next === null) { - user_error("Couldn\'t parse {$this->path} when building config static manifest", E_USER_ERROR); - } - - $class = $next; - } - else if($type == T_NAMESPACE) { - $namespace = ''; - while(true) { - $next = $this->next(); - - if($next == ';') { - break; - } elseif($next[0] == T_NS_SEPARATOR) { - $namespace .= $next[1]; - $next = $this->next(); - } - - if(!is_string($next) && $next[0] != T_STRING) { - user_error("Couldn\'t parse {$this->path} when building config static manifest", E_USER_ERROR); - } - - $namespace .= is_string($next) ? $next : $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, see: ".$this->path, E_USER_ERROR); - } - else if($type == T_PUBLIC || $type == T_PRIVATE || $type == T_PROTECTED) { - $access = $type; - } - else if($type == T_STATIC && $class && $depth == $clsdepth) { - $this->parseStatic($access, $namespace ? $namespace.'\\'.$class : $class); - $access = 0; - } - else { - $access = 0; - } - } - } - - /** - * 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 if($type == T_COMMENT || $type == T_DOC_COMMENT) { - // NOP - } - else { - user_error('Unexpected token ("' . token_name($type) . '") when building static manifest in class "' - . $class . '": '.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 || $type == '[') { - $depth += 1; - } elseif($type == ')' || $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(); - } - - $value = trim($value); - if ($value) { - $value = eval('static $temp = '.$value.";\n".'return $temp'.";\n"); - } - else { - $value = null; - } - - $this->statics[$class][$variable] = array( - 'access' => $access, - 'value' => $value - ); - - if($token == ',') $this->parseStatic($access, $class); + return null; } } diff --git a/model/DataQuery.php b/model/DataQuery.php index 4f22b2b27..c72af3f62 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -225,6 +225,8 @@ class DataQuery { } } + + // Resolve colliding fields if($this->collidingFields) { foreach($this->collidingFields as $k => $collisions) { diff --git a/tests/core/manifest/ConfigStaticManifestTest.php b/tests/core/manifest/ConfigStaticManifestTest.php index 3709bd384..834945504 100644 --- a/tests/core/manifest/ConfigStaticManifestTest.php +++ b/tests/core/manifest/ConfigStaticManifestTest.php @@ -2,268 +2,31 @@ class ConfigStaticManifestTest extends SapphireTest { - /* Example statics */ + private static $testString = 'string'; - // Different access levels - static $nolevel; - public static $public; - protected static $protected; - private static $private; - static public $public2; - static protected $protected2; - static private $private2; - static $nolevel_after_private; + private static $testArray = array('foo' => 'bar'); - // 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); - static $sheredoc = <<assertNull($manifest->get(__CLASS__, 'madeup', null)); + // Test string value + $this->assertEquals('string', $manifest->get(__CLASS__, 'testString')); - static /* Has comment inline */ $commented_int = 1, /* And here */ $commented_string = 'string'; + // Test array value + $this->assertEquals(array('foo' => 'bar'), $manifest->get(__CLASS__, 'testArray')); - static - /** - * Has docblock inline - */ - $docblocked_int = 1, - /** And here */ - $docblocked_string = 'string'; + // Test to ensure we're only picking up private statics + $this->assertNull($manifest->get(__CLASS__, 'ignored', null)); - // 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, - 'nolevel_after_private' => null - ); - - foreach($levels as $var => $level) { - $this->assertEquals( - $level, - $statics[__CLASS__][$var]['access'], - 'Variable '.$var.' has '.($level ? token_name($level) : 'no').' access level' - ); + // Test madeup class + if(!class_exists('aonsffgrgx')) { + $this->assertNull($manifest->get('aonsffgrgx', 'madeup', null)); } } - public function testParsingValues() { - $statics = $this->parseSelf()->getStatics(); - - // Check assigning values - $values = array( - 'none', - 'null', - 'int', - 'float', - 'string', - 'array', - 'heredoc', - 'nowdoc' - ); - - $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 testIgnoreComments() { - $statics = $this->parseSelf()->getStatics(); - - $this->assertEquals(self::$commented_int, $statics[__CLASS__]['commented_int']['value']); - $this->assertEquals(self::$commented_string, $statics[__CLASS__]['commented_string']['value']); - - $this->assertEquals(self::$docblocked_int, $statics[__CLASS__]['docblocked_int']['value']); - $this->assertEquals(self::$docblocked_string, $statics[__CLASS__]['docblocked_string']['value']); - } - - 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']); - } - - public function testParsingShortArray() { - if(version_compare(PHP_VERSION, '5.4', '<')) { - $this->markTestSkipped('This test requires PHP 5.4 or higher'); - return; - } - - $parser = new SS_ConfigStaticManifest_Parser(__DIR__ . - '/ConfigStaticManifestTest/ConfigStaticManifestTestMyObject.php'); - $parser->parse(); - - $statics = $parser->getStatics(); - - $expectedValue = array( - 'Name' => 'Varchar', - 'Description' => 'Text', - ); - - $this->assertEquals($expectedValue, $statics['ConfigStaticManifestTestMyObject']['db']['value']); - } - - public function testParsingNamespacesclass() { - $parser = new SS_ConfigStaticManifest_Parser(__DIR__ . - '/ConfigStaticManifestTest/ConfigStaticManifestTestNamespace.php'); - $parser->parse(); - - $statics = $parser->getStatics(); - - $expectedValue = array( - 'Name' => 'Varchar', - 'Description' => 'Text', - ); - - $this->assertEquals($expectedValue, $statics['config\staticmanifest\NamespaceTest']['db']['value']); - } - - public function testParsingMultyStringClass() { - static $tokens = array( - array(T_OPEN_TAG, "parse(); - - $statics = $parser->getStatics(); - - $expected = array( - 'test' => array( - 'access' => T_PRIVATE, - 'value' => array(3) - ) - ); - - $this->assertEquals($expected, $statics[':ss:test2']); - } -} - -class ConfigStaticManifestTest_Parser extends SS_ConfigStaticManifest_Parser implements TestOnly { - public function __construct($tokens) { - $this->path = __FILE__; - $this->tokens = $tokens; - $this->length = count($this->tokens); - $this->pos = 0; - } }