2013-02-26 04:44:48 +01:00
< ? php
2015-12-29 07:18:10 +01:00
/**
* Allows access to config values set on classes using private statics .
*
* @ package framework
* @ subpackage manifest
*/
2016-01-05 11:05:04 +01:00
class SS_ConfigStaticManifest_Reflection extends SS_ConfigStaticManifest {
2015-12-29 07:18:10 +01:00
/**
* 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 ) {
// Stubbed as these parameters are not needed for the newer SS_ConficStaticManifest version.
}
/**
* Completely regenerates the manifest file .
*/
public function regenerate ( $cache = true ) {
2016-01-05 11:05:04 +01:00
Deprecation :: notice ( '3.4' , 'This is no longer available as SS_ConfigStaticManifest now uses Reflection. For backwards compatibility define SS_USE_OLD_CONFIGSTATICMANIFEST in your _ss_environment.php file.' );
2015-12-29 07:18:10 +01:00
}
/**
* @ param string $class
* @ param string $name
* @ param null $default
*
* @ return mixed | null
*/
public function get ( $class , $name , $default = null ) {
if ( class_exists ( $class )) {
// The config system is case-sensitive so we need to check the exact value
$reflection = new ReflectionClass ( $class );
if ( strcmp ( $reflection -> name , $class ) === 0 ) {
if ( $reflection -> hasProperty ( $name )) {
$property = $reflection -> getProperty ( $name );
if ( $property -> isStatic ()) {
if ( ! $property -> isPrivate ()) {
2016-01-05 11:05:04 +01:00
Deprecation :: notice ( '3.4' , " Config static $class :: \$ $name must be marked as private " ,
2015-12-29 07:18:10 +01:00
Deprecation :: SCOPE_GLOBAL );
return null ;
}
$property -> setAccessible ( true );
return $property -> getValue ();
}
}
}
}
return null ;
}
public function getStatics () {
2016-01-05 11:05:04 +01:00
Deprecation :: notice ( '3.4' , 'This is no longer available as SS_ConfigStaticManifest now uses Reflection. For backwards compatibility define SS_USE_OLD_CONFIGSTATICMANIFEST in your _ss_environment.php file.' );
2015-12-29 07:18:10 +01:00
return array ();
}
}
2013-02-26 04:44:48 +01:00
/**
* 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 )
*
2013-11-29 05:12:47 +01:00
* @ package framework
2013-02-26 04:44:48 +01:00
* @ subpackage manifest
*/
class SS_ConfigStaticManifest {
protected $base ;
protected $tests ;
protected $cache ;
protected $key ;
protected $index ;
protected $statics ;
static protected $initial_classes = array (
2013-03-03 21:25:23 +01:00
'Object' , 'ViewableData' , 'Injector' , 'Director'
2013-02-26 04:44:48 +01:00
);
/**
* 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 ;
2013-03-13 22:33:29 +01:00
$cacheClass = defined ( 'SS_MANIFESTCACHE' ) ? SS_MANIFESTCACHE : 'ManifestCache_File' ;
2013-02-26 04:44:48 +01:00
2013-03-13 22:33:29 +01:00
$this -> cache = new $cacheClass ( 'staticmanifest' . ( $includeTests ? '_tests' : '' ));
$this -> key = sha1 ( $base );
2013-02-26 04:44:48 +01:00
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 ];
2013-03-03 21:25:23 +01:00
2013-03-12 05:14:12 +01:00
if ( isset ( $info [ 'key' ]) && $details = $this -> cache -> load ( $this -> key . '_' . $info [ 'key' ])) {
2013-03-03 21:25:23 +01:00
$this -> statics += $details ;
}
2013-02-26 04:44:48 +01:00
if ( ! isset ( $this -> statics [ $class ])) {
$this -> handleFile ( null , $info [ 'path' ], null );
}
}
else {
$this -> statics [ $class ] = false ;
}
}
2013-03-03 21:25:23 +01:00
if ( isset ( $this -> statics [ $class ][ $name ])) {
$static = $this -> statics [ $class ][ $name ];
2013-02-26 04:44:48 +01:00
2013-03-03 21:25:23 +01:00
if ( $static [ 'access' ] != T_PRIVATE ) {
2015-06-19 01:59:27 +02:00
Deprecation :: notice ( '4.0' , " Config static $class :: \$ $name must be marked as private " ,
2013-06-26 05:49:00 +02:00
Deprecation :: SCOPE_GLOBAL );
2013-02-26 04:44:48 +01:00
// Don't warn more than once per static
2013-03-22 02:26:48 +01:00
$this -> statics [ $class ][ $name ][ 'access' ] = T_PRIVATE ;
2013-02-26 04:44:48 +01:00
}
2013-03-03 21:25:23 +01:00
return $static [ 'value' ];
2013-02-26 04:44:48 +01:00
}
return $default ;
}
/**
* Completely regenerates the manifest file .
*/
public function regenerate ( $cache = true ) {
2013-03-12 05:14:12 +01:00
$this -> index = array ( '$statics' => array ());
2013-02-26 04:44:48 +01:00
$this -> statics = array ();
$finder = new ManifestFileFinder ();
$finder -> setOptions ( array (
'name_regex' => '/^([^_].*\.php)$/' ,
2013-07-08 10:41:46 +02:00
'ignore_files' => array ( 'index.php' , 'main.php' , 'cli-script.php' , 'SSTemplateParser.php' ),
2013-02-26 04:44:48 +01:00
'ignore_tests' => ! $this -> tests ,
'file_callback' => array ( $this , 'handleFile' )
));
$finder -> find ( $this -> base );
2013-03-03 21:25:23 +01:00
if ( $cache ) {
2013-03-12 04:52:11 +01:00
$keysets = array ();
2013-02-26 04:44:48 +01:00
2013-03-03 21:25:23 +01:00
foreach ( $this -> statics as $class => $details ) {
if ( in_array ( $class , self :: $initial_classes )) {
2013-03-12 05:14:12 +01:00
$this -> index [ '$statics' ][ $class ] = $details ;
2013-03-03 21:25:23 +01:00
}
else {
2013-03-12 04:52:11 +01:00
$key = sha1 ( $class );
2013-03-12 05:14:12 +01:00
$this -> index [ $class ][ 'key' ] = $key ;
2013-03-03 21:25:23 +01:00
2013-03-12 05:14:12 +01:00
$keysets [ $key ][ $class ] = $details ;
2013-03-03 21:25:23 +01:00
}
}
2013-02-26 04:44:48 +01:00
2013-03-12 04:52:11 +01:00
foreach ( $keysets as $key => $details ) {
$this -> cache -> save ( $details , $this -> key . '_' . $key );
2013-02-26 04:44:48 +01:00
}
2013-03-12 05:14:12 +01:00
$this -> cache -> save ( $this -> index , $this -> key );
2013-02-26 04:44:48 +01:00
}
}
public function handleFile ( $basename , $pathname , $depth ) {
$parser = new SS_ConfigStaticManifest_Parser ( $pathname );
2013-03-12 05:14:12 +01:00
$parser -> parse ();
$this -> index = array_merge ( $this -> index , $parser -> getInfo ());
$this -> statics = array_merge ( $this -> statics , $parser -> getStatics ());
2013-02-26 04:44:48 +01:00
}
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 )
2013-11-29 05:12:47 +01:00
*
* @ package framework
* @ subpackage manifest
2013-02-26 04:44:48 +01:00
*/
class SS_ConfigStaticManifest_Parser {
2013-03-12 05:14:12 +01:00
protected $info = array ();
2013-02-26 04:44:48 +01:00
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 ;
}
2013-03-12 05:14:12 +01:00
function getInfo () {
return $this -> info ;
}
function getStatics () {
return $this -> statics ;
}
2013-02-26 04:44:48 +01:00
/**
* Get the next token to process , incrementing the pointer
*
* @ param bool $ignoreWhitespace - if true will skip any whitespace tokens & only return non - whitespace ones
2014-05-05 12:55:59 +02:00
* @ return null | mixed - Either the next token or null if there isn ' t one
2013-02-26 04:44:48 +01:00
*/
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 ;
}
2016-07-07 13:14:54 +02:00
/**
* Get the previous token processed . Does * not * decrement the pointer
*
* @ param bool $ignoreWhitespace - if true will skip any whitespace tokens & only return non - whitespace ones
* @ return null | mixed - Either the previous token or null if there isn ' t one
*/
protected function lastToken ( $ignoreWhitespace = true ) {
// Subtract 1 as the pointer is always 1 place ahead of the current token
$pos = $this -> pos - 1 ;
do {
if ( $pos <= 0 ) return null ;
$pos -- ;
$prev = $this -> tokens [ $pos ];
}
while ( $ignoreWhitespace && is_array ( $prev ) && $prev [ 0 ] == T_WHITESPACE );
return $prev ;
}
2014-05-05 12:55:59 +02:00
/**
* 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 ;
}
}
2013-02-26 04:44:48 +01:00
/**
* 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 ()) {
2015-12-05 09:39:37 +01:00
$type = ( $token === ( array ) $token ) ? $token [ 0 ] : $token ;
2013-02-26 04:44:48 +01:00
if ( $type == T_CLASS ) {
2016-07-07 13:14:54 +02:00
$lastToken = $this -> lastToken ();
$lastType = ( $lastToken === ( array ) $lastToken ) ? $lastToken [ 0 ] : $lastToken ;
2016-07-07 11:23:38 +02:00
// Ignore class keyword if it's being used for class name resolution: ClassName::class
if ( $lastType === T_PAAMAYIM_NEKUDOTAYIM ) {
continue ;
}
2014-05-05 12:55:59 +02:00
$next = $this -> nextString ();
if ( $next === null ) {
2013-02-26 04:44:48 +01:00
user_error ( " Couldn \ 't parse { $this -> path } when building config static manifest " , E_USER_ERROR );
}
2014-05-05 12:55:59 +02:00
$class = $next ;
2013-02-26 04:44:48 +01:00
}
else if ( $type == T_NAMESPACE ) {
2013-06-26 05:49:00 +02:00
$namespace = '' ;
while ( true ) {
$next = $this -> next ();
if ( $next == ';' ) {
break ;
} elseif ( $next [ 0 ] == T_NS_SEPARATOR ) {
$namespace .= $next [ 1 ];
$next = $this -> next ();
}
2014-05-05 12:55:59 +02:00
if ( ! is_string ( $next ) && $next [ 0 ] != T_STRING ) {
2013-06-26 05:49:00 +02:00
user_error ( " Couldn \ 't parse { $this -> path } when building config static manifest " , E_USER_ERROR );
}
2014-05-05 12:55:59 +02:00
$namespace .= is_string ( $next ) ? $next : $next [ 1 ];
2013-02-26 04:44:48 +01:00
}
}
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 ;
2013-10-01 04:02:11 +02:00
if ( $depth < 0 ) user_error ( " Hmm - depth calc wrong, hit negatives, see: " . $this -> path , E_USER_ERROR );
2013-02-26 04:44:48 +01:00
}
else if ( $type == T_PUBLIC || $type == T_PRIVATE || $type == T_PROTECTED ) {
$access = $type ;
}
2013-03-22 02:26:48 +01:00
else if ( $type == T_STATIC && $class && $depth == $clsdepth ) {
$this -> parseStatic ( $access , $namespace ? $namespace . '\\' . $class : $class );
$access = 0 ;
2013-02-26 04:44:48 +01:00
}
else {
2013-03-22 02:26:48 +01:00
$access = 0 ;
2013-02-26 04:44:48 +01:00
}
}
}
/**
* 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 ()) {
2015-12-05 09:39:37 +01:00
$type = ( $token === ( array ) $token ) ? $token [ 0 ] : $token ;
2013-02-26 04:44:48 +01:00
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 ;
}
2013-03-12 23:59:49 +01:00
else if ( $type == T_COMMENT || $type == T_DOC_COMMENT ) {
2013-03-12 23:26:49 +01:00
// NOP
}
2013-02-26 04:44:48 +01:00
else {
2014-03-30 08:51:38 +02:00
user_error ( 'Unexpected token ("' . token_name ( $type ) . '") when building static manifest in class "'
. $class . '": ' . print_r ( $token , true ), E_USER_ERROR );
2013-02-26 04:44:48 +01:00
}
}
if ( $token == '=' ) {
$depth = 0 ;
2015-12-05 09:39:37 +01:00
while ( $token = ( $this -> pos >= $this -> length ) ? null : $this -> tokens [ $this -> pos ++ ]) {
$type = ( $token === ( array ) $token ) ? $token [ 0 ] : $token ;
2013-02-26 04:44:48 +01:00
// Track array nesting depth
2013-05-05 02:19:31 +02:00
if ( $type == T_ARRAY || $type == '[' ) {
2013-02-26 04:44:48 +01:00
$depth += 1 ;
2013-05-05 02:19:31 +02:00
} elseif ( $type == ')' || $type == ']' ) {
2013-02-26 04:44:48 +01:00
$depth -= 1 ;
}
2013-06-26 05:49:00 +02:00
// Parse out the assignment side of a static declaration,
// ending on either a ';' or a ',' outside an array
2013-02-26 04:44:48 +01:00
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 {
2015-12-05 09:39:37 +01:00
$value .= ( $token === ( array ) $token ) ? $token [ 1 ] : $token ;
2013-02-26 04:44:48 +01:00
}
}
}
2013-03-12 05:14:12 +01:00
if ( ! isset ( $this -> info [ $class ])) {
$this -> info [ $class ] = array (
2013-02-26 04:44:48 +01:00
'path' => $this -> path ,
2013-03-12 05:14:12 +01:00
'mtime' => filemtime ( $this -> path ),
2013-02-26 04:44:48 +01:00
);
}
2013-03-12 05:14:12 +01:00
if ( ! isset ( $this -> statics [ $class ])) {
$this -> statics [ $class ] = array ();
}
2013-03-13 00:42:48 +01:00
$value = trim ( $value );
if ( $value ) {
$value = eval ( 'static $temp = ' . $value . " ; \n " . 'return $temp' . " ; \n " );
}
else {
$value = null ;
}
2013-02-26 04:44:48 +01:00
$this -> statics [ $class ][ $variable ] = array (
'access' => $access ,
2013-03-13 00:42:48 +01:00
'value' => $value
2013-02-26 04:44:48 +01:00
);
if ( $token == ',' ) $this -> parseStatic ( $access , $class );
}
2013-05-05 02:19:31 +02:00
}