Adaptable.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\core;
  9. use lithium\core\Environment;
  10. use SplDoublyLinkedList;
  11. use lithium\core\ConfigException;
  12. /**
  13. * The `Adaptable` static class is the base class from which all adapter implementations extend.
  14. *
  15. * `Adaptable` provides the logic necessary for generic configuration of named adapter
  16. * configurations (such as the ones used in `Cache`) as well as a unified method of locating and
  17. * obtaining an instance to a specified adapter.
  18. *
  19. * All immediate subclasses to `Adaptable` must define the protected attributes `$_configurations`
  20. * and `$_adapters`. The former is where all local adapter named configurations will be
  21. * stored (as an array of named configuration settings), and the latter must contain the
  22. * `Libraries::locate()`-compatible path string (or array of strings) specifying how adapter classes
  23. * should be located.
  24. *
  25. * This static class should **never** be called explicitly.
  26. */
  27. class Adaptable extends \lithium\core\StaticObject {
  28. /**
  29. * Must always be re-defined in sub-classes. Can provide initial
  30. * configurations, i.e. `'development'` or `'default'` along with
  31. * default options for each.
  32. *
  33. * Example:
  34. * {{{
  35. * array(
  36. * 'production' => array(),
  37. * 'development' => array(),
  38. * 'test' => array()
  39. * )
  40. * }}}
  41. *
  42. * @var object `Collection` of configurations, indexed by name.
  43. */
  44. protected static $_configurations = array();
  45. /**
  46. * Must only be re-defined in sub-classes if the class is using strategies.
  47. * Holds the `Libraries::locate()` compatible path string where the strategy
  48. * in question may be found i.e. `'strategy.storage.cache'`.
  49. *
  50. * @var string Path string.
  51. */
  52. protected static $_strategies = null;
  53. /**
  54. * Must always be re-defined in sub-classes. Holds the
  55. * `Libraries::locate()` compatible path string where the adapter in
  56. * question may be found i.e. `'adapter.storage.cache'`.
  57. *
  58. * @var string Path string.
  59. */
  60. protected static $_adapters = null;
  61. /**
  62. * Sets configurations for a particular adaptable implementation, or returns the current
  63. * configuration settings.
  64. *
  65. * @param array $config Configurations, indexed by name.
  66. * @return object `Collection` of configurations or void if setting configurations.
  67. */
  68. public static function config($config = null) {
  69. if ($config && is_array($config)) {
  70. static::$_configurations = $config;
  71. return;
  72. }
  73. if ($config) {
  74. return static::_config($config);
  75. }
  76. $result = array();
  77. static::$_configurations = array_filter(static::$_configurations);
  78. foreach (array_keys(static::$_configurations) as $key) {
  79. $result[$key] = static::_config($key);
  80. }
  81. return $result;
  82. }
  83. /**
  84. * Clears all configurations.
  85. *
  86. * @return void
  87. */
  88. public static function reset() {
  89. static::$_configurations = array();
  90. }
  91. /**
  92. * Returns adapter class name for given `$name` configuration, using
  93. * the `$_adapter` path defined in Adaptable subclasses.
  94. *
  95. * @param string $name Class name of adapter to load.
  96. * @return object Adapter object.
  97. */
  98. public static function adapter($name = null) {
  99. $config = static::_config($name);
  100. if ($config === null) {
  101. throw new ConfigException("Configuration `{$name}` has not been defined.");
  102. }
  103. if (isset($config['object'])) {
  104. return $config['object'];
  105. }
  106. $class = static::_class($config, static::$_adapters);
  107. $settings = static::$_configurations[$name];
  108. $settings[0]['object'] = static::_initAdapter($class, $config);
  109. static::$_configurations[$name] = $settings;
  110. return static::$_configurations[$name][0]['object'];
  111. }
  112. /**
  113. * Obtain an `SplDoublyLinkedList` of the strategies for the given `$name` configuration, using
  114. * the `$_strategies` path defined in `Adaptable` subclasses.
  115. *
  116. * @param string $name Class name of adapter to load.
  117. * @return object `SplDoublyLinkedList` of strategies, or `null` if none are defined.
  118. */
  119. public static function strategies($name) {
  120. $config = static::_config($name);
  121. if ($config === null) {
  122. throw new ConfigException("Configuration `{$name}` has not been defined.");
  123. }
  124. if (!isset($config['strategies'])) {
  125. return null;
  126. }
  127. $stack = new SplDoublyLinkedList();
  128. foreach ($config['strategies'] as $key => $strategy) {
  129. if (!is_array($strategy)) {
  130. $name = $strategy;
  131. $class = static::_strategy($name, static::$_strategies);
  132. $stack->push(new $class());
  133. continue;
  134. }
  135. $class = static::_strategy($key, static::$_strategies);
  136. $index = (isset($config['strategies'][$key])) ? $key : $class;
  137. $stack->push(new $class($config['strategies'][$index]));
  138. }
  139. return $stack;
  140. }
  141. /**
  142. * Applies strategies configured in `$name` for `$method` on `$data`.
  143. *
  144. * @param string $method The strategy method to be applied.
  145. * @param string $name The named configuration
  146. * @param mixed $data The data to which the strategies will be applied.
  147. * @param array $options If `mode` is set to 'LIFO', the strategies are applied in reverse.
  148. * order of their definition.
  149. * @return mixed Result of application of strategies to data. If no strategies
  150. * have been configured, this method will simply return the original data.
  151. */
  152. public static function applyStrategies($method, $name, $data, array $options = array()) {
  153. $options += array('mode' => null);
  154. if (!$strategies = static::strategies($name)) {
  155. return $data;
  156. }
  157. if (!count($strategies)) {
  158. return $data;
  159. }
  160. if (isset($options['mode']) && ($options['mode'] === 'LIFO')) {
  161. $strategies->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
  162. unset($options['mode']);
  163. }
  164. foreach ($strategies as $strategy) {
  165. if (method_exists($strategy, $method)) {
  166. $data = $strategy->{$method}($data, $options);
  167. }
  168. }
  169. return $data;
  170. }
  171. /**
  172. * Determines if the adapter specified in the named configuration is enabled.
  173. *
  174. * `Enabled` can mean various things, e.g. having a PECL memcached extension compiled
  175. * & loaded, as well as having the memcache server up & available.
  176. *
  177. * @param string $name The named configuration whose adapter will be checked.
  178. * @return boolean True if adapter is enabled, false if not. This method will
  179. * return null if no configuration under the given $name exists.
  180. */
  181. public static function enabled($name) {
  182. if (!static::_config($name)) {
  183. return null;
  184. }
  185. $adapter = static::adapter($name);
  186. return $adapter::enabled();
  187. }
  188. /**
  189. * Provides an extension point for modifying how adapters are instantiated.
  190. *
  191. * @see lithium\core\Object::__construct()
  192. * @param string $class The fully-namespaced class name of the adapter to instantiate.
  193. * @param array $config The configuration array to be passed to the adapter instance. See the
  194. * `$config` parameter of `Object::__construct()`.
  195. * @return object The adapter's class.
  196. * @filter This method can be filtered.
  197. */
  198. protected static function _initAdapter($class, array $config) {
  199. return static::_filter(__FUNCTION__, compact('class', 'config'), function($self, $params) {
  200. return new $params['class']($params['config']);
  201. });
  202. }
  203. /**
  204. * Looks up an adapter by class by name.
  205. *
  206. * @see lithium\core\libraries::locate()
  207. * @param string $config Configuration array of class to be found.
  208. * @param array $paths Optional array of search paths that will be checked.
  209. * @return string Returns a fully-namespaced class reference to the adapter class.
  210. */
  211. protected static function _class($config, $paths = array()) {
  212. if (!$name = $config['adapter']) {
  213. $self = get_called_class();
  214. throw new ConfigException("No adapter set for configuration in class `{$self}`.");
  215. }
  216. if (!$class = static::_locate($paths, $name)) {
  217. $self = get_called_class();
  218. throw new ConfigException("Could not find adapter `{$name}` in class `{$self}`.");
  219. }
  220. return $class;
  221. }
  222. /**
  223. * Looks up a strategy by class by name.
  224. *
  225. * @see lithium\core\libraries::locate()
  226. * @param string $name The strategy to locate.
  227. * @param array $paths Optional array of search paths that will be checked.
  228. * @return string Returns a fully-namespaced class reference to the adapter class.
  229. */
  230. protected static function _strategy($name, $paths = array()) {
  231. if (!$name) {
  232. $self = get_called_class();
  233. throw new ConfigException("No strategy set for configuration in class `{$self}`.");
  234. }
  235. if (!$class = static::_locate($paths, $name)) {
  236. $self = get_called_class();
  237. throw new ConfigException("Could not find strategy `{$name}` in class `{$self}`.");
  238. }
  239. return $class;
  240. }
  241. /**
  242. * Perform library location for an array of paths or a single string-based path.
  243. *
  244. * @param string|array $paths Paths that Libraries::locate() will utilize.
  245. * @param string $name The name of the class to be located.
  246. * @return string Fully-namespaced path to the class, or null if not found.
  247. */
  248. protected static function _locate($paths, $name) {
  249. foreach ((array) $paths as $path) {
  250. if ($class = Libraries::locate($path, $name)) {
  251. return $class;
  252. }
  253. }
  254. return null;
  255. }
  256. /**
  257. * Gets an array of settings for the given named configuration in the current
  258. * environment.
  259. *
  260. * The default types of settings for all adapters will contain keys for:
  261. * `adapter` - The class name of the adapter
  262. * `filters` - An array of filters to be applied to the adapter methods
  263. *
  264. * @see lithium\core\Environment
  265. * @param string $name Named configuration.
  266. * @return array Settings for the named configuration.
  267. */
  268. protected static function _config($name) {
  269. if (!isset(static::$_configurations[$name])) {
  270. return null;
  271. }
  272. $settings = static::$_configurations[$name];
  273. if (isset($settings[0])) {
  274. return $settings[0];
  275. }
  276. $env = Environment::get();
  277. $config = isset($settings[$env]) ? $settings[$env] : $settings;
  278. if (isset($settings[$env]) && isset($settings[true])) {
  279. $config += $settings[true];
  280. }
  281. static::$_configurations[$name] += array(static::_initConfig($name, $config));
  282. return static::$_configurations[$name][0];
  283. }
  284. /**
  285. * A stub method called by `_config()` which allows `Adaptable` subclasses to automatically
  286. * assign or auto-generate additional configuration data, once a configuration is first
  287. * accessed. This allows configuration data to be lazy-loaded from adapters or other data
  288. * sources.
  289. *
  290. * @param string $name The name of the configuration which is being accessed. This is the key
  291. * name containing the specific set of configuration passed into `config()`.
  292. * @param array $config Contains the configuration assigned to `$name`. If this configuration is
  293. * segregated by environment, then this will contain the configuration for the
  294. * current environment.
  295. * @return array Returns the final array of settings for the given named configuration.
  296. */
  297. protected static function _initConfig($name, $config) {
  298. $defaults = array('adapter' => null, 'filters' => array());
  299. return (array) $config + $defaults;
  300. }
  301. }
  302. ?>