<?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;
	}
}