1. sfClassManipulator.class.php
  2. /** * sfClassManipulator manipulates class code. * * @package symfony * @subpackage util * @author Fabien Potencier * @version SVN: $Id: sfClassManipulator.class.php 25063 2009-12-08 06:02:07Z Kris.Wallsmith $ */
  3. class sfClassManipulator
  4. {
  5. static protected $signatureTokens = array(
  6. T_FINAL,
  7. T_ABSTRACT,
  8. T_STATIC,
  9. T_PUBLIC,
  10. T_PROTECTED,
  11. T_PRIVATE,
  12. T_FUNCTION,
  13. );
  14. protected $code = '', $file = false;
  15. /**
  16. * Constructor.
  17. *
  18. * @param string $code The code to manipulate
  19. */
  20. public function __construct($code)
  21. {
  22. $this->code = $code;
  23. }
  24. /**
  25. * Creates a manipulator object from a file.
  26. *
  27. * @param string $file A file name
  28. *
  29. * @return sfClassManipulator A sfClassManipulator instance
  30. */
  31. static public function fromFile($file)
  32. {
  33. $manipulator = new self(file_get_contents($file));
  34. $manipulator->setFile($file);
  35. return $manipulator;
  36. }
  37. /**
  38. * Saves the code back to the associated file.
  39. *
  40. * This only works if you have bound the instance with a file with the setFile() method.
  41. *
  42. * @throw LogicException if no file is associated with the instance
  43. */
  44. public function save()
  45. {
  46. if (!$this->file)
  47. {
  48. throw new LogicException('Unable to save the code as no file has been provided.');
  49. }
  50. file_put_contents($this->file, $this->code);
  51. }
  52. /**
  53. * Gets the modified code.
  54. *
  55. * @return string The modified code
  56. */
  57. public function getCode()
  58. {
  59. return $this->code;
  60. }
  61. /**
  62. * Gets the associated file.
  63. *
  64. * @return string The associated file
  65. */
  66. public function getFile()
  67. {
  68. return $this->file;
  69. }
  70. /**
  71. * Sets the file associated with this instance.
  72. *
  73. * @param string A file name
  74. */
  75. public function setFile($file)
  76. {
  77. $this->file = $file;
  78. }
  79. /**
  80. * Wraps an existing method with some code.
  81. *
  82. * @param string $method The method name to change
  83. * @param string $topCode The code to add at the top of the method
  84. * @param string $bottomCode The code to add at the bottom of the method
  85. */
  86. public function wrapMethod($method, $topCode = '', $bottomCode = '')
  87. {
  88. $code = '';
  89. $insideSetup = -1;
  90. $parens = 0;
  91. foreach (token_get_all($this->code) as $token)
  92. {
  93. if (isset($token[1]))
  94. {
  95. if (-1 == $insideSetup && T_FUNCTION == $token[0])
  96. {
  97. $insideSetup = 0;
  98. }
  99. elseif (0 == $insideSetup && T_STRING == $token[0])
  100. {
  101. $insideSetup = $method == $token[1] ? 1 : -1;
  102. }
  103. $code .= $token[1];
  104. }
  105. else
  106. {
  107. if (1 == $insideSetup && '{' == $token)
  108. {
  109. if (!$parens)
  110. {
  111. $code .= $topCode ? $token.PHP_EOL.' '.$topCode : $token;
  112. }
  113. else
  114. {
  115. $code .= $token;
  116. }
  117. ++$parens;
  118. }
  119. elseif (1 == $insideSetup && '}' == $token)
  120. {
  121. --$parens;
  122. if (!$parens)
  123. {
  124. $insideSetup = -1;
  125. $code .= $bottomCode ? ' '.$bottomCode.PHP_EOL.' '.$token : $token;
  126. }
  127. else
  128. {
  129. $code .= $token;
  130. }
  131. }
  132. else
  133. {
  134. $code .= $token;
  135. }
  136. }
  137. }
  138. return $this->code = $code;
  139. }
  140. /**
  141. * Filters each line of the given method through a callable.
  142. *
  143. * @param string $method The method name
  144. * @param mixed $callable A PHP callable that accepts and returns one line of PHP code
  145. */
  146. public function filterMethod($method, $callable)
  147. {
  148. $line = '';
  149. $code = '';
  150. $insideSetup = -1;
  151. $parens = 0;
  152. $break = false;
  153. $tokens = token_get_all($this->code);
  154. for ($i = 0; $i < count($tokens); $i++)
  155. {
  156. $token = $tokens[$i];
  157. if (is_array($token))
  158. {
  159. $line .= $token[1];
  160. if (-1 == $insideSetup && T_FUNCTION == $token[0])
  161. {
  162. $insideSetup = 0;
  163. }
  164. elseif (0 == $insideSetup && T_STRING == $token[0])
  165. {
  166. $insideSetup = $method == $token[1] ? 1 : -1;
  167. }
  168. }
  169. else
  170. {
  171. if (1 == $insideSetup && '{' == $token)
  172. {
  173. ++$parens;
  174. }
  175. elseif (1 == $insideSetup && '}' == $token)
  176. {
  177. --$parens;
  178. if (!$parens)
  179. {
  180. $break = true;
  181. }
  182. }
  183. $line .= $token;
  184. }
  185. $lines = preg_split('/(\r?\n)/', $line, null, PREG_SPLIT_DELIM_CAPTURE);
  186. if (count($lines) > 1 || $break)
  187. {
  188. $line = $break ? '' : array_pop($lines);
  189. foreach (array_chunk($lines, 2) as $chunk)
  190. {
  191. list($l, $eol) = array_pad($chunk, 2, '');
  192. if (1 == $insideSetup)
  193. {
  194. list($before, $setup) = $this->splitSetup($l);
  195. $code .= $before;
  196. $code .= call_user_func($callable, $setup.$eol);
  197. }
  198. else
  199. {
  200. $code .= $l.$eol;
  201. }
  202. }
  203. }
  204. if ($break)
  205. {
  206. $insideSetup = -1;
  207. $break = false;
  208. }
  209. }
  210. if ($line)
  211. {
  212. $code .= $line;
  213. }
  214. return $this->code = $code;
  215. }
  216. protected function splitSetup($line)
  217. {
  218. $before = '';
  219. $setup = '';
  220. if ($line)
  221. {
  222. if (false === stripos($line, '<?php'))
  223. {
  224. // add a function so we can accurately slice
  225. $tokens = token_get_all('<?php function'.$line);
  226. $tokens = array_slice($tokens, 2);
  227. }
  228. else
  229. {
  230. $tokens = token_get_all($line);
  231. }
  232. // we're in reverse
  233. $inSignature = false;
  234. while ($token = array_pop($tokens))
  235. {
  236. $value = $this->getTokenValue($token);
  237. if (is_array($token) && in_array($token[0], self::$signatureTokens))
  238. {
  239. $inSignature = true;
  240. }
  241. elseif ($inSignature && !preg_match('/\s+/', $value))
  242. {
  243. // clean up
  244. preg_match('/^\s*/', $setup, $match);
  245. $before = implode('', array_map(array($this, 'getTokenValue'), $tokens)).$value.$match[0];
  246. $setup = substr($setup, strlen($match[0]));
  247. return array($before, $setup);
  248. }
  249. $setup = $value.$setup;
  250. }
  251. }
  252. return array($before, $setup);
  253. }
  254. /**
  255. * Returns a token's string value.
  256. *
  257. * @param array|string $token
  258. *
  259. * @return string
  260. */
  261. protected function getTokenValue($token)
  262. {
  263. return is_array($token) ? $token[1] : $token;
  264. }
  265. }

Debug toolbar