PageRenderTime 56ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Assetic/Factory/AssetFactory.php

https://github.com/macolu/assetic
PHP | 366 lines | 201 code | 47 blank | 118 comment | 37 complexity | 1e3cc59b835b41944de57c92b847df1d MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Assetic package, an OpenSky project.
  4. *
  5. * (c) 2010-2011 OpenSky Project Inc
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Assetic\Factory;
  11. use Assetic\Asset\AssetCollection;
  12. use Assetic\Asset\AssetCollectionInterface;
  13. use Assetic\Asset\AssetInterface;
  14. use Assetic\Asset\AssetReference;
  15. use Assetic\Asset\FileAsset;
  16. use Assetic\Asset\GlobAsset;
  17. use Assetic\Asset\HttpAsset;
  18. use Assetic\AssetManager;
  19. use Assetic\Factory\Worker\WorkerInterface;
  20. use Assetic\FilterManager;
  21. /**
  22. * The asset factory creates asset objects.
  23. *
  24. * @author Kris Wallsmith <kris.wallsmith@gmail.com>
  25. */
  26. class AssetFactory
  27. {
  28. private $root;
  29. private $debug;
  30. private $output;
  31. private $workers;
  32. private $am;
  33. private $fm;
  34. /**
  35. * Constructor.
  36. *
  37. * @param string $root The default root directory
  38. * @param string $output The default output string
  39. * @param Boolean $debug Filters prefixed with a "?" will be omitted in debug mode
  40. */
  41. public function __construct($root, $debug = false)
  42. {
  43. $this->root = rtrim($root, '/');
  44. $this->debug = $debug;
  45. $this->output = 'assetic/*';
  46. $this->workers = array();
  47. }
  48. /**
  49. * Sets debug mode for the current factory.
  50. *
  51. * @param Boolean $debug Debug mode
  52. */
  53. public function setDebug($debug)
  54. {
  55. $this->debug = $debug;
  56. }
  57. /**
  58. * Checks if the factory is in debug mode.
  59. *
  60. * @return Boolean Debug mode
  61. */
  62. public function isDebug()
  63. {
  64. return $this->debug;
  65. }
  66. /**
  67. * Sets the default output string.
  68. *
  69. * @param string $output The default output string
  70. */
  71. public function setDefaultOutput($output)
  72. {
  73. $this->output = $output;
  74. }
  75. /**
  76. * Adds a factory worker.
  77. *
  78. * @param WorkerInterface $worker A worker
  79. */
  80. public function addWorker(WorkerInterface $worker)
  81. {
  82. $this->workers[] = $worker;
  83. }
  84. /**
  85. * Returns the current asset manager.
  86. *
  87. * @return AssetManager|null The asset manager
  88. */
  89. public function getAssetManager()
  90. {
  91. return $this->am;
  92. }
  93. /**
  94. * Sets the asset manager to use when creating asset references.
  95. *
  96. * @param AssetManager $am The asset manager
  97. */
  98. public function setAssetManager(AssetManager $am)
  99. {
  100. $this->am = $am;
  101. }
  102. /**
  103. * Returns the current filter manager.
  104. *
  105. * @return FilterManager|null The filter manager
  106. */
  107. public function getFilterManager()
  108. {
  109. return $this->fm;
  110. }
  111. /**
  112. * Sets the filter manager to use when adding filters.
  113. *
  114. * @param FilterManager $fm The filter manager
  115. */
  116. public function setFilterManager(FilterManager $fm)
  117. {
  118. $this->fm = $fm;
  119. }
  120. /**
  121. * Creates a new asset.
  122. *
  123. * Prefixing a filter name with a question mark will cause it to be
  124. * omitted when the factory is in debug mode.
  125. *
  126. * Available options:
  127. *
  128. * * output: An output string
  129. * * name: An asset name for interpolation in output patterns
  130. * * debug: Forces debug mode on or off for this asset
  131. * * root: An array or string of more root directories
  132. *
  133. * @param array|string $inputs An array of input strings
  134. * @param array|string $filters An array of filter names
  135. * @param array $options An array of options
  136. *
  137. * @return AssetCollection An asset collection
  138. */
  139. public function createAsset($inputs = array(), $filters = array(), array $options = array())
  140. {
  141. if (!is_array($inputs)) {
  142. $inputs = array($inputs);
  143. }
  144. if (!is_array($filters)) {
  145. $filters = array($filters);
  146. }
  147. if (!isset($options['output'])) {
  148. $options['output'] = $this->output;
  149. }
  150. if (!isset($options['name'])) {
  151. $options['name'] = $this->generateAssetName($inputs, $filters, $options);
  152. }
  153. if (!isset($options['debug'])) {
  154. $options['debug'] = $this->debug;
  155. }
  156. if (!isset($options['root'])) {
  157. $options['root'] = array($this->root);
  158. } else {
  159. if (!is_array($options['root'])) {
  160. $options['root'] = array($options['root']);
  161. }
  162. $options['root'][] = $this->root;
  163. }
  164. $asset = $this->createAssetCollection();
  165. $extensions = array();
  166. // inner assets
  167. foreach ($inputs as $input) {
  168. if (is_array($input)) {
  169. // nested formula
  170. $asset->add(call_user_func_array(array($this, 'createAsset'), $input));
  171. } else {
  172. $asset->add($this->parseInput($input, $options));
  173. $extensions[pathinfo($input, PATHINFO_EXTENSION)] = true;
  174. }
  175. }
  176. // filters
  177. foreach ($filters as $filter) {
  178. if ('?' != $filter[0]) {
  179. $asset->ensureFilter($this->getFilter($filter));
  180. } elseif (!$options['debug']) {
  181. $asset->ensureFilter($this->getFilter(substr($filter, 1)));
  182. }
  183. }
  184. // append consensus extension if missing
  185. if (1 == count($extensions) && !pathinfo($options['output'], PATHINFO_EXTENSION) && $extension = key($extensions)) {
  186. $options['output'] .= '.'.$extension;
  187. }
  188. // output --> target url
  189. $asset->setTargetPath(str_replace('*', $options['name'], $options['output']));
  190. // apply workers and return
  191. return $this->applyWorkers($asset);
  192. }
  193. public function generateAssetName($inputs, $filters, $options = array())
  194. {
  195. foreach (array_diff(array_keys($options), array('output', 'debug', 'root')) as $key) {
  196. unset($options[$key]);
  197. }
  198. ksort($options);
  199. return substr(sha1(serialize($inputs).serialize($filters).serialize($options)), 0, 7);
  200. }
  201. /**
  202. * Parses an input string string into an asset.
  203. *
  204. * The input string can be one of the following:
  205. *
  206. * * A reference: If the string starts with an "at" sign it will be interpreted as a reference to an asset in the asset manager
  207. * * An absolute URL: If the string contains "://" or starts with "//" it will be interpreted as an HTTP asset
  208. * * A glob: If the string contains a "*" it will be interpreted as a glob
  209. * * A path: Otherwise the string is interpreted as a filesystem path
  210. *
  211. * Both globs and paths will be absolutized using the current root directory.
  212. *
  213. * @param string $input An input string
  214. * @param array $options An array of options
  215. *
  216. * @return AssetInterface An asset
  217. */
  218. protected function parseInput($input, array $options = array())
  219. {
  220. if ('@' == $input[0]) {
  221. return $this->createAssetReference(substr($input, 1));
  222. }
  223. if (false !== strpos($input, '://') || 0 === strpos($input, '//')) {
  224. return $this->createHttpAsset($input);
  225. }
  226. if (self::isAbsolutePath($input)) {
  227. if ($root = self::findRootDir($input, $options['root'])) {
  228. $path = ltrim(substr($input, strlen($root)), '/');
  229. } else {
  230. $path = null;
  231. }
  232. } else {
  233. $root = $this->root;
  234. $path = $input;
  235. $input = $this->root.'/'.$path;
  236. }
  237. if (false !== strpos($input, '*')) {
  238. return $this->createGlobAsset($input, $root);
  239. } else {
  240. return $this->createFileAsset($input, $root, $path);
  241. }
  242. }
  243. protected function createAssetCollection(array $assets = array())
  244. {
  245. return new AssetCollection($assets);
  246. }
  247. protected function createAssetReference($name)
  248. {
  249. if (!$this->am) {
  250. throw new \LogicException('There is no asset manager.');
  251. }
  252. return new AssetReference($this->am, $name);
  253. }
  254. protected function createHttpAsset($sourceUrl)
  255. {
  256. return new HttpAsset($sourceUrl);
  257. }
  258. protected function createGlobAsset($glob, $root = null)
  259. {
  260. return new GlobAsset($glob, array(), $root);
  261. }
  262. protected function createFileAsset($source, $root = null, $path = null)
  263. {
  264. return new FileAsset($source, array(), $root, $path);
  265. }
  266. protected function getFilter($name)
  267. {
  268. if (!$this->fm) {
  269. throw new \LogicException('There is no filter manager.');
  270. }
  271. return $this->fm->get($name);
  272. }
  273. /**
  274. * Filters an asset collection through the factory workers.
  275. *
  276. * Each leaf asset will be processed first, followed by the asset
  277. * collection itself.
  278. *
  279. * @param AssetCollectionInterface $asset An asset collection
  280. */
  281. private function applyWorkers(AssetCollectionInterface $asset)
  282. {
  283. foreach ($asset as $leaf) {
  284. foreach ($this->workers as $worker) {
  285. $retval = $worker->process($leaf);
  286. if ($retval instanceof AssetInterface && $leaf !== $retval) {
  287. $asset->replaceLeaf($leaf, $retval);
  288. }
  289. }
  290. }
  291. foreach ($this->workers as $worker) {
  292. $retval = $worker->process($asset);
  293. if ($retval instanceof AssetInterface) {
  294. $asset = $retval;
  295. }
  296. }
  297. return $asset instanceof AssetCollectionInterface ? $asset : $this->createAssetCollection(array($asset));
  298. }
  299. static private function isAbsolutePath($path)
  300. {
  301. return '/' == $path[0] || '\\' == $path[0] || (3 < strlen($path) && ctype_alpha($path[0]) && $path[1] == ':' && ('\\' == $path[2] || '/' == $path[2]));
  302. }
  303. /**
  304. * Loops through the root directories and returns the first match.
  305. *
  306. * @param string $path An absolute path
  307. * @param array $roots An array of root directories
  308. *
  309. * @return string|null The matching root directory, if found
  310. */
  311. static private function findRootDir($path, array $roots)
  312. {
  313. foreach ($roots as $root) {
  314. if (0 === strpos($path, $root)) {
  315. return $root;
  316. }
  317. }
  318. }
  319. }