asset.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. <?php namespace System;
  2. use System\File;
  3. use System\HTML;
  4. class Asset {
  5. /**
  6. * All of the asset containers. Asset containers are created through the
  7. * container method, and are managed as singletons.
  8. *
  9. * @var array
  10. */
  11. public static $containers = array();
  12. /**
  13. * Get an asset container instance.
  14. *
  15. * If no container name is specified, the default container will be returned.
  16. * Containers provide a convenient method of grouping assets while maintaining
  17. * expressive code and a clean API.
  18. *
  19. * <code>
  20. * // Get the default asset container
  21. * $container = Asset::container();
  22. *
  23. * // Get the "footer" asset contanier
  24. * $container = Asset::container('footer');
  25. * </code>
  26. *
  27. * @param string $container
  28. * @return Asset_Container
  29. */
  30. public static function container($container = 'default')
  31. {
  32. if ( ! isset(static::$containers[$container]))
  33. {
  34. static::$containers[$container] = new Asset_Container($container);
  35. }
  36. return static::$containers[$container];
  37. }
  38. /**
  39. * Magic Method for calling methods on the default Asset container.
  40. * This allows a convenient API for working with the default container.
  41. *
  42. * <code>
  43. * // Add jQuery to the default container
  44. * Asset::script('jquery', 'js/jquery.js');
  45. * </code>
  46. *
  47. */
  48. public static function __callStatic($method, $parameters)
  49. {
  50. return call_user_func_array(array(static::container(), $method), $parameters);
  51. }
  52. }
  53. class Asset_Container {
  54. /**
  55. * The asset container name.
  56. *
  57. * @var string
  58. */
  59. public $name;
  60. /**
  61. * All of the registered assets.
  62. *
  63. * @var array
  64. */
  65. public $assets = array();
  66. /**
  67. * Create a new asset container instance.
  68. *
  69. * @param string $name
  70. * @return void
  71. */
  72. public function __construct($name)
  73. {
  74. $this->name = $name;
  75. }
  76. /**
  77. * Add an asset to the container.
  78. *
  79. * The extension of the asset source will be used to determine the type of
  80. * asset being registered (CSS or JavaScript). If you are using a non-standard
  81. * extension, you may use the style or script methods to register assets.
  82. *
  83. * <code>
  84. * // Register a jQuery asset
  85. * Asset::add('jquery', 'js/jquery.js');
  86. * </code>
  87. *
  88. * You may also specify asset dependencies. This will instruct the class to
  89. * only link to the registered asset after its dependencies have been linked.
  90. * For example, you may wish to make jQuery UI dependent on jQuery.
  91. *
  92. * <code>
  93. * // Register jQuery UI as dependent on jQuery
  94. * Asset::add('jquery-ui', 'js/jquery-ui.js', 'jquery');
  95. *
  96. * // Register jQuery UI with multiple dependencies
  97. * Asset::add('jquery-ui', 'js/jquery-ui.js', array('jquery', 'fader'));
  98. * </code>
  99. *
  100. * @param string $name
  101. * @param string $source
  102. * @param array $dependencies
  103. * @param array $attributes
  104. * @return void
  105. */
  106. public function add($name, $source, $dependencies = array(), $attributes = array())
  107. {
  108. $type = (File::extension($source) == 'css') ? 'style' : 'script';
  109. return call_user_func(array($this, $type), $name, $source, $dependencies, $attributes);
  110. }
  111. /**
  112. * Add CSS to the registered assets.
  113. *
  114. * @param string $name
  115. * @param string $source
  116. * @param array $dependencies
  117. * @param array $attributes
  118. * @return void
  119. * @see add
  120. */
  121. public function style($name, $source, $dependencies = array(), $attributes = array())
  122. {
  123. if ( ! array_key_exists('media', $attributes))
  124. {
  125. $attributes['media'] = 'all';
  126. }
  127. $this->register('style', $name, $source, $dependencies, $attributes);
  128. }
  129. /**
  130. * Add JavaScript to the registered assets.
  131. *
  132. * @param string $name
  133. * @param string $source
  134. * @param array $dependencies
  135. * @param array $attributes
  136. * @return void
  137. * @see add
  138. */
  139. public function script($name, $source, $dependencies = array(), $attributes = array())
  140. {
  141. $this->register('script', $name, $source, $dependencies, $attributes);
  142. }
  143. /**
  144. * Add an asset to the registered assets.
  145. *
  146. * @param string $type
  147. * @param string $name
  148. * @param string $source
  149. * @param array $dependencies
  150. * @param array $attributes
  151. * @return void
  152. */
  153. private function register($type, $name, $source, $dependencies, $attributes)
  154. {
  155. $dependencies = (array) $dependencies;
  156. $this->assets[$type][$name] = compact('source', 'dependencies', 'attributes');
  157. }
  158. /**
  159. * Get the links to all of the registered CSS assets.
  160. *
  161. * @return string
  162. */
  163. public function styles()
  164. {
  165. return $this->get_group('style');
  166. }
  167. /**
  168. * Get the links to all of the registered JavaScript assets.
  169. *
  170. * @return string
  171. */
  172. public function scripts()
  173. {
  174. return $this->get_group('script');
  175. }
  176. /**
  177. * Get all of the registered assets for a given group.
  178. *
  179. * @param string $group
  180. * @return string
  181. */
  182. private function get_group($group)
  183. {
  184. if ( ! isset($this->assets[$group]) or count($this->assets[$group]) == 0) return '';
  185. $assets = '';
  186. foreach ($this->arrange($this->assets[$group]) as $name => $data)
  187. {
  188. $assets .= $this->get_asset($group, $name);
  189. }
  190. return $assets;
  191. }
  192. /**
  193. * Get the link to a single registered CSS asset.
  194. *
  195. * <code>
  196. * echo $container->get_style('common');
  197. * </code>
  198. *
  199. * @param string $name
  200. * @return string
  201. */
  202. public function get_style($name)
  203. {
  204. return $this->get_asset('style', $name);
  205. }
  206. /**
  207. * Get the link to a single registered JavaScript asset.
  208. *
  209. * <code>
  210. * echo $container->get_script('jquery');
  211. * </code>
  212. *
  213. * @param string $name
  214. * @return string
  215. */
  216. public function get_script($name)
  217. {
  218. return $this->get_asset('script', $name);
  219. }
  220. /**
  221. * Get a registered asset.
  222. *
  223. * @param string $group
  224. * @param string $name
  225. * @return string
  226. */
  227. private function get_asset($group, $name)
  228. {
  229. if ( ! isset($this->assets[$group][$name])) return '';
  230. $asset = $this->assets[$group][$name];
  231. return HTML::$group($asset['source'], $asset['attributes']);
  232. }
  233. /**
  234. * Sort and retrieve assets based on their dependencies
  235. *
  236. * @param array $assets
  237. * @return array
  238. */
  239. private function arrange($assets)
  240. {
  241. list($original, $sorted) = array($assets, array());
  242. while (count($assets) > 0)
  243. {
  244. foreach ($assets as $asset => $value)
  245. {
  246. $this->evaluate_asset($asset, $value, $original, $sorted, $assets);
  247. }
  248. }
  249. return $sorted;
  250. }
  251. /**
  252. * Evaluate an asset and its dependencies.
  253. *
  254. * @param string $asset
  255. * @param string $value
  256. * @param array $original
  257. * @param array $sorted
  258. * @param array $assets
  259. * @return void
  260. */
  261. private function evaluate_asset($asset, $value, $original, &$sorted, &$assets)
  262. {
  263. // If the asset has no more dependencies, we can add it to the sorted list
  264. // and remove it from the array of assets. Otherwise, we will not verify
  265. // the asset's dependencies and determine if they have already been sorted.
  266. if (count($assets[$asset]['dependencies']) == 0)
  267. {
  268. $sorted[$asset] = $value;
  269. unset($assets[$asset]);
  270. }
  271. else
  272. {
  273. foreach ($assets[$asset]['dependencies'] as $key => $dependency)
  274. {
  275. if ( ! $this->dependency_is_valid($asset, $dependency, $original, $assets))
  276. {
  277. unset($assets[$asset]['dependencies'][$key]);
  278. continue;
  279. }
  280. // If the dependency has not yet been added to the sorted list, we can not
  281. // remove it from this asset's array of dependencies. We'll try again on
  282. // the next trip through the loop.
  283. if ( ! isset($sorted[$dependency])) continue;
  284. unset($assets[$asset]['dependencies'][$key]);
  285. }
  286. }
  287. }
  288. /**
  289. * Check that a dependency is valid.
  290. *
  291. * @param string $asset
  292. * @param string $dependency
  293. * @param array $original
  294. * @param array $assets
  295. * @return bool
  296. */
  297. private function dependency_is_valid($asset, $dependency, $original, $assets)
  298. {
  299. if ( ! isset($original[$dependency]))
  300. {
  301. return false;
  302. }
  303. elseif ($dependency === $asset)
  304. {
  305. throw new \Exception("Asset [$asset] is dependent on itself.");
  306. }
  307. elseif (isset($assets[$dependency]) and in_array($asset, $assets[$dependency]['dependencies']))
  308. {
  309. throw new \Exception("Assets [$asset] and [$dependency] have a circular dependency.");
  310. }
  311. }
  312. }