1<?php 2 3/* 4 * This file is part of the Assetic package, an OpenSky project. 5 * 6 * (c) 2010-2014 OpenSky Project Inc 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Assetic\Factory\Loader; 13 14use Assetic\Factory\AssetFactory; 15use Assetic\Factory\Resource\ResourceInterface; 16use Assetic\Util\FilesystemUtils; 17 18/** 19 * Loads asset formulae from PHP files. 20 * 21 * @author Kris Wallsmith <kris.wallsmith@gmail.com> 22 */ 23abstract class BasePhpFormulaLoader implements FormulaLoaderInterface 24{ 25 protected $factory; 26 protected $prototypes; 27 28 public function __construct(AssetFactory $factory) 29 { 30 $this->factory = $factory; 31 $this->prototypes = array(); 32 33 foreach ($this->registerPrototypes() as $prototype => $options) { 34 $this->addPrototype($prototype, $options); 35 } 36 } 37 38 public function addPrototype($prototype, array $options = array()) 39 { 40 $tokens = token_get_all('<?php '.$prototype); 41 array_shift($tokens); 42 43 $this->prototypes[$prototype] = array($tokens, $options); 44 } 45 46 public function load(ResourceInterface $resource) 47 { 48 if (!$nbProtos = count($this->prototypes)) { 49 throw new \LogicException('There are no prototypes registered.'); 50 } 51 52 $buffers = array_fill(0, $nbProtos, ''); 53 $bufferLevels = array_fill(0, $nbProtos, 0); 54 $buffersInWildcard = array(); 55 56 $tokens = token_get_all($resource->getContent()); 57 $calls = array(); 58 59 while ($token = array_shift($tokens)) { 60 $current = self::tokenToString($token); 61 // loop through each prototype (by reference) 62 foreach (array_keys($this->prototypes) as $i) { 63 $prototype = & $this->prototypes[$i][0]; 64 $options = $this->prototypes[$i][1]; 65 $buffer = & $buffers[$i]; 66 $level = & $bufferLevels[$i]; 67 68 if (isset($buffersInWildcard[$i])) { 69 switch ($current) { 70 case '(': ++$level; break; 71 case ')': --$level; break; 72 } 73 74 $buffer .= $current; 75 76 if (!$level) { 77 $calls[] = array($buffer.';', $options); 78 $buffer = ''; 79 unset($buffersInWildcard[$i]); 80 } 81 } elseif ($current == self::tokenToString(current($prototype))) { 82 $buffer .= $current; 83 if ('*' == self::tokenToString(next($prototype))) { 84 $buffersInWildcard[$i] = true; 85 ++$level; 86 } 87 } else { 88 reset($prototype); 89 unset($buffersInWildcard[$i]); 90 $buffer = ''; 91 } 92 } 93 } 94 95 $formulae = array(); 96 foreach ($calls as $call) { 97 $formulae += call_user_func_array(array($this, 'processCall'), $call); 98 } 99 100 return $formulae; 101 } 102 103 private function processCall($call, array $protoOptions = array()) 104 { 105 $tmp = FilesystemUtils::createTemporaryFile('php_formula_loader'); 106 file_put_contents($tmp, implode("\n", array( 107 '<?php', 108 $this->registerSetupCode(), 109 $call, 110 'echo serialize($_call);', 111 ))); 112 $args = unserialize(shell_exec('php '.escapeshellarg($tmp))); 113 unlink($tmp); 114 115 $inputs = isset($args[0]) ? self::argumentToArray($args[0]) : array(); 116 $filters = isset($args[1]) ? self::argumentToArray($args[1]) : array(); 117 $options = isset($args[2]) ? $args[2] : array(); 118 119 if (!isset($options['debug'])) { 120 $options['debug'] = $this->factory->isDebug(); 121 } 122 123 if (!is_array($options)) { 124 throw new \RuntimeException('The third argument must be omitted, null or an array.'); 125 } 126 127 // apply the prototype options 128 $options += $protoOptions; 129 130 if (!isset($options['name'])) { 131 $options['name'] = $this->factory->generateAssetName($inputs, $filters, $options); 132 } 133 134 return array($options['name'] => array($inputs, $filters, $options)); 135 } 136 137 /** 138 * Returns an array of prototypical calls and options. 139 * 140 * @return array Prototypes and options 141 */ 142 abstract protected function registerPrototypes(); 143 144 /** 145 * Returns setup code for the reflection scriptlet. 146 * 147 * @return string Some PHP setup code 148 */ 149 abstract protected function registerSetupCode(); 150 151 protected static function tokenToString($token) 152 { 153 return is_array($token) ? $token[1] : $token; 154 } 155 156 protected static function argumentToArray($argument) 157 { 158 return is_array($argument) ? $argument : array_filter(array_map('trim', explode(',', $argument))); 159 } 160} 161