Completed
Push — master ( 1c3709...1468db )
by Daniel
11:48
created

Object::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
use SilverStripe\Framework\Core\Configurable;
4
use SilverStripe\Framework\Core\Extensible;
5
use SilverStripe\Framework\Core\Injectable;
6
7
/**
8
 * A base class for all SilverStripe objects to inherit from.
9
 *
10
 * This class provides a number of pattern implementations, as well as methods and fixes to add extra psuedo-static
11
 * and method functionality to PHP.
12
 *
13
 * See {@link Extension} on how to implement a custom multiple
14
 * inheritance for object instances based on PHP5 method call overloading.
15
 *
16
 * @todo Create instance-specific removeExtension() which removes an extension from $extension_instances,
17
 * but not from static $extensions, and clears everything added through defineMethods(), mainly $extra_methods.
18
 *
19
 * @package framework
20
 * @subpackage core
21
 */
22
abstract class Object {
23
	use Extensible;
24
	use Injectable;
25
	use Configurable;
26
27
	/**
28
	 * @var string the class name
29
	 */
30
	public $class;
31
32
	private static $_cache_inst_args = array();
33
34
	/**
35
	 * Create an object from a string representation.  It treats it as a PHP constructor without the
36
	 * 'new' keyword.  It also manages to construct the object without the use of eval().
37
	 *
38
	 * Construction itself is done with Object::create(), so that Object::useCustomClass() calls
39
	 * are respected.
40
	 *
41
	 * `Object::create_from_string("Versioned('Stage','Live')")` will return the result of
42
	 * `Versioned::create('Stage', 'Live);`
43
	 *
44
	 * It is designed for simple, cloneable objects.  The first time this method is called for a given
45
	 * string it is cached, and clones of that object are returned.
46
	 *
47
	 * If you pass the $firstArg argument, this will be prepended to the constructor arguments. It's
48
	 * impossible to pass null as the firstArg argument.
49
	 *
50
	 * `Object::create_from_string("Varchar(50)", "MyField")` will return the result of
51
	 * `Varchar::create('MyField', '50');`
52
	 *
53
	 * Arguments are always strings, although this is a quirk of the current implementation rather
54
	 * than something that can be relied upon.
55
	 *
56
	 * @param string $classSpec
57
	 * @param mixed $firstArg
58
	 * @return object
59
	 */
60
	public static function create_from_string($classSpec, $firstArg = null) {
61
		if(!isset(self::$_cache_inst_args[$classSpec.$firstArg])) {
62
			// an $extension value can contain parameters as a string,
63
			// e.g. "Versioned('Stage','Live')"
64
			if(strpos($classSpec, '(') === false) {
65
				if($firstArg === null) {
66
					self::$_cache_inst_args[$classSpec.$firstArg] = Object::create($classSpec);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
67
				} else {
68
					self::$_cache_inst_args[$classSpec.$firstArg] = Object::create($classSpec, $firstArg);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
69
				}
70
71
			} else {
72
				list($class, $args) = self::parse_class_spec($classSpec);
73
74
				if($firstArg !== null) {
75
					array_unshift($args, $firstArg);
76
				}
77
				array_unshift($args, $class);
78
79
				self::$_cache_inst_args[$classSpec.$firstArg] = call_user_func_array(array('Object','create'), $args);
80
			}
81
		}
82
83
		return clone self::$_cache_inst_args[$classSpec.$firstArg];
84
	}
85
86
	/**
87
	 * Parses a class-spec, such as "Versioned('Stage','Live')", as passed to create_from_string().
88
	 * Returns a 2-element array, with classname and arguments
89
	 *
90
	 * @param string $classSpec
91
	 * @return array
92
	 * @throws Exception
93
	 */
94
	public static function parse_class_spec($classSpec) {
95
		$tokens = token_get_all("<?php $classSpec");
96
		$class = null;
97
		$args = array();
98
99
		// Keep track of the current bucket that we're putting data into
100
		$bucket = &$args;
101
		$bucketStack = array();
102
		$had_ns = false;
103
104
		foreach($tokens as $token) {
105
			$tName = is_array($token) ? $token[0] : $token;
106
			// Get the class naem
107
			if($class == null && is_array($token) && $token[0] == T_STRING) {
108
				$class = $token[1];
109
			} elseif(is_array($token) && $token[0] == T_NS_SEPARATOR) {
110
				$class .= $token[1];
111
				$had_ns = true;
112
			} elseif ($had_ns && is_array($token) && $token[0] == T_STRING) {
113
				$class .= $token[1];
114
				$had_ns = false;
115
			// Get arguments
116
			} else if(is_array($token)) {
117
				switch($token[0]) {
118
				case T_CONSTANT_ENCAPSED_STRING:
119
					$argString = $token[1];
120
					switch($argString[0]) {
121
					case '"':
122
						$argString = stripcslashes(substr($argString,1,-1));
123
						break;
124
					case "'":
125
						$argString = str_replace(array("\\\\", "\\'"),array("\\", "'"), substr($argString,1,-1));
126
						break;
127
					default:
128
						throw new Exception("Bad T_CONSTANT_ENCAPSED_STRING arg $argString");
129
					}
130
					$bucket[] = $argString;
131
					break;
132
133
				case T_DNUMBER:
134
					$bucket[] = (double)$token[1];
135
					break;
136
137
				case T_LNUMBER:
138
					$bucket[] = (int)$token[1];
139
					break;
140
141
				case T_STRING:
142
					switch($token[1]) {
143
						case 'true': $bucket[] = true; break;
144
						case 'false': $bucket[] = false; break;
145
						case 'null': $bucket[] = null; break;
146
						default: throw new Exception("Bad T_STRING arg '{$token[1]}'");
147
					}
148
					break;
149
150
				case T_ARRAY:
151
					// Add an empty array to the bucket
152
					$bucket[] = array();
153
					$bucketStack[] = &$bucket;
154
					$bucket = &$bucket[sizeof($bucket)-1];
155
156
				}
157
158
			} else {
159
				if($tName == '[') {
160
					// Add an empty array to the bucket
161
					$bucket[] = array();
162
					$bucketStack[] = &$bucket;
163
					$bucket = &$bucket[sizeof($bucket)-1];
164
				} elseif($tName == ')' || $tName == ']') {
165
					// Pop-by-reference
166
					$bucket = &$bucketStack[sizeof($bucketStack)-1];
167
					array_pop($bucketStack);
168
				}
169
			}
170
		}
171
172
		return array($class, $args);
173
	}
174
175
	/**
176
	 * Get the value of a static property of a class, even in that property is declared protected (but not private),
177
	 * without any inheritance, merging or parent lookup if it doesn't exist on the given class.
178
	 *
179
	 * @static
180
	 * @param $class - The class to get the static from
181
	 * @param $name - The property to get from the class
182
	 * @param null $default - The value to return if property doesn't exist on class
183
	 * @return any - The value of the static property $name on class $class, or $default if that property is not
184
	 *               defined
185
	 */
186
	public static function static_lookup($class, $name, $default = null) {
187
		if (is_subclass_of($class, 'Object')) {
188
			if (isset($class::$$name)) {
189
				$parent = get_parent_class($class);
190
				if (!$parent || !isset($parent::$$name) || $parent::$$name !== $class::$$name) return $class::$$name;
191
			}
192
			return $default;
193
		} else {
194
			// TODO: This gets set once, then not updated, so any changes to statics after this is called the first
195
			// time for any class won't be exposed
196
			static $static_properties = array();
197
198
			if (!isset($static_properties[$class])) {
199
				$reflection = new ReflectionClass($class);
200
				$static_properties[$class] = $reflection->getStaticProperties();
201
			}
202
203
			if (isset($static_properties[$class][$name])) {
204
				$value = $static_properties[$class][$name];
205
206
				$parent = get_parent_class($class);
207
				if (!$parent) return $value;
208
209
				if (!isset($static_properties[$parent])) {
210
					$reflection = new ReflectionClass($parent);
211
					$static_properties[$parent] = $reflection->getStaticProperties();
212
				}
213
214
				if (!isset($static_properties[$parent][$name]) || $static_properties[$parent][$name] !== $value) {
215
					return $value;
216
				}
217
			}
218
		}
219
220
		return $default;
221
	}
222
223
	public function __construct() {
224
		$this->class = get_class($this);
225
		$this->constructExtensions();
226
	}
227
228
	// --------------------------------------------------------------------------------------------------------------
229
230
	/**
231
	 * Return true if this object "exists" i.e. has a sensible value
232
	 *
233
	 * This method should be overriden in subclasses to provide more context about the classes state. For example, a
234
	 * {@link DataObject} class could return false when it is deleted from the database
235
	 *
236
	 * @return bool
237
	 */
238
	public function exists() {
239
		return true;
240
	}
241
242
	/**
243
	 * @return string this classes parent class
244
	 */
245
	public function parentClass() {
246
		return get_parent_class($this);
247
	}
248
249
	/**
250
	 * Check if this class is an instance of a specific class, or has that class as one of its parents
251
	 *
252
	 * @param string $class
253
	 * @return bool
254
	 */
255
	public function is_a($class) {
256
		return $this instanceof $class;
257
	}
258
259
	/**
260
	 * @return string the class name
261
	 */
262
	public function __toString() {
263
		return $this->class;
264
	}
265
266
}
267