PageRenderTime 26ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Symfony/Component/BrowserKit/Client.php

https://github.com/jalopezcar/symfony
PHP | 483 lines | 195 code | 54 blank | 234 comment | 20 complexity | 5a8df7d049a072477c219fd55c09b8d7 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  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 Symfony\Component\BrowserKit;
  11. use Symfony\Component\DomCrawler\Crawler;
  12. use Symfony\Component\DomCrawler\Link;
  13. use Symfony\Component\DomCrawler\Form;
  14. use Symfony\Component\Process\PhpProcess;
  15. use Symfony\Component\BrowserKit\Request;
  16. use Symfony\Component\BrowserKit\Response;
  17. use Symfony\Component\BrowserKit\Client;
  18. /**
  19. * Client simulates a browser.
  20. *
  21. * To make the actual request, you need to implement the doRequest() method.
  22. *
  23. * If you want to be able to run requests in their own process (insulated flag),
  24. * you need to also implement the getScript() method.
  25. *
  26. * @author Fabien Potencier <fabien@symfony.com>
  27. *
  28. * @api
  29. */
  30. abstract class Client
  31. {
  32. protected $history;
  33. protected $cookieJar;
  34. protected $server;
  35. protected $request;
  36. protected $response;
  37. protected $crawler;
  38. protected $insulated;
  39. protected $redirect;
  40. protected $followRedirects;
  41. /**
  42. * Constructor.
  43. *
  44. * @param array $server The server parameters (equivalent of $_SERVER)
  45. * @param History $history A History instance to store the browser history
  46. * @param CookieJar $cookieJar A CookieJar instance to store the cookies
  47. *
  48. * @api
  49. */
  50. public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null)
  51. {
  52. $this->setServerParameters($server);
  53. $this->history = null === $history ? new History() : $history;
  54. $this->cookieJar = null === $cookieJar ? new CookieJar() : $cookieJar;
  55. $this->insulated = false;
  56. $this->followRedirects = true;
  57. }
  58. /**
  59. * Sets whether to automatically follow redirects or not.
  60. *
  61. * @param Boolean $followRedirect Whether to follow redirects
  62. *
  63. * @api
  64. */
  65. public function followRedirects($followRedirect = true)
  66. {
  67. $this->followRedirects = (Boolean) $followRedirect;
  68. }
  69. /**
  70. * Sets the insulated flag.
  71. *
  72. * @param Boolean $insulated Whether to insulate the requests or not
  73. *
  74. * @throws \RuntimeException When Symfony Process Component is not installed
  75. *
  76. * @api
  77. */
  78. public function insulate($insulated = true)
  79. {
  80. if (!class_exists('Symfony\\Component\\Process\\Process')) {
  81. // @codeCoverageIgnoreStart
  82. throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.');
  83. // @codeCoverageIgnoreEnd
  84. }
  85. $this->insulated = (Boolean) $insulated;
  86. }
  87. /**
  88. * Sets server parameters.
  89. *
  90. * @param array $server An array of server parameters
  91. *
  92. * @api
  93. */
  94. public function setServerParameters(array $server)
  95. {
  96. $this->server = array_merge(array(
  97. 'HTTP_HOST' => 'localhost',
  98. 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3',
  99. ), $server);
  100. }
  101. /**
  102. * Sets single server parameter.
  103. *
  104. * @param string $key A key of the parameter
  105. * @param string $value A value of the parameter
  106. */
  107. public function setServerParameter($key, $value)
  108. {
  109. $this->server[$key] = $value;
  110. }
  111. /**
  112. * Gets single server parameter for specified key.
  113. *
  114. * @param string $key A key of the parameter to get
  115. * @param string $default A default value when key is undefined
  116. * @return string A value of the parameter
  117. */
  118. public function getServerParameter($key, $default = '')
  119. {
  120. return (isset($this->server[$key])) ? $this->server[$key] : $default;
  121. }
  122. /**
  123. * Returns the History instance.
  124. *
  125. * @return History A History instance
  126. *
  127. * @api
  128. */
  129. public function getHistory()
  130. {
  131. return $this->history;
  132. }
  133. /**
  134. * Returns the CookieJar instance.
  135. *
  136. * @return CookieJar A CookieJar instance
  137. *
  138. * @api
  139. */
  140. public function getCookieJar()
  141. {
  142. return $this->cookieJar;
  143. }
  144. /**
  145. * Returns the current Crawler instance.
  146. *
  147. * @return Crawler A Crawler instance
  148. *
  149. * @api
  150. */
  151. public function getCrawler()
  152. {
  153. return $this->crawler;
  154. }
  155. /**
  156. * Returns the current Response instance.
  157. *
  158. * @return Response A Response instance
  159. *
  160. * @api
  161. */
  162. public function getResponse()
  163. {
  164. return $this->response;
  165. }
  166. /**
  167. * Returns the current Request instance.
  168. *
  169. * @return Request A Request instance
  170. *
  171. * @api
  172. */
  173. public function getRequest()
  174. {
  175. return $this->request;
  176. }
  177. /**
  178. * Clicks on a given link.
  179. *
  180. * @param Link $link A Link instance
  181. *
  182. * @api
  183. */
  184. public function click(Link $link)
  185. {
  186. if ($link instanceof Form) {
  187. return $this->submit($link);
  188. }
  189. return $this->request($link->getMethod(), $link->getUri());
  190. }
  191. /**
  192. * Submits a form.
  193. *
  194. * @param Form $form A Form instance
  195. * @param array $values An array of form field values
  196. *
  197. * @api
  198. */
  199. public function submit(Form $form, array $values = array())
  200. {
  201. $form->setValues($values);
  202. return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles());
  203. }
  204. /**
  205. * Calls a URI.
  206. *
  207. * @param string $method The request method
  208. * @param string $uri The URI to fetch
  209. * @param array $parameters The Request parameters
  210. * @param array $files The files
  211. * @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does)
  212. * @param string $content The raw body data
  213. * @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
  214. *
  215. * @return Crawler
  216. *
  217. * @api
  218. */
  219. public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true)
  220. {
  221. $uri = $this->getAbsoluteUri($uri);
  222. $server = array_merge($this->server, $server);
  223. if (!$this->history->isEmpty()) {
  224. $server['HTTP_REFERER'] = $this->history->current()->getUri();
  225. }
  226. $server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST);
  227. $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME);
  228. $request = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
  229. $this->request = $this->filterRequest($request);
  230. if (true === $changeHistory) {
  231. $this->history->add($request);
  232. }
  233. if ($this->insulated) {
  234. $this->response = $this->doRequestInProcess($this->request);
  235. } else {
  236. $this->response = $this->doRequest($this->request);
  237. }
  238. $response = $this->filterResponse($this->response);
  239. $this->cookieJar->updateFromResponse($response, $uri);
  240. $this->redirect = $response->getHeader('Location');
  241. if ($this->followRedirects && $this->redirect) {
  242. return $this->crawler = $this->followRedirect();
  243. }
  244. return $this->crawler = $this->createCrawlerFromContent($request->getUri(), $response->getContent(), $response->getHeader('Content-Type'));
  245. }
  246. /**
  247. * Makes a request in another process.
  248. *
  249. * @param Request $request A Request instance
  250. *
  251. * @return Response A Response instance
  252. *
  253. * @throws \RuntimeException When processing returns exit code
  254. */
  255. protected function doRequestInProcess($request)
  256. {
  257. // We set the TMPDIR (for Macs) and TEMP (for Windows), because on these platforms the temp directory changes based on the user.
  258. $process = new PhpProcess($this->getScript($request), null, array('TMPDIR' => sys_get_temp_dir(), 'TEMP' => sys_get_temp_dir()));
  259. $process->run();
  260. if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) {
  261. throw new \RuntimeException($process->getErrorOutput());
  262. }
  263. return unserialize($process->getOutput());
  264. }
  265. /**
  266. * Makes a request.
  267. *
  268. * @param Request $request A Request instance
  269. *
  270. * @return Response A Response instance
  271. */
  272. abstract protected function doRequest($request);
  273. /**
  274. * Returns the script to execute when the request must be insulated.
  275. *
  276. * @param Request $request A Request instance
  277. *
  278. * @throws \LogicException When this abstract class is not implemented
  279. */
  280. protected function getScript($request)
  281. {
  282. // @codeCoverageIgnoreStart
  283. throw new \LogicException('To insulate requests, you need to override the getScript() method.');
  284. // @codeCoverageIgnoreEnd
  285. }
  286. /**
  287. * Filters the request.
  288. *
  289. * @param Request $request The request to filter
  290. *
  291. * @return Request
  292. */
  293. protected function filterRequest(Request $request)
  294. {
  295. return $request;
  296. }
  297. /**
  298. * Filters the Response.
  299. *
  300. * @param Response $response The Response to filter
  301. *
  302. * @return Response
  303. */
  304. protected function filterResponse($response)
  305. {
  306. return $response;
  307. }
  308. /**
  309. * Creates a crawler.
  310. *
  311. * @param string $uri A uri
  312. * @param string $content Content for the crawler to use
  313. * @param string $type Content type
  314. *
  315. * @return Crawler
  316. */
  317. protected function createCrawlerFromContent($uri, $content, $type)
  318. {
  319. $crawler = new Crawler(null, $uri);
  320. $crawler->addContent($content, $type);
  321. return $crawler;
  322. }
  323. /**
  324. * Goes back in the browser history.
  325. *
  326. * @return Crawler
  327. *
  328. * @api
  329. */
  330. public function back()
  331. {
  332. return $this->requestFromRequest($this->history->back(), false);
  333. }
  334. /**
  335. * Goes forward in the browser history.
  336. *
  337. * @return Crawler
  338. *
  339. * @api
  340. */
  341. public function forward()
  342. {
  343. return $this->requestFromRequest($this->history->forward(), false);
  344. }
  345. /**
  346. * Reloads the current browser.
  347. *
  348. * @return Crawler
  349. *
  350. * @api
  351. */
  352. public function reload()
  353. {
  354. return $this->requestFromRequest($this->history->current(), false);
  355. }
  356. /**
  357. * Follow redirects?
  358. *
  359. * @return Crawler
  360. *
  361. * @throws \LogicException If request was not a redirect
  362. *
  363. * @api
  364. */
  365. public function followRedirect()
  366. {
  367. if (empty($this->redirect)) {
  368. throw new \LogicException('The request was not redirected.');
  369. }
  370. return $this->request('get', $this->redirect);
  371. }
  372. /**
  373. * Restarts the client.
  374. *
  375. * It flushes history and all cookies.
  376. *
  377. * @api
  378. */
  379. public function restart()
  380. {
  381. $this->cookieJar->clear();
  382. $this->history->clear();
  383. }
  384. /**
  385. * Takes a URI and converts it to absolute if it is not already absolute.
  386. *
  387. * @param string $uri A uri
  388. * @return string An absolute uri
  389. */
  390. protected function getAbsoluteUri($uri)
  391. {
  392. // already absolute?
  393. if ('http' === substr($uri, 0, 4)) {
  394. return $uri;
  395. }
  396. if (!$this->history->isEmpty()) {
  397. $currentUri = $this->history->current()->getUri();
  398. } else {
  399. $currentUri = sprintf('http%s://%s/',
  400. isset($this->server['HTTPS']) ? 's' : '',
  401. isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost'
  402. );
  403. }
  404. // anchor?
  405. if (!$uri || '#' == $uri[0]) {
  406. return preg_replace('/#.*?$/', '', $currentUri).$uri;
  407. }
  408. if ('/' !== $uri[0]) {
  409. $path = parse_url($currentUri, PHP_URL_PATH);
  410. if ('/' !== substr($path, -1)) {
  411. $path = substr($path, 0, strrpos($path, '/') + 1);
  412. }
  413. $uri = $path.$uri;
  414. }
  415. return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri;
  416. }
  417. /**
  418. * Makes a request from a Request object directly.
  419. *
  420. * @param Request $request A Request instance
  421. * @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
  422. *
  423. * @return Crawler
  424. */
  425. protected function requestFromRequest(Request $request, $changeHistory = true)
  426. {
  427. return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), array(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
  428. }
  429. }