sfBrowser.class.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * sfBrowser simulates a fake browser which can surf a symfony application.
  11. *
  12. * @package symfony
  13. * @subpackage util
  14. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  15. * @version SVN: $Id: sfBrowser.class.php 15726 2009-02-23 15:56:12Z fabien $
  16. */
  17. class sfBrowser
  18. {
  19. protected
  20. $context = null,
  21. $hostname = null,
  22. $remote = null,
  23. $dom = null,
  24. $stack = array(),
  25. $stackPosition = -1,
  26. $cookieJar = array(),
  27. $fields = array(),
  28. $files = array(),
  29. $vars = array(),
  30. $defaultServerArray = array(),
  31. $headers = array(),
  32. $currentException = null,
  33. $rawConfiguration = null;
  34. /**
  35. * Class constructor.
  36. *
  37. * @param string $hostname Hostname to browse
  38. * @param string $remote Remote address to spook
  39. * @param array $options Options for sfBrowser
  40. *
  41. * @return void
  42. */
  43. public function __construct($hostname = null, $remote = null, $options = array())
  44. {
  45. $this->initialize($hostname, $remote, $options);
  46. }
  47. /**
  48. * Initializes sfBrowser - sets up environment
  49. *
  50. * @param string $hostname Hostname to browse
  51. * @param string $remote Remote address to spook
  52. * @param array $options Options for sfBrowser
  53. *
  54. * @return void
  55. */
  56. public function initialize($hostname = null, $remote = null, $options = array())
  57. {
  58. unset($_SERVER['argv']);
  59. unset($_SERVER['argc']);
  60. // setup our fake environment
  61. $this->hostname = $hostname;
  62. $this->remote = $remote;
  63. sfConfig::set('sf_path_info_array', 'SERVER');
  64. sfConfig::set('sf_test', true);
  65. // we set a session id (fake cookie / persistence)
  66. $this->newSession();
  67. // store default global $_SERVER array
  68. $this->defaultServerArray = $_SERVER;
  69. // register our shutdown function
  70. register_shutdown_function(array($this, 'shutdown'));
  71. }
  72. /**
  73. * Sets variable name
  74. *
  75. * @param string $name The variable name
  76. * @param mixed $value The value
  77. *
  78. * @return sfBrowser
  79. */
  80. public function setVar($name, $value)
  81. {
  82. $this->vars[$name] = $value;
  83. return $this;
  84. }
  85. /**
  86. * Sets a HTTP header for the very next request.
  87. *
  88. * @param string $header The header name
  89. * @param string $value The header value
  90. */
  91. public function setHttpHeader($header, $value)
  92. {
  93. $this->headers[$header] = $value;
  94. return $this;
  95. }
  96. /**
  97. * Sets username and password for simulating http authentication.
  98. *
  99. * @param string $username The username
  100. * @param string $password The password
  101. *
  102. * @return sfBrowser
  103. */
  104. public function setAuth($username, $password)
  105. {
  106. $this->vars['PHP_AUTH_USER'] = $username;
  107. $this->vars['PHP_AUTH_PW'] = $password;
  108. return $this;
  109. }
  110. /**
  111. * Gets a uri.
  112. *
  113. * @param string $uri The URI to fetch
  114. * @param array $parameters The Request parameters
  115. *
  116. * @return sfBrowser
  117. */
  118. public function get($uri, $parameters = array())
  119. {
  120. return $this->call($uri, 'get', $parameters);
  121. }
  122. /**
  123. * Posts a uri.
  124. *
  125. * @param string $uri The URI to fetch
  126. * @param array $parameters The Request parameters
  127. *
  128. * @return sfBrowser
  129. */
  130. public function post($uri, $parameters = array())
  131. {
  132. return $this->call($uri, 'post', $parameters);
  133. }
  134. /**
  135. * Calls a request to a uri.
  136. *
  137. * @param string $uri The URI to fetch
  138. * @param string $method The request method
  139. * @param array $parameters The Request parameters
  140. * @param bool $changeStack Change the browser history stack?
  141. *
  142. * @return sfBrowser
  143. */
  144. public function call($uri, $method = 'get', $parameters = array(), $changeStack = true)
  145. {
  146. // check that the previous call() hasn't returned an uncatched exception
  147. $this->checkCurrentExceptionIsEmpty();
  148. $uri = $this->fixUri($uri);
  149. // add uri to the stack
  150. if ($changeStack)
  151. {
  152. $this->stack = array_slice($this->stack, 0, $this->stackPosition + 1);
  153. $this->stack[] = array(
  154. 'uri' => $uri,
  155. 'method' => $method,
  156. 'parameters' => $parameters,
  157. );
  158. $this->stackPosition = count($this->stack) - 1;
  159. }
  160. list($path, $query_string) = false !== ($pos = strpos($uri, '?')) ? array(substr($uri, 0, $pos), substr($uri, $pos + 1)) : array($uri, '');
  161. $query_string = html_entity_decode($query_string);
  162. // remove anchor
  163. $path = preg_replace('/#.*/', '', $path);
  164. // removes all fields from previous request
  165. $this->fields = array();
  166. // prepare the request object
  167. $_SERVER = $this->defaultServerArray;
  168. $_SERVER['HTTP_HOST'] = $this->hostname ? $this->hostname : sfConfig::get('sf_app').'-'.sfConfig::get('sf_environment');
  169. $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];
  170. $_SERVER['SERVER_PORT'] = 80;
  171. $_SERVER['HTTP_USER_AGENT'] = 'PHP5/CLI';
  172. $_SERVER['REMOTE_ADDR'] = $this->remote ? $this->remote : '127.0.0.1';
  173. $_SERVER['REQUEST_METHOD'] = strtoupper($method);
  174. $_SERVER['PATH_INFO'] = $path;
  175. $_SERVER['REQUEST_URI'] = '/index.php'.$uri;
  176. $_SERVER['SCRIPT_NAME'] = '/index.php';
  177. $_SERVER['SCRIPT_FILENAME'] = '/index.php';
  178. $_SERVER['QUERY_STRING'] = $query_string;
  179. foreach ($this->vars as $key => $value)
  180. {
  181. $_SERVER[strtoupper($key)] = $value;
  182. }
  183. foreach ($this->headers as $header => $value)
  184. {
  185. $_SERVER['HTTP_'.strtoupper(str_replace('-', '_', $header))] = $value;
  186. }
  187. $this->headers = array();
  188. // request parameters
  189. $_GET = $_POST = array();
  190. if (strtoupper($method) == 'POST')
  191. {
  192. $_POST = $parameters;
  193. }
  194. if (strtoupper($method) == 'GET')
  195. {
  196. $_GET = $parameters;
  197. }
  198. // handle input type="file" fields
  199. if (count($this->files))
  200. {
  201. $_FILES = $this->files;
  202. }
  203. $this->files = array();
  204. parse_str($query_string, $qs);
  205. if (is_array($qs))
  206. {
  207. $_GET = array_merge($qs, $_GET);
  208. }
  209. // restore cookies
  210. $_COOKIE = array();
  211. foreach ($this->cookieJar as $name => $cookie)
  212. {
  213. $_COOKIE[$name] = $cookie['value'];
  214. }
  215. ob_start();
  216. // recycle our context object
  217. $this->context = $this->getContext(true);
  218. // launch request via controller
  219. $controller = $this->context->getController();
  220. $request = $this->context->getRequest();
  221. $response = $this->context->getResponse();
  222. // we register a fake rendering filter
  223. sfConfig::set('sf_rendering_filter', array('sfFakeRenderingFilter', null));
  224. $this->currentException = null;
  225. // dispatch our request
  226. $controller->dispatch();
  227. $retval = ob_get_clean();
  228. // append retval to the response content
  229. $response->setContent($retval);
  230. // manually shutdown user to save current session data
  231. $this->context->getUser()->shutdown();
  232. $this->context->getStorage()->shutdown();
  233. // save cookies
  234. $this->cookieJar = array();
  235. foreach ($response->getCookies() as $name => $cookie)
  236. {
  237. // FIXME: deal with expire, path, secure, ...
  238. $this->cookieJar[$name] = $cookie;
  239. }
  240. // support for the ETag header
  241. if ($etag = $this->context->getResponse()->getHttpHeader('Etag'))
  242. {
  243. $this->vars['HTTP_IF_NONE_MATCH'] = $etag;
  244. }
  245. else
  246. {
  247. unset($this->vars['HTTP_IF_NONE_MATCH']);
  248. }
  249. // support for the last modified header
  250. if ($lastModified = $this->context->getResponse()->getHttpHeader('Last-Modified'))
  251. {
  252. $this->vars['HTTP_IF_MODIFIED_SINCE'] = $lastModified;
  253. }
  254. else
  255. {
  256. unset($this->vars['HTTP_IF_MODIFIED_SINCE']);
  257. }
  258. // for HTML/XML content, create a DOM and sfDomCssSelector objects for the response content
  259. if (preg_match('/(x|ht)ml/i', $response->getContentType(), $matches))
  260. {
  261. $this->dom = new DomDocument('1.0', sfConfig::get('sf_charset'));
  262. $this->dom->validateOnParse = true;
  263. if ('x' == $matches[1])
  264. {
  265. @$this->dom->loadXML($response->getContent());
  266. }
  267. else
  268. {
  269. @$this->dom->loadHTML($response->getContent());
  270. }
  271. $this->domCssSelector = new sfDomCssSelector($this->dom);
  272. }
  273. else
  274. {
  275. $this->dom = null;
  276. $this->domCssSelector = null;
  277. }
  278. return $this;
  279. }
  280. /**
  281. * Go back in the browser history stack.
  282. *
  283. * @return sfBrowser
  284. */
  285. public function back()
  286. {
  287. if ($this->stackPosition < 1)
  288. {
  289. throw new sfException('You are already on the first page.');
  290. }
  291. --$this->stackPosition;
  292. return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);
  293. }
  294. /**
  295. * Go forward in the browser history stack.
  296. *
  297. * @return sfBrowser
  298. */
  299. public function forward()
  300. {
  301. if ($this->stackPosition > count($this->stack) - 2)
  302. {
  303. throw new sfException('You are already on the last page.');
  304. }
  305. ++$this->stackPosition;
  306. return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);
  307. }
  308. /**
  309. * Reload the current browser.
  310. *
  311. * @return sfBrowser
  312. */
  313. public function reload()
  314. {
  315. if (-1 == $this->stackPosition)
  316. {
  317. throw new sfException('No page to reload.');
  318. }
  319. return $this->call($this->stack[$this->stackPosition]['uri'], $this->stack[$this->stackPosition]['method'], $this->stack[$this->stackPosition]['parameters'], false);
  320. }
  321. /**
  322. * Get response dom css selector.
  323. *
  324. * @return sfDomCssSelector
  325. */
  326. public function getResponseDomCssSelector()
  327. {
  328. if (is_null($this->dom))
  329. {
  330. throw new sfException('The DOM is not accessible because the browser response content type is not HTML.');
  331. }
  332. return $this->domCssSelector;
  333. }
  334. /**
  335. * Get response dom.
  336. *
  337. * @return sfDomCssSelector
  338. */
  339. public function getResponseDom()
  340. {
  341. if (is_null($this->dom))
  342. {
  343. throw new sfException('The DOM is not accessible because the browser response content type is not HTML.');
  344. }
  345. return $this->dom;
  346. }
  347. /**
  348. * Returns the current application context.
  349. *
  350. * @param bool $forceReload true to force context reload, false otherwise
  351. *
  352. * @return sfContext
  353. */
  354. public function getContext($forceReload = false)
  355. {
  356. if (is_null($this->context) || $forceReload)
  357. {
  358. $isContextEmpty = is_null($this->context);
  359. $context = $isContextEmpty ? sfContext::getInstance() : $this->context;
  360. $currentConfiguration = $context->getConfiguration();
  361. $configuration = ProjectConfiguration::getApplicationConfiguration($currentConfiguration->getApplication(), $currentConfiguration->getEnvironment(), $currentConfiguration->isDebug());
  362. $this->context = sfContext::createInstance($configuration);
  363. unset($currentConfiguration);
  364. if (!$isContextEmpty)
  365. {
  366. sfConfig::clear();
  367. sfConfig::add($this->rawConfiguration);
  368. }
  369. else
  370. {
  371. $this->rawConfiguration = sfConfig::getAll();
  372. }
  373. $this->context->getEventDispatcher()->connect('application.throw_exception', array($this, 'ListenToException'));
  374. }
  375. return $this->context;
  376. }
  377. /**
  378. * Gets response.
  379. *
  380. * @return sfWebResponse
  381. */
  382. public function getResponse()
  383. {
  384. return $this->context->getResponse();
  385. }
  386. /**
  387. * Gets request.
  388. *
  389. * @return sfWebRequest
  390. */
  391. public function getRequest()
  392. {
  393. return $this->context->getRequest();
  394. }
  395. /**
  396. * Gets current exception.
  397. *
  398. * @return sfException
  399. */
  400. public function getCurrentException()
  401. {
  402. return $this->currentException;
  403. }
  404. /**
  405. * Resets the current exception.
  406. */
  407. public function resetCurrentException()
  408. {
  409. $this->currentException = null;
  410. }
  411. /**
  412. * Test for an uncaught exception.
  413. *
  414. * @return boolean
  415. */
  416. public function checkCurrentExceptionIsEmpty()
  417. {
  418. return is_null($this->getCurrentException()) || $this->getCurrentException() instanceof sfError404Exception;
  419. }
  420. /**
  421. * Follow redirects?
  422. *
  423. * @throws sfException If request was not a redirect
  424. *
  425. * @return sfBrowser
  426. */
  427. public function followRedirect()
  428. {
  429. if (null === $this->context->getResponse()->getHttpHeader('Location'))
  430. {
  431. throw new sfException('The request was not redirected.');
  432. }
  433. return $this->get($this->context->getResponse()->getHttpHeader('Location'));
  434. }
  435. /**
  436. * Sets a form field in the browser.
  437. *
  438. * @param string $name The field name
  439. * @param string $value The field value
  440. *
  441. * @return sfBrowser
  442. */
  443. public function setField($name, $value)
  444. {
  445. // as we don't know yet the form, just store name/value pairs
  446. $this->parseArgumentAsArray($name, $value, $this->fields);
  447. return $this;
  448. }
  449. /**
  450. * Simulates a click on a link or button.
  451. *
  452. * @param string $name The link or button text
  453. * @param array $arguments
  454. *
  455. * @return sfBrowser
  456. */
  457. public function click($name, $arguments = array())
  458. {
  459. $dom = $this->getResponseDom();
  460. if (!$dom)
  461. {
  462. throw new sfException('Cannot click because there is no current page in the browser.');
  463. }
  464. $xpath = new DomXpath($dom);
  465. // text link
  466. if ($link = $xpath->query(sprintf('//a[.="%s"]', $name))->item(0))
  467. {
  468. return $this->get($link->getAttribute('href'));
  469. }
  470. // image link
  471. if ($link = $xpath->query(sprintf('//a/img[@alt="%s"]/ancestor::a', $name))->item(0))
  472. {
  473. return $this->get($link->getAttribute('href'));
  474. }
  475. // form
  476. if (!$form = $xpath->query(sprintf('//input[((@type="submit" or @type="button") and @value="%s") or (@type="image" and @alt="%s")]/ancestor::form', $name, $name))->item(0))
  477. {
  478. if (!$form = $xpath->query(sprintf('//button[.="%s" or @id="%s" or @name="%s"]/ancestor::form', $name, $name, $name))->item(0))
  479. {
  480. throw new sfException(sprintf('Cannot find the "%s" link or button.', $name));
  481. }
  482. }
  483. // form attributes
  484. $url = $form->getAttribute('action');
  485. $method = $form->getAttribute('method') ? strtolower($form->getAttribute('method')) : 'get';
  486. // merge form default values and arguments
  487. $defaults = array();
  488. $arguments = sfToolkit::arrayDeepMerge($this->fields, $arguments);
  489. foreach ($xpath->query('descendant::input | descendant::textarea | descendant::select', $form) as $element)
  490. {
  491. $elementName = $element->getAttribute('name');
  492. $nodeName = $element->nodeName;
  493. $value = null;
  494. if ($nodeName == 'input' && ($element->getAttribute('type') == 'checkbox' || $element->getAttribute('type') == 'radio'))
  495. {
  496. if ($element->getAttribute('checked'))
  497. {
  498. $value = $element->hasAttribute('value') ? $element->getAttribute('value') : '1';
  499. }
  500. }
  501. else if ($nodeName == 'input' && $element->getAttribute('type') == 'file')
  502. {
  503. $ph = new sfParameterHolder();
  504. $ph->add($arguments);
  505. $filename = $ph->get($elementName, '');
  506. if (is_readable($filename))
  507. {
  508. $fileError = UPLOAD_ERR_OK;
  509. $fileSize = filesize($filename);
  510. }
  511. else
  512. {
  513. $fileError = UPLOAD_ERR_NO_FILE;
  514. $fileSize = 0;
  515. }
  516. $ph->remove($elementName);
  517. $arguments = $ph->getAll();
  518. $this->parseArgumentAsArray($elementName, array('name' => basename($filename), 'type' => '', 'tmp_name' => $filename, 'error' => $fileError, 'size' => $fileSize), $this->files);
  519. }
  520. else if (
  521. $nodeName == 'input'
  522. &&
  523. (($element->getAttribute('type') != 'submit' && $element->getAttribute('type') != 'button') || $element->getAttribute('value') == $name)
  524. &&
  525. ($element->getAttribute('type') != 'image' || $element->getAttribute('alt') == $name)
  526. )
  527. {
  528. $value = $element->getAttribute('value');
  529. }
  530. else if ($nodeName == 'textarea')
  531. {
  532. $value = '';
  533. foreach ($element->childNodes as $el)
  534. {
  535. $value .= $dom->saveXML($el);
  536. }
  537. }
  538. else if ($nodeName == 'select')
  539. {
  540. if ($multiple = $element->hasAttribute('multiple'))
  541. {
  542. $elementName = str_replace('[]', '', $elementName);
  543. $value = array();
  544. }
  545. else
  546. {
  547. $value = null;
  548. }
  549. $found = false;
  550. foreach ($xpath->query('descendant::option', $element) as $option)
  551. {
  552. if ($option->getAttribute('selected'))
  553. {
  554. $found = true;
  555. if ($multiple)
  556. {
  557. $value[] = $option->getAttribute('value');
  558. }
  559. else
  560. {
  561. $value = $option->getAttribute('value');
  562. }
  563. }
  564. }
  565. // if no option is selected and if it is a simple select box, take the first option as the value
  566. $option = $xpath->query('descendant::option', $element)->item(0);
  567. if (!$found && !$multiple && $option instanceof DOMElement)
  568. {
  569. $value = $option->getAttribute('value');
  570. }
  571. }
  572. if (null !== $value)
  573. {
  574. $this->parseArgumentAsArray($elementName, $value, $defaults);
  575. }
  576. }
  577. // create request parameters
  578. $arguments = sfToolkit::arrayDeepMerge($defaults, $arguments);
  579. if ('post' == $method)
  580. {
  581. return $this->post($url, $arguments);
  582. }
  583. else
  584. {
  585. $query_string = http_build_query($arguments, null, '&');
  586. $sep = false === strpos($url, '?') ? '?' : '&';
  587. return $this->get($url.($query_string ? $sep.$query_string : ''));
  588. }
  589. }
  590. /**
  591. * Parses arguments as array
  592. *
  593. * @param string $name The argument name
  594. * @param string $value The argument value
  595. * @param array $vars
  596. */
  597. protected function parseArgumentAsArray($name, $value, &$vars)
  598. {
  599. if (false !== $pos = strpos($name, '['))
  600. {
  601. $var = &$vars;
  602. $tmps = array_filter(preg_split('/(\[ | \[\] | \])/x', $name), create_function('$s', 'return $s !== "";'));
  603. foreach ($tmps as $tmp)
  604. {
  605. $var = &$var[$tmp];
  606. }
  607. if ($var)
  608. {
  609. if (!is_array($var))
  610. {
  611. $var = array($var);
  612. }
  613. $var[] = $value;
  614. }
  615. else
  616. {
  617. $var = $value;
  618. }
  619. }
  620. else
  621. {
  622. $vars[$name] = $value;
  623. }
  624. }
  625. /**
  626. * Reset browser to original state
  627. *
  628. * @return sfBrowser
  629. */
  630. public function restart()
  631. {
  632. $this->newSession();
  633. $this->cookieJar = array();
  634. $this->stack = array();
  635. $this->fields = array();
  636. $this->vars = array();
  637. $this->dom = null;
  638. $this->stackPosition = -1;
  639. return $this;
  640. }
  641. /**
  642. * Shutdown function to clean up and remove sessions
  643. *
  644. * @return void
  645. */
  646. public function shutdown()
  647. {
  648. $this->checkCurrentExceptionIsEmpty();
  649. // we remove all session data
  650. sfToolkit::clearDirectory(sfConfig::get('sf_test_cache_dir').'/sessions');
  651. }
  652. /**
  653. * Fixes uri removing # declarations and front controller.
  654. *
  655. * @param string $uri The URI to fix
  656. * @return string The fixed uri
  657. */
  658. protected function fixUri($uri)
  659. {
  660. // remove absolute information if needed (to be able to do follow redirects, click on links, ...)
  661. if (0 === strpos($uri, 'http'))
  662. {
  663. // detect secure request
  664. if (0 === strpos($uri, 'https'))
  665. {
  666. $this->defaultServerArray['HTTPS'] = 'on';
  667. }
  668. else
  669. {
  670. unset($this->defaultServerArray['HTTPS']);
  671. }
  672. $uri = substr($uri, strpos($uri, 'index.php') + strlen('index.php'));
  673. }
  674. $uri = str_replace('/index.php', '', $uri);
  675. // # as a uri
  676. if ($uri && '#' == $uri[0])
  677. {
  678. $uri = $this->stack[$this->stackPosition]['uri'].$uri;
  679. }
  680. return $uri;
  681. }
  682. /**
  683. * Creates a new session in the browser.
  684. *
  685. * @return void
  686. */
  687. protected function newSession()
  688. {
  689. $this->defaultServerArray['session_id'] = $_SERVER['session_id'] = md5(uniqid(rand(), true));
  690. }
  691. /**
  692. * Listener for exceptions
  693. *
  694. * @param sfEvent $event The event to handle
  695. *
  696. * @return void
  697. */
  698. public function listenToException(sfEvent $event)
  699. {
  700. $this->currentException = $event->getSubject();
  701. }
  702. }
  703. class sfFakeRenderingFilter extends sfFilter
  704. {
  705. public function execute($filterChain)
  706. {
  707. $filterChain->execute();
  708. $this->context->getResponse()->sendContent();
  709. }
  710. }