asset.php 8.6 KB

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