PageRenderTime 39ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/pkp/classes/filter/FilterRegistry.inc.php

https://github.com/lib-uoguelph-ca/ocs
PHP | 327 lines | 73 code | 24 blank | 230 comment | 12 complexity | 1fb45da53b32a4aec77454c47b55a1a7 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * @file classes/filter/FilterRegistry.inc.php
  4. *
  5. * Copyright (c) 2000-2012 John Willinsky
  6. * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
  7. *
  8. * @class FilterRegistry
  9. * @ingroup filter
  10. * @see Filter
  11. *
  12. * @brief Maintains a list of filter transformations.
  13. *
  14. * The filter registry allows filter consumers to identify available transformations
  15. * that convert a given input type into a required output type.
  16. *
  17. * Transformations are defined as a combination of a filter class and a pair of
  18. * input/output type specifications supported by that filter implementation.
  19. *
  20. * Input and output type specifications need to fulfill several requirements:
  21. * 1) They must uniquely and reliably identify the transformations supported
  22. * by a filter.
  23. * 2) They must be flexible enough to deal with type polymorphism (e.g. a
  24. * generic XSL filter may accept several XML input formats while a
  25. * specialized crosswalk filter may only accept a very specific XML
  26. * encoding as input.)
  27. * 3) A single filter may implement several transformations, i.e. distinct
  28. * combinations of input and output types. Therefore the filter registry
  29. * must be able to check all available transformations against a given input
  30. * type and an expected output type and select those filters that support
  31. * compatible transformations.
  32. * 4) Type definitions must be consistent over all filters (even cross-plugin).
  33. * 5) New filters can introduce new types at any time. We therefore cannot
  34. * implement a static set (or vocabulary) of types.
  35. *
  36. * Additional requirements:
  37. * 1) The registry must take care to only select such transformations that are
  38. * supported by the current runtime environment.
  39. * 2) The registry implementation must be performant and memory efficient.
  40. * 3) The registry must support static registration of core filters and dynamic
  41. * registration for filters provided by plug-ins.
  42. *
  43. * Implementation decisions:
  44. * - Filters will be uniquely identified by their canonical class name. We chose
  45. * the well established class name nomenclature used with the import() method.
  46. * - Input and output are typed according to their PHP class. There are several
  47. * advantages to such an approach:
  48. * * The PHP type system is well known to developers. This makes transformation
  49. * definitions easily understandable.
  50. * * We can re-use an existing and well understood type system that supports
  51. * polymorphism.
  52. * * We can use PHP's existing type checking functions which are highly
  53. * optimized and very well tested. This makes type checking very easy to
  54. * implement.
  55. * * As filters have to work on PHP objects or types anyway the definition of
  56. * input and output types by their PHP types comes in naturally and can be
  57. * implemented with very little overhead in filter implementations.
  58. * Disadvantages of using the PHP type system are:
  59. * * PHP4 doesn't support interfaces or multiple inheritance which reduces the
  60. * flexibility of the type system. (FIXME: When we drop PHP4 support we
  61. * should implement multiple inheritance for input/output types based on
  62. * interface hierarchies.)
  63. * * In PHP basic types like strings, integers, booleans, floats, etc. are not
  64. * implemented as classes. Checking these types will require additional
  65. * programming logic.
  66. * * PHP4 has very limited support for type reflection. Many PHP4 class handling
  67. * methods work on object instances and not on type definitions. PHP does not
  68. * represent classes as objects which makes abstract type checking more
  69. * complicated to implement.
  70. * * Types cannot represent the internal state of objects which may be relevant
  71. * for the definition of the accepted input/output types of a transformation.
  72. * To give an example: The MetadataSchema of a given MetadataDescription is
  73. * not represented in it's object type. We have to inspect the internal state
  74. * of the MetadataDescription object to determine whether it complies to a
  75. * certain meta-data schema.
  76. * - To support use-cases as the one just described we allow filters to (optionally)
  77. * inspect internal object state to determine whether a given input/output
  78. * combination is really supported by the filter. We however assume that a
  79. * transformation can be uniquely identified by the name of the filter that
  80. * implements it and the given input and output types, i.e. their class names,
  81. * even if the filter performs stateful inspection. In other words: The registry
  82. * can only register several transformations for a single filter if their input
  83. * and output class combinations differ. (FIXME: It might become necessary to
  84. * include a textual representation of stateful inspection into our transformation
  85. * type identifiers if type names are not granular enough.)
  86. * - As we use textual representations to define transformations (i.e. class name,
  87. * input/output type names) we only have to instantiate filters for stateful
  88. * inspection. Filters that support incompatible class types can be excluded
  89. * from the result set without even having to load their class definition.
  90. */
  91. // $Id$
  92. class FilterRegistry {
  93. /**
  94. * @var array A list of all registered transformations that maps the
  95. * transformation id to the filter class name.
  96. */
  97. var $_registeredTransformations = array();
  98. /**
  99. * @var array Map a transformation id to a translation key that
  100. * describes the transformation to the end user.
  101. */
  102. var $_displayNameMap = array();
  103. /**
  104. * @var array List where runtime requirements can be looked up
  105. * for a given transformation.
  106. * The key of this array is a concatenation of the filter name
  107. * and the input/output type specifications.
  108. */
  109. var $_runtimeRequirements = array();
  110. /** @var array Maps input types to an array of supported Filters. */
  111. var $_inputTypeMap = array();
  112. /** @var array Maps output types to an array of supported Filters. */
  113. var $_outputTypeMap = array();
  114. /**
  115. * Constructor
  116. */
  117. function FilterRegistry() {
  118. }
  119. //
  120. // Public methods
  121. //
  122. /**
  123. * Register a filter transformation with the system. This method
  124. * can be called several times for every filter with different
  125. * input and output types.
  126. *
  127. * NB: In principle the input and output types would not be
  128. * necessary for filter selection as Filter classes' static
  129. * supports*() methods fully specify the supported transformations.
  130. * We require textual input and output type specification to
  131. * be able to implement late loading and filter instantiation.
  132. * This considerably improves filter registration performance
  133. * and memory footprint. We also require these type parameters
  134. * to register separate display names for all transformations
  135. * even if they are based on the same filter class.
  136. *
  137. * FIXME: We might have to include stateful inspection properties
  138. * into the input/output type definition if type based specification
  139. * is not granular enough.
  140. *
  141. * FIXME: In the future we want to support configurable filters.
  142. * We then have to rename $filterClassName to $filterInstanceName
  143. * and provide a configuration source for filter instances. The
  144. * configuration source could use some kind of dependency injection
  145. * framework to configure filter instances e.g. from an XML file.
  146. *
  147. * @param $filterClassName string The (unique) class name of the Filter
  148. * to register. Corresponds to a string that can be resolved by the
  149. * import() function.
  150. * @param $inputType string The input class of the transformation, primitive
  151. * PHP types must be prefixed with 'primitive::', example: 'primitive::string',
  152. * arrays of types can be declared with '[]', example: 'MyClass[]'. The give
  153. * type of an array that contains heterogeneous types is the type of the
  154. * first entry in the array. This will be used for type-based transformation
  155. * candidate pre-selection.
  156. * @param $outputType string The output class of the transformation, see
  157. * use of 'primitive::' prefix and array declarations above.
  158. * @param $displayName string A translation string that resolves
  159. * to the display name that represents the registered transformation.
  160. * @param $requiredRuntimeEnvironment RuntimeEnvironment specifies
  161. * runtime requirements for the transformation
  162. */
  163. function &registerTransformation($filterClassName, $inputType, $outputType, $displayName, $requiredRuntimeEnvironment = null) {
  164. // Construct the unique transformation ID
  165. $transformationId = $filterClassName.'-'.$inputType.'-'.$outputType;
  166. // Make sure that no such transformation has been
  167. // registered before.
  168. assert(!isset($this->_registeredTransformations[$transformationId]));
  169. // Register filter name, display name and runtime environment restrictions
  170. $this->_registeredTransformations[$transformationId] = $filterClassName;
  171. $this->_displayNameMap[$transformationId] = $displayName;
  172. $this->_runtimeRequirements[$transformationId] = $requiredRuntimeEnvironment;
  173. // Create an entry for the transformation in the input and output type maps
  174. $this->_inputTypeMap[$inputType] = $transformationId;
  175. $this->_outputTypeMap[$outputType] = $transformationId;
  176. }
  177. /**
  178. * Retrieve transformations that support a given input and
  179. * output sample object.
  180. *
  181. * The selection of filters that are compatible with the
  182. * given input and output samples is based on type (class
  183. * hierarchy) checks and stateful inspection of the
  184. * sample objects.
  185. *
  186. * The given objects must contain just enough state (if
  187. * any at all) for filter candidates to be able to decide
  188. * during stateful inspection whether they support object
  189. * instances with the same state.
  190. *
  191. * FIXME: Extend type checks to interfaces once we drop
  192. * PHP4 support.
  193. *
  194. * @param $inputSample mixed
  195. * @param $outputSample mixed
  196. * @return array an array of filter object instances with
  197. * their display names set to the registered values for the
  198. * transformation corresponding to the requested input/output
  199. * type definition.
  200. */
  201. function &retrieveCompatibleTransformations(&$inputStub, &$outputStub) {
  202. // 1) Type check
  203. // Retrieve transformation candidates for the given
  204. // input and output types.
  205. $inputCandidates =& $this->_retrieveTransformationCandidates($inputStub, $this->_inputTypeMap);
  206. $outputCandidates =& $this->_retrieveTransformationCandidates($outputStub, $this->_outputTypeMap);
  207. // The intersection of both result sets gives us a preliminary
  208. // shortlist of transformation candidates.
  209. // FIXME: If this is not selective enough we may encode a
  210. // stateful inspection in future releases into the type
  211. // representation, e.g. 'Type->someField[state]'.
  212. $transformationCandidates = array_intersect($inputCandidates, $outputCandidates);
  213. // 2) Iterate over the shortlist for final filter selection and
  214. // instantiation.
  215. $selectedFilters = array();
  216. foreach ($transformationCandidates as $candidateIndex => $transformationCandidate) {
  217. // 3) Check runtime environment requirements
  218. // Exclude filters that are not supported by the current
  219. // runtime environment. We check this before we load the
  220. // class definition to avoid potential parse errors and
  221. // performance overhead.
  222. $requiredRuntimeEnvironment = $this->_runtimeRequirements[$transformationCandidate];
  223. if (!is_null($requiredRuntimeEnvironment)) {
  224. assert(is_a($requiredRuntimeEnvironment, 'RuntimeRequirement'));
  225. if (!$requiredRuntimeEnvironment->isCompatible()) continue;
  226. }
  227. // 4) Stateful inspection
  228. // Instantiate filters and call their supports() method to
  229. // determine the final list of compatible filters.
  230. $filterClassAndPackage = $this->_registeredTransformations[$transformationCandidate];
  231. $filterClassAndPackageParts = explode('.', $filterClassAndPackage);
  232. $filterClass = array_pop($filterClassAndPackageParts);
  233. import($filterClassAndPackage);
  234. $filterInstance = new $filterClass();
  235. if ($filterInstance->supports($inputStub, $outputStub)) {
  236. // Set the filter's display name to the value defined for this transformation
  237. $filterInstance->setDisplayName($this->_displayNameMap[$transformationCandidate]);
  238. // Add the filter instance to the final result list
  239. $selectedTransformations[] = $filterInstance;
  240. }
  241. }
  242. // Return the list of selected filters
  243. return $selectedTransformations;
  244. }
  245. //
  246. // Private helper methods
  247. //
  248. /**
  249. * Retrieves the transformation candidates for a given stub type.
  250. * @param $stub mixed
  251. * @param $map array the transformation map
  252. * @return array a list of transformation ids
  253. */
  254. function &_retrieveTransformationCandidates(&$stub, &$map) {
  255. // 1) Identify the type of the stub
  256. // Array handling
  257. if (is_array($stub)) {
  258. // If the stub is an array then we'll use the first
  259. // entry of the array as an indicator for transformation
  260. // candidate selection. Checks for heterogeneous arrays
  261. // must be done by stateful inspection of the array.
  262. $arraySuffix = '[]';
  263. assert(count($stub));
  264. $stubType = $stub[0];
  265. } else {
  266. $arraySuffix = '';
  267. }
  268. // Distinguish between objects and primitive types
  269. if ($isObject = is_object($stub)) {
  270. $stubType = get_class($stub);
  271. } else {
  272. $stubType = $this->_getPrimitiveTypeName($stub);
  273. }
  274. // 2) Iterate over the type and all of its parent types and
  275. // retrieve the transformation candidates of all types.
  276. $transformationCandidates = array();
  277. while($stubType !== false) {
  278. // Add the entries in the registry for the type
  279. // to the candidate result set (if any).
  280. if (isset($map[$stubType])) {
  281. $transformationCandidates = array_merge($transformationCandidates, $map[$stubType.$arraySuffix]);
  282. }
  283. if ($isObject) {
  284. $stubType = get_parent_class($stubType);
  285. } else {
  286. $stubType = false;
  287. }
  288. }
  289. return $transformationCandidates;
  290. }
  291. /**
  292. * Return a string representation of a primitive type.
  293. * @param $variable mixed
  294. */
  295. function _getPrimitiveTypeName(&$variable) {
  296. assert(!(is_object($variable) || is_array($variable) || is_null($variable)));
  297. // FIXME: When gettype's implementation changes as mentioned
  298. // in <http://www.php.net/manual/en/function.gettype.php> then
  299. // we have to manually re-implement this method.
  300. return str_replace('double', 'float', gettype($variable));
  301. }
  302. }
  303. ?>