PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/jasonlewis/basset/src/Basset/Filter/Filter.php

https://bitbucket.org/larryg/powerhut
PHP | 565 lines | 257 code | 67 blank | 241 comment | 11 complexity | 38cb83e7385ab265b4f4881fc774045d MD5 | raw file
  1. <?php namespace Basset\Filter;
  2. use Closure;
  3. use Basset\Asset;
  4. use ReflectionClass;
  5. use ReflectionException;
  6. use Illuminate\Log\Writer;
  7. use Assetic\Filter\FilterInterface;
  8. use Symfony\Component\Process\ExecutableFinder;
  9. class Filter {
  10. /**
  11. * Array of instantiation arguments.
  12. *
  13. * @var array
  14. */
  15. protected $arguments = array();
  16. /**
  17. * Array of before filtering callbacks.
  18. *
  19. * @var array
  20. */
  21. protected $before = array();
  22. /**
  23. * Filter name.
  24. *
  25. * @var string
  26. */
  27. protected $filter;
  28. /**
  29. * Resource being filtered.
  30. *
  31. * @var \Basset\Filterable
  32. */
  33. protected $resource;
  34. /**
  35. * Array of filter requirements.
  36. *
  37. * @var array
  38. */
  39. protected $requirements = array();
  40. /**
  41. * Array of node module paths.
  42. *
  43. * @var array
  44. */
  45. protected $nodePaths = array();
  46. /**
  47. * Array of valid executable argument suffixes.
  48. *
  49. * @var array
  50. */
  51. protected $validSuffixes = array('bin', 'path');
  52. /**
  53. * Indicates if the filter should be ignored when building assets.
  54. *
  55. * @var bool
  56. */
  57. protected $ignored = false;
  58. /**
  59. * Application working environment.
  60. *
  61. * @var string
  62. */
  63. protected $applicationEnvironment;
  64. /**
  65. * Illuminate log writer instance.
  66. *
  67. * @var \Illuminate\Log\Writer
  68. */
  69. protected $log;
  70. /**
  71. * Indicates if the build is a production build.
  72. *
  73. * @var bool
  74. */
  75. protected $production = false;
  76. /**
  77. * Create a new filter instance.
  78. *
  79. * @param \Illuminate\Log\Writer $log
  80. * @param string $filter
  81. * @param array $nodePaths
  82. * @param string $applicationEnvironment
  83. * @return void
  84. */
  85. public function __construct(Writer $log, $filter, array $nodePaths, $applicationEnvironment)
  86. {
  87. $this->log = $log;
  88. $this->filter = $filter;
  89. $this->nodePaths = $nodePaths;
  90. $this->applicationEnvironment = $applicationEnvironment;
  91. }
  92. /**
  93. * Find and set any missing constructor arguments.
  94. *
  95. * @return \Basset\Filter\Filter
  96. */
  97. public function findMissingConstructorArgs()
  98. {
  99. try
  100. {
  101. $class = new ReflectionClass($this->getClassName());
  102. }
  103. catch (ReflectionException $e)
  104. {
  105. return $this;
  106. }
  107. if ($constructor = $class->getConstructor())
  108. {
  109. $finder = $this->getExecutableFinder();
  110. // Spin through all of the constructor parameters and for those that we can find the executable
  111. // path for we'll attempt to locate the executable. If we can't find the path then its more
  112. // then we'll ignore this filter as it will fail during the build.
  113. foreach ($constructor->getParameters() as $key => $parameter)
  114. {
  115. if ($this->hasArgumentAtPosition($key))
  116. {
  117. continue;
  118. }
  119. $snakeParameter = $this->normalizeConstructorParameter($parameter->name);
  120. list($name, $suffix) = explode('_', $snakeParameter);
  121. // If the suffix is in the array of valid suffixes we can attempt to locate the parameter
  122. // first as an environment variable and secondly by recursively searching through our
  123. // paths defined in PATH. If we can't find it then ignore the filter.
  124. if (in_array($suffix, $this->validSuffixes))
  125. {
  126. $path = $this->getEnvironmentVariable($snakeParameter) ?: $finder->find($name);
  127. if ($path)
  128. {
  129. $this->setArgument($path, $key);
  130. }
  131. else
  132. {
  133. // There was a problem locating the path to the argument, we'll log that the
  134. // filter was ignored here so that the developer can debug this better.
  135. $this->log->error(sprintf('Failed to find required constructor argument for filter "%s". (%s)', $this->filter, $parameter));
  136. $this->ignored = true;
  137. }
  138. }
  139. elseif(str_is('nodePaths', $parameter->name))
  140. {
  141. $this->setArgument($this->nodePaths, $key);
  142. }
  143. }
  144. }
  145. return $this;
  146. }
  147. /**
  148. * Get an environment variable.
  149. *
  150. * @param string $key
  151. * @return string|bool
  152. */
  153. public function getEnvironmentVariable($key)
  154. {
  155. return getenv(strtoupper($key));
  156. }
  157. /**
  158. * Convert a constructor parameter to snake case and all lowercase.
  159. *
  160. * @param string $name
  161. * @return string
  162. */
  163. protected function normalizeConstructorParameter($name)
  164. {
  165. return strtolower(snake_case($name));
  166. }
  167. /**
  168. * Get an executable finder instance.
  169. *
  170. * @return \Symfony\Component\Process\ExecutableFinder
  171. */
  172. public function getExecutableFinder()
  173. {
  174. return new ExecutableFinder;
  175. }
  176. /**
  177. * Add a requirement to the filter.
  178. *
  179. * @param \Closure $callback
  180. * @return \Basset\Filter\Filter
  181. */
  182. public function when(Closure $callback)
  183. {
  184. $this->requirements[] = $callback;
  185. return $this;
  186. }
  187. /**
  188. * Add a class exists requirement to the filter.
  189. *
  190. * @param string $class
  191. * @return \Basset\Filter\Filter
  192. */
  193. public function whenClassExists($class)
  194. {
  195. return $this->when(function() use ($class)
  196. {
  197. return class_exists($class);
  198. });
  199. }
  200. /**
  201. * Add a asset name pattern requirement to the filter.
  202. *
  203. * @param string $pattern
  204. * @return \Basset\Filter\Filter
  205. */
  206. public function whenAssetIs($pattern)
  207. {
  208. return $this->when(function($asset) use ($pattern)
  209. {
  210. return (bool) preg_match('#'.$pattern.'#', $asset->getRelativePath());
  211. });
  212. }
  213. /**
  214. * Add an environment requirement to the filter.
  215. *
  216. * @return \Basset\Filter\Filter
  217. */
  218. public function whenEnvironmentIs()
  219. {
  220. $environments = func_get_args();
  221. $applicationEnvironment = $this->applicationEnvironment;
  222. return $this->when(function($asset) use ($environments, $applicationEnvironment)
  223. {
  224. return in_array($applicationEnvironment, $environments);
  225. });
  226. }
  227. /**
  228. * Add a stylesheets only requirement to the filter.
  229. *
  230. * @return \Basset\Filter\Filter
  231. */
  232. public function whenAssetIsStylesheet()
  233. {
  234. return $this->when(function($asset)
  235. {
  236. return $asset->isStylesheet();
  237. });
  238. }
  239. /**
  240. * Add a javascripts only requirement to the filter.
  241. *
  242. * @return \Basset\Filter\Filter
  243. */
  244. public function whenAssetIsJavascript()
  245. {
  246. return $this->when(function($asset)
  247. {
  248. return $asset->isJavascript();
  249. });
  250. }
  251. /**
  252. * Add a production build requirement to the filter.
  253. *
  254. * @return \Basset\Filter\Filter
  255. */
  256. public function whenProductionBuild()
  257. {
  258. return $this->when(function($asset, $filter)
  259. {
  260. return $filter->getProduction() === true;
  261. });
  262. }
  263. /**
  264. * Add a development build requirement to the filter.
  265. *
  266. * @return \Basset\Filter\Filter
  267. */
  268. public function whenDevelopmentBuild()
  269. {
  270. return $this->when(function($asset, $filter)
  271. {
  272. return $filter->getProduction() === false;
  273. });
  274. }
  275. /**
  276. * Add a before filtering callback.
  277. *
  278. * @param \Closure $callback
  279. * @return \Basset\Filter\Filter
  280. */
  281. public function beforeFiltering(Closure $callback)
  282. {
  283. $this->before[] = $callback;
  284. return $this;
  285. }
  286. /**
  287. * Determine if the filter has an instantiation argument at a given position.
  288. *
  289. * @param int $position
  290. * @return bool
  291. */
  292. public function hasArgumentAtPosition($position)
  293. {
  294. return isset($this->arguments[$position]);
  295. }
  296. /**
  297. * Set a single instantiation argument.
  298. *
  299. * @param string $argument
  300. * @param int $position
  301. * @return \Basset\Filter\Filter
  302. */
  303. public function setArgument($argument, $position = null)
  304. {
  305. array_splice($this->arguments, $position ?: count($this->arguments), 0, array($argument));
  306. return $this;
  307. }
  308. /**
  309. * Set the filters instantiation arguments
  310. *
  311. * @return Basset\Filter\Filter
  312. */
  313. public function setArguments()
  314. {
  315. $this->arguments = array_merge($this->arguments, func_get_args());
  316. return $this;
  317. }
  318. /**
  319. * Set the resource on the filter.
  320. *
  321. * @param \Basset\Filter\Filterable $resource
  322. * @return \Basset\Filter\Filter
  323. */
  324. public function setResource(Filterable $resource)
  325. {
  326. $this->resource = $resource;
  327. return $this;
  328. }
  329. /**
  330. * Get the parent resource.
  331. *
  332. * @return mixed
  333. */
  334. public function getResource()
  335. {
  336. return $this->resource;
  337. }
  338. /**
  339. * Get the filter name.
  340. *
  341. * @return string
  342. */
  343. public function getFilter()
  344. {
  345. return $this->filter;
  346. }
  347. /**
  348. * Get the array of environments.
  349. *
  350. * @return array
  351. */
  352. public function getEnvironments()
  353. {
  354. return $this->environments;
  355. }
  356. /**
  357. * Get the filters instantiation arguments.
  358. *
  359. * @return array
  360. */
  361. public function getArguments()
  362. {
  363. return $this->arguments;
  364. }
  365. /**
  366. * Determine if filter is ignored.
  367. *
  368. * @return bool
  369. */
  370. public function isIgnored()
  371. {
  372. return $this->ignored;
  373. }
  374. /**
  375. * Get the class name for the filter if it exists.
  376. *
  377. * @return string
  378. */
  379. public function getClassName()
  380. {
  381. if (class_exists("Assetic\\Filter\\{$this->filter}"))
  382. {
  383. return "Assetic\\Filter\\{$this->filter}";
  384. }
  385. elseif (class_exists("Basset\\Filter\\{$this->filter}"))
  386. {
  387. return "Basset\\Filter\\{$this->filter}";
  388. }
  389. }
  390. /**
  391. * Attempt to instantiate the filter if it exists and has not been ignored.
  392. *
  393. * @return null|\Assetic\Filter\FilterInterface
  394. */
  395. public function getInstance()
  396. {
  397. if ( ! $class = $this->getClassName())
  398. {
  399. $this->log->error(sprintf('"%s" will not be applied to asset "%s" due to an invalid filter name.', $this->filter, $this->resource->getRelativePath()));
  400. }
  401. if ($this->ignored or is_null($class) or ! $this->processRequirements()) return;
  402. // If the class returned is already implements Assetic\Filter\FilterInterface then
  403. // we can set the instance directly and not worry about using reflection.
  404. if ($class instanceof FilterInterface)
  405. {
  406. $instance = $class;
  407. }
  408. else
  409. {
  410. $reflection = new ReflectionClass($class);
  411. // If no constructor is available on the filters class then we'll instantiate
  412. // the filter without passing in any arguments.
  413. if ( ! $reflection->getConstructor())
  414. {
  415. $instance = $reflection->newInstance();
  416. }
  417. else
  418. {
  419. $instance = $reflection->newInstanceArgs($this->arguments);
  420. }
  421. }
  422. // Spin through each of the before filtering callbacks and fire each one. We'll
  423. // pass in an instance of the filter to the callback.
  424. foreach ($this->before as $callback)
  425. {
  426. if (is_callable($callback))
  427. {
  428. call_user_func($callback, $instance);
  429. }
  430. }
  431. return $instance;
  432. }
  433. /**
  434. * Process any requirements on the filter.
  435. *
  436. * @return bool
  437. */
  438. public function processRequirements()
  439. {
  440. foreach ($this->requirements as $requirement)
  441. {
  442. if ( ! call_user_func($requirement, $this->resource, $this))
  443. {
  444. return false;
  445. }
  446. }
  447. return true;
  448. }
  449. /**
  450. * Get the filter requirements.
  451. *
  452. * @return array
  453. */
  454. public function getRequirements()
  455. {
  456. return $this->requirements;
  457. }
  458. /**
  459. * Set the production build indicator.
  460. *
  461. * @param bool $production
  462. * @return \Basset\Filter\Filter
  463. */
  464. public function setProduction($production)
  465. {
  466. $this->production = $production;
  467. return $this;
  468. }
  469. /**
  470. * Get the production build indicator.
  471. *
  472. * @return bool
  473. */
  474. public function getProduction()
  475. {
  476. return $this->production;
  477. }
  478. /**
  479. * Get the array of node paths.
  480. *
  481. * @return array
  482. */
  483. public function getNodePath()
  484. {
  485. return $this->nodePaths;
  486. }
  487. /**
  488. * Dynamically chain uncallable methods to the belonging resource.
  489. *
  490. * @param string $method
  491. * @param array $parameters
  492. * @return mixed
  493. */
  494. public function __call($method, $parameters)
  495. {
  496. return call_user_func_array(array($this->resource, $method), $parameters);
  497. }
  498. }