summaryrefslogtreecommitdiff
path: root/lib/querypath/QueryPathExtension.php
blob: 31e269445566c528e3753ce3c1f616b84330debc (plain)
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
<?php
/** @file
 * This file contains the Query Path extension tools.
 *
 * Query Path can be extended to support additional features. To do this, 
 * you need only create a new class that implements {@link QueryPathExtension}
 * and add your own methods. This class can then be registered as an extension. 
 * It will then be available through Query Path.
 *
 * For information on building your own extension, see {@link QueryPathExtension}.
 * If you are trying to load an extension you have downloaded, chances are good that
 * all you need to do is {@link require_once} the file that contains the extension.
 *
 * @author M Butcher <matt@aleph-null.tv>
 * @license http://opensource.org/licenses/lgpl-2.1.php LGPL or MIT-like license.
 * @see QueryPathExtension
 * @see QueryPathExtensionRegistry::extend()
 */

/** @addtogroup querypath_extensions Extensions
 * The QueryPath extension system and bundled extensions.
 *
 * Much like jQuery, QueryPath provides a simple extension mechanism that allows 
 * extensions to auto-register themselves upon being loaded. For a simple example, see
 * QPXML. For the internals, see QueryPathExntesion and QueryPath::__construct().
 */

/**
 * A QueryPathExtension is a tool that extends the capabilities of a QueryPath object.
 *
 * Extensions to QueryPath should implement the QueryPathExtension interface. The
 * only requirement is that the extension provide a constructor that takes a
 * QueryPath object as a parameter.
 *
 * Here is an example QueryPath extension:
 * <code><?php
 * class StubExtensionOne implements QueryPathExtension {
 *   private $qp = NULL;
 *   public function __construct(QueryPath $qp) {
 *     $this->qp = $qp;
 *   }
 *
 *   public function stubToe() {
 *     $this->qp->find(':root')->append('<toe/>')->end();
 *     return $this->qp;
 *   }
 * }
 * QueryPathExtensionRegistry::extend('StubExtensionOne');
 * ?></code>
 * In this example, the StubExtensionOne class implements QueryPathExtension.
 * The constructor stores a local copyof the QueryPath object. This is important
 * if you are planning on fully integrating with QueryPath's Fluent Interface.
 *
 * Finally, the stubToe() function illustrates how the extension makes use of 
 * QueryPath internally, and remains part of the fluent interface by returning
 * the $qp object.
 *
 * Notice that beneath the class, there is a single call to register the 
 * extension with QueryPath's registry. Your extension should end with a line 
 * similar to this.
 *
 * <b>How is a QueryPath extension called?</b>
 *
 * QueryPath extensions are called like regular QueryPath functions. For 
 * example, the extension above can be called like this:
 * <code>
 * qp('some.xml')->stubToe();
 * </code>
 * Since it returns the QueryPath ($qp) object, chaining is supported:
 * <code>
 * print qp('some.xml')->stubToe()->xml();
 * </code>
 * When you write your own extensions, anything that does not need to return a 
 * specific value should return the QueryPath object. Between that and the 
 * extension registry, this will provide the best developer experience.
 *
 * @ingroup querypath_extensions
 */
interface QueryPathExtension {
  public function __construct(QueryPath $qp);
}

/**
 * A registry for QueryPath extensions.
 *
 * QueryPath extensions should call the {@link QueryPathExtensionRegistry::extend()}
 * function to register their extension classes. The QueryPath library then 
 * uses this information to determine what QueryPath extensions should be loaded and
 * executed.
 *
 * @ingroup querypath_extensions
 */
class QueryPathExtensionRegistry {
  /**
   * Internal flag indicating whether or not the registry should
   * be used for automatic extension loading. If this is false, then
   * implementations should not automatically load extensions.
   */
  public static $useRegistry = TRUE;
  /**
   * The extension registry. This should consist of an array of class
   * names.
   */
  protected static $extensionRegistry = array();
  protected static $extensionMethodRegistry = array();
  /**
   * Extend QueryPath with the given extension class.
   */
  public static function extend($classname) {
    self::$extensionRegistry[] = $classname;
    $class = new ReflectionClass($classname);
    $methods = $class->getMethods();
    foreach ($methods as $method) {
      self::$extensionMethodRegistry[$method->getName()] = $classname;
    }
  }
  
  /**
   * Check to see if a method is known.
   * This checks to see if the given method name belongs to one of the 
   * registered extensions. If it does, then this will return TRUE.
   *
   * @param string $name
   *  The name of the method to search for.
   * @return boolean
   *  TRUE if the method exists, false otherwise.
   */
  public static function hasMethod($name) {
    return isset(self::$extensionMethodRegistry[$name]);
  }
  
  /**
   * Check to see if the given extension class is registered.
   * Given a class name for a {@link QueryPathExtension} class, this 
   * will check to see if that class is registered. If so, it will return
   * TRUE.
   * 
   * @param string $name
   *  The name of the class.
   * @return boolean
   *  TRUE if the class is registered, FALSE otherwise.
   */
  public static function hasExtension($name) {
    return in_array($name, self::$extensionRegistry);
  }
  
  /**
   * Get the class that a given method belongs to.
   * Given a method name, this will check all registered extension classes
   * to see if any of them has the named method. If so, this will return 
   * the classname.
   *
   * Note that if two extensions are registered that contain the same 
   * method name, the last one registred will be the only one recognized.
   *
   * @param string $name
   *  The name of the method.
   * @return string
   *  The name of the class.
   */
  public static function getMethodClass($name) {
    return self::$extensionMethodRegistry[$name];
  }
  
  /**
   * Get extensions for the given QueryPath object.
   *
   * Given a {@link QueryPath} object, this will return
   * an associative array of extension names to (new) instances.
   * Generally, this is intended to be used internally.
   *
   * @param QueryPath $qp
   *  The QueryPath into which the extensions should be registered.
   * @return array
   *  An associative array of classnames to instances.
   */
  public static function getExtensions(QueryPath $qp) {
    $extInstances = array();
    foreach (self::$extensionRegistry as $ext) {
      $extInstances[$ext] = new $ext($qp);
    }
    return $extInstances;
  }
  
  /**
   * Enable or disable automatic extension loading.
   *
   * If extension autoloading is disabled, then QueryPath will not 
   * automatically load all registred extensions when a new QueryPath
   * object is created using {@link qp()}.
   */
  public static function autoloadExtensions($boolean = TRUE) {
    self::$useRegistry = $boolean;
  }
}