faces.inc
10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<?php
/**
* @file Extendable Object Faces API. Provided by the faces module.
*/
if (!interface_exists('FacesExtenderInterface', FALSE)) {
/**
* Interface for extenders.
*/
interface FacesExtenderInterface {
/**
* Constructs an instance of the extender.
*/
function __construct(FacesExtendable $object);
/**
* Returns the extended object.
*/
public function getExtendable();
}
/**
* The Exception thrown by the FacesExtendable.
*/
class FacesExtendableException extends ErrorException {}
}
if (!class_exists('FacesExtender', FALSE)) {
/**
* A common base class for FacesExtenders. Extenders may access protected
* methods and properties of the extendable using the property() and call()
* methods.
*/
abstract class FacesExtender implements FacesExtenderInterface {
/**
* @var FacesExtendable
*/
protected $object;
function __construct(FacesExtendable $object) {
$this->object = $object;
}
/**
* Returns the extended object.
*/
public function getExtendable() {
return $this->object;
}
/**
* Makes protected properties of the extendable accessible.
*/
protected function &property($name) {
$var =& $this->object->property($name);
return $var;
}
/**
* Invokes any method on the extended object. May be used to invoke
* protected methods.
*
* @param $name
* The method name.
* @param $arguments
* An array of arguments to pass to the method.
*/
protected function call($name, array $args = array()) {
return $this->object->call($name, $args);
}
}
}
if (!class_exists('FacesExtendable', FALSE)) {
/**
* An extendable base class.
*/
abstract class FacesExtendable {
protected $facesMethods = array();
protected $faces = array();
protected $facesIncludes = array();
protected $facesClassInstances = array();
static protected $facesIncluded = array();
/**
* Wraps calls to module_load_include() to prevent multiple inclusions.
*
* @see module_load_include()
*/
protected static function load_include($args) {
$args += array('type' => 'inc', 'module' => '', 'name' => NULL);
$key = implode(':', $args);
if (!isset(self::$facesIncluded[$key])) {
self::$facesIncluded[$key] = TRUE;
module_load_include($args['type'], $args['module'], $args['name']);
}
}
/**
* Magic method: Invoke the dynamically implemented methods.
*/
function __call($name, $arguments = array()) {
if (isset($this->facesMethods[$name])) {
$method = $this->facesMethods[$name];
// Include code, if necessary.
if (isset($this->facesIncludes[$name])) {
self::load_include($this->facesIncludes[$name]);
$this->facesIncludes[$name] = NULL;
}
if (isset($method[0])) {
// We always pass the object reference and the name of the method.
$arguments[] = $this;
$arguments[] = $name;
return call_user_func_array($method[0], $arguments);
}
// Call the method on the extender object, but don't use extender()
// for performance reasons.
if (!isset($this->facesClassInstances[$method[1]])) {
$this->facesClassInstances[$method[1]] = new $method[1]($this);
}
return call_user_func_array(array($this->facesClassInstances[$method[1]], $name), $arguments);
}
$class = check_plain(get_class($this));
throw new FacesExtendableException("There is no method $name for this instance of the class $class.");
}
/**
* Returns the extender object for the given class. May be used to
* explicitly invoke a specific extender, e.g. a function overriding a
* method may use that to explicitly invoke the original extender.
*/
public function extender($class) {
if (!isset($this->facesClassInstances[$class])) {
$this->facesClassInstances[$class] = new $class($this);
}
return $this->facesClassInstances[$class];
}
/**
* Returns whether the object can face as the given interface, thus it
* returns TRUE if this oject has been extended by an appropriate
* implementation.
*
* @param $interface
* Optional. A interface to test for. If it's omitted, all interfaces that
* the object can be faced as are returned.
* @return
* Whether the object can face as the interface or an array of interface
* names.
*/
public function facesAs($interface = NULL) {
if (!isset($interface)) {
return array_values($this->faces);
}
return in_array($interface, $this->faces) || $this instanceof $interface;
}
/**
* Extend the object by a class to implement the given interfaces.
*
* @param $interface
* The interface name or an array of interface names.
* @param $class
* The extender class, which has to implement the FacesExtenderInterface.
* @param $include
* An optional array describing the file to include before invoking the
* class. The array entries known are 'type', 'module', and 'name'
* matching the parameters of module_load_include(). Only 'module' is
* required as 'type' defaults to 'inc' and 'name' to NULL.
*/
public function extendByClass($interface, $className, array $includes = array()) {
$parents = class_implements($className);
if (!in_array('FacesExtenderInterface', $parents)) {
throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the FacesExtenderInterface.");
}
$interfaces = is_array($interface) ? $interface : array($interface);
foreach ($interfaces as $interface) {
if (!in_array($interface, $parents)) {
throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the interface " . check_plain($interface) . ".");
}
$this->faces[$interface] = $interface;
$this->faces += class_implements($interface);
$face_methods = get_class_methods($interface);
$this->addIncludes($face_methods, $includes);
foreach ($face_methods as $method) {
$this->facesMethods[$method] = array(1 => $className);
}
}
}
/**
* Extend the object by the given functions to implement the given
* interface. There has to be an implementation function for each method of
* the interface.
*
* @param $interface
* The interface name or FALSE to extend the object without a given
* interface.
* @param $methods
* An array, where the keys are methods of the given interface and the
* values the callback functions to use.
* @param $includes
* An optional array to describe files to include before invoking the
* callbacks. You may pass a single array describing one include for all
* callbacks or an array of arrays, keyed by the method names. Look at the
* extendByClass() $include parameter for more details about how to
* describe a single file.
*/
public function extend($interface, array $callbacks = array(), array $includes = array()) {
$face_methods = $interface ? get_class_methods($interface) : array_keys($callbacks);
if ($interface) {
if (array_diff($face_methods, array_keys($callbacks))) {
throw new FacesExtendableException("Missing methods for implementing the interface " . check_plain($interface) . ".");
}
$this->faces[$interface] = $interface;
$this->faces += class_implements($interface);
}
$this->addIncludes($face_methods, $includes);
foreach ($face_methods as $method) {
$this->facesMethods[$method] = array(0 => $callbacks[$method]);
}
}
/**
* Override the implementation of an extended method.
*
* @param $methods
* An array of methods of the interface, that should be overriden, where
* the keys are methods to override and the values the callback functions
* to use.
* @param $includes
* An optional array to describe files to include before invoking the
* callbacks. You may pass a single array describing one include for all
* callbacks or an array of arrays, keyed by the method names. Look at the
* extendByClass() $include parameter for more details about how to
* describe a single file.
*/
public function override(array $callbacks = array(), array $includes = array()) {
if (array_diff_key($callbacks, $this->facesMethods)) {
throw new FacesExtendableException("A not implemented method is to be overridden.");
}
$this->addIncludes(array_keys($callbacks), $includes);
foreach ($callbacks as $method => $callback) {
$this->facesMethods[$method] = array(0 => $callback);
}
}
/**
* Adds in include files for the given methods while removing any old files.
* If a single include file is described, it's added for all methods.
*/
protected function addIncludes($methods, $includes) {
$includes = isset($includes['module']) && is_string($includes['module']) ? array_fill_keys($methods, $includes) : $includes;
$this->facesIncludes = $includes + array_diff_key($this->facesIncludes, array_flip($methods));
}
/**
* Only serialize what is really necessary.
*/
public function __sleep() {
return array('facesMethods', 'faces', 'facesIncludes');
}
/**
* Destroys all references to created instances so that PHP's garbage
* collection can do its work. This is needed as PHP's gc has troubles with
* circular references until PHP < 5.3.
*/
public function destroy() {
// Avoid circular references.
$this->facesClassInstances = array();
}
/**
* Makes protected properties accessible.
*/
public function &property($name) {
if (property_exists($this, $name)) {
return $this->$name;
}
}
/**
* Invokes any method.
*
* This also allows to pass arguments by reference, so it may be used to
* pass arguments by reference to dynamically extended methods.
*
* @param $name
* The method name.
* @param $arguments
* An array of arguments to pass to the method.
*/
public function call($name, array $args = array()) {
if (method_exists($this, $name)) {
return call_user_func_array(array($this, $name), $args);
}
return $this->__call($name, $args);
}
}
}