Projet

Général

Profil

Paste
Télécharger (10,5 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / rules / includes / faces.inc @ d719f12f

1
<?php
2

    
3
/**
4
 * @file Extendable Object Faces API. Provided by the faces module.
5
 */
6

    
7
if (!interface_exists('FacesExtenderInterface', FALSE)) {
8

    
9
  /**
10
   * Interface for extenders.
11
   */
12
  interface FacesExtenderInterface {
13

    
14
    /**
15
     * Constructs an instance of the extender.
16
     */
17
    function __construct(FacesExtendable $object);
18

    
19
    /**
20
     * Returns the extended object.
21
     */
22
    public function getExtendable();
23
  }
24

    
25
  /**
26
   * The Exception thrown by the FacesExtendable.
27
   */
28
  class FacesExtendableException extends ErrorException {}
29

    
30
}
31

    
32
if (!class_exists('FacesExtender', FALSE)) {
33
  /**
34
   * A common base class for FacesExtenders. Extenders may access protected
35
   * methods and properties of the extendable using the property() and call()
36
   * methods.
37
   */
38
  abstract class FacesExtender implements FacesExtenderInterface {
39

    
40
    /**
41
     * @var FacesExtendable
42
     */
43
    protected $object;
44

    
45

    
46
    function __construct(FacesExtendable $object) {
47
      $this->object = $object;
48
    }
49

    
50
    /**
51
     * Returns the extended object.
52
     */
53
    public function getExtendable() {
54
      return $this->object;
55
    }
56

    
57
    /**
58
     * Makes protected properties of the extendable accessible.
59
     */
60
    protected function &property($name) {
61
      $var =& $this->object->property($name);
62
      return $var;
63
    }
64

    
65
    /**
66
     * Invokes any method on the extended object. May be used to invoke
67
     * protected methods.
68
     *
69
     * @param $name
70
     *   The method name.
71
     * @param $arguments
72
     *   An array of arguments to pass to the method.
73
     */
74
    protected function call($name, array $args = array()) {
75
      return $this->object->call($name, $args);
76
    }
77
  }
78
}
79

    
80

    
81
if (!class_exists('FacesExtendable', FALSE)) {
82

    
83
  /**
84
   * An extendable base class.
85
   */
86
  abstract class FacesExtendable {
87

    
88
    protected $facesMethods = array();
89
    protected $faces = array();
90
    protected $facesIncludes = array();
91
    protected $facesClassInstances = array();
92
    static protected $facesIncluded = array();
93

    
94
    /**
95
     * Wraps calls to module_load_include() to prevent multiple inclusions.
96
     *
97
     * @see module_load_include()
98
     */
99
    protected static function load_include($args) {
100
      $args += array('type' => 'inc', 'module' => '', 'name' => NULL);
101
      $key = implode(':', $args);
102
      if (!isset(self::$facesIncluded[$key])) {
103
        self::$facesIncluded[$key] = TRUE;
104
        module_load_include($args['type'], $args['module'], $args['name']);
105
      }
106
    }
107

    
108
    /**
109
     * Magic method: Invoke the dynamically implemented methods.
110
     */
111
    function __call($name, $arguments = array()) {
112
      if (isset($this->facesMethods[$name])) {
113
        $method = $this->facesMethods[$name];
114
        // Include code, if necessary.
115
        if (isset($this->facesIncludes[$name])) {
116
          self::load_include($this->facesIncludes[$name]);
117
          $this->facesIncludes[$name] = NULL;
118
        }
119
        if (isset($method[0])) {
120
          // We always pass the object reference and the name of the method.
121
          $arguments[] = $this;
122
          $arguments[] = $name;
123
          return call_user_func_array($method[0], $arguments);
124
        }
125
        // Call the method on the extender object, but don't use extender()
126
        // for performance reasons.
127
        if (!isset($this->facesClassInstances[$method[1]])) {
128
          $this->facesClassInstances[$method[1]] = new $method[1]($this);
129
        }
130
        return call_user_func_array(array($this->facesClassInstances[$method[1]], $name), $arguments);
131
      }
132
      $class = check_plain(get_class($this));
133
      throw new FacesExtendableException("There is no method $name for this instance of the class $class.");
134
    }
135

    
136
    /**
137
     * Returns the extender object for the given class. May be used to
138
     * explicitly invoke a specific extender, e.g. a function overriding a
139
     * method may use that to explicitly invoke the original extender.
140
     */
141
    public function extender($class) {
142
      if (!isset($this->facesClassInstances[$class])) {
143
        $this->facesClassInstances[$class] = new $class($this);
144
      }
145
      return $this->facesClassInstances[$class];
146
    }
147

    
148
    /**
149
     * Returns whether the object can face as the given interface, thus it
150
     * returns TRUE if this oject has been extended by an appropriate
151
     * implementation.
152
     *
153
     * @param $interface
154
     *   Optional. A interface to test for. If it's omitted, all interfaces that
155
     *   the object can be faced as are returned.
156
     * @return
157
     *   Whether the object can face as the interface or an array of interface
158
     *   names.
159
     */
160
    public function facesAs($interface = NULL) {
161
      if (!isset($interface)) {
162
        return array_values($this->faces);
163
      }
164
      return in_array($interface, $this->faces) || $this instanceof $interface;
165
    }
166

    
167
    /**
168
     * Extend the object by a class to implement the given interfaces.
169
     *
170
     * @param $interface
171
     *   The interface name or an array of interface names.
172
     * @param $class
173
     *   The extender class, which has to implement the FacesExtenderInterface.
174
     * @param $include
175
     *   An optional array describing the file to include before invoking the
176
     *   class. The array entries known are 'type', 'module', and 'name'
177
     *   matching the parameters of module_load_include(). Only 'module' is
178
     *   required as 'type' defaults to 'inc' and 'name' to NULL.
179
     */
180
    public function extendByClass($interface, $className, array $includes = array()) {
181
      $parents = class_implements($className);
182
      if (!in_array('FacesExtenderInterface', $parents)) {
183
        throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the FacesExtenderInterface.");
184
      }
185
      $interfaces = is_array($interface) ? $interface : array($interface);
186

    
187
      foreach ($interfaces as $interface) {
188
        if (!in_array($interface, $parents)) {
189
          throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the interface " . check_plain($interface) . ".");
190
        }
191
        $this->faces[$interface] = $interface;
192
        $this->faces += class_implements($interface);
193
        $face_methods = get_class_methods($interface);
194
        $this->addIncludes($face_methods, $includes);
195
        foreach ($face_methods as $method) {
196
          $this->facesMethods[$method] = array(1 => $className);
197
        }
198
      }
199
    }
200

    
201
    /**
202
     * Extend the object by the given functions to implement the given
203
     * interface. There has to be an implementation function for each method of
204
     * the interface.
205
     *
206
     * @param $interface
207
     *   The interface name or FALSE to extend the object without a given
208
     *   interface.
209
     * @param $methods
210
     *   An array, where the keys are methods of the given interface and the
211
     *   values the callback functions to use.
212
     * @param $includes
213
     *   An optional array to describe files to include before invoking the
214
     *   callbacks. You may pass a single array describing one include for all
215
     *   callbacks or an array of arrays, keyed by the method names. Look at the
216
     *   extendByClass() $include parameter for more details about how to
217
     *   describe a single file.
218
     */
219
    public function extend($interface, array $callbacks = array(), array $includes = array()) {
220
      $face_methods = $interface ? get_class_methods($interface) : array_keys($callbacks);
221
      if ($interface) {
222
        if (array_diff($face_methods, array_keys($callbacks))) {
223
          throw new FacesExtendableException("Missing methods for implementing the interface " . check_plain($interface) . ".");
224
        }
225
        $this->faces[$interface] = $interface;
226
        $this->faces += class_implements($interface);
227
      }
228
      $this->addIncludes($face_methods, $includes);
229
      foreach ($face_methods as $method) {
230
        $this->facesMethods[$method] = array(0 => $callbacks[$method]);
231
      }
232
    }
233

    
234
    /**
235
     * Override the implementation of an extended method.
236
     *
237
     * @param $methods
238
     *   An array of methods of the interface, that should be overriden, where
239
     *   the keys are methods to override and the values the callback functions
240
     *   to use.
241
     * @param $includes
242
     *   An optional array to describe files to include before invoking the
243
     *   callbacks. You may pass a single array describing one include for all
244
     *   callbacks or an array of arrays, keyed by the method names. Look at the
245
     *   extendByClass() $include parameter for more details about how to
246
     *   describe a single file.
247
     */
248
    public function override(array $callbacks = array(), array $includes = array()) {
249
      if (array_diff_key($callbacks, $this->facesMethods)) {
250
        throw new FacesExtendableException("A not implemented method is to be overridden.");
251
      }
252
      $this->addIncludes(array_keys($callbacks), $includes);
253
      foreach ($callbacks as $method => $callback) {
254
        $this->facesMethods[$method] = array(0 => $callback);
255
      }
256
    }
257

    
258
    /**
259
     * Adds in include files for the given methods while removing any old files.
260
     * If a single include file is described, it's added for all methods.
261
     */
262
    protected function addIncludes($methods, $includes) {
263
      $includes = isset($includes['module']) && is_string($includes['module']) ? array_fill_keys($methods, $includes) : $includes;
264
      $this->facesIncludes = $includes + array_diff_key($this->facesIncludes, array_flip($methods));
265
    }
266

    
267
    /**
268
     * Only serialize what is really necessary.
269
     */
270
    public function __sleep() {
271
      return array('facesMethods', 'faces', 'facesIncludes');
272
    }
273

    
274
    /**
275
     * Destroys all references to created instances so that PHP's garbage
276
     * collection can do its work. This is needed as PHP's gc has troubles with
277
     * circular references until PHP < 5.3.
278
     */
279
    public function destroy() {
280
      // Avoid circular references.
281
      $this->facesClassInstances = array();
282
    }
283

    
284
    /**
285
     * Makes protected properties accessible.
286
     */
287
    public function &property($name) {
288
      if (property_exists($this, $name)) {
289
        return $this->$name;
290
      }
291
    }
292

    
293
    /**
294
     * Invokes any method.
295
     *
296
     * This also allows to pass arguments by reference, so it may be used to
297
     * pass arguments by reference to dynamically extended methods.
298
     *
299
     * @param $name
300
     *   The method name.
301
     * @param $arguments
302
     *   An array of arguments to pass to the method.
303
     */
304
    public function call($name, array $args = array()) {
305
      if (method_exists($this, $name)) {
306
        return call_user_func_array(array($this, $name), $args);
307
      }
308
      return $this->__call($name, $args);
309
    }
310
  }
311
}