Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\BrowserKit;
|
Chris@0
|
13
|
Chris@0
|
14 use Symfony\Component\DomCrawler\Crawler;
|
Chris@0
|
15 use Symfony\Component\DomCrawler\Link;
|
Chris@0
|
16 use Symfony\Component\DomCrawler\Form;
|
Chris@0
|
17 use Symfony\Component\Process\PhpProcess;
|
Chris@0
|
18
|
Chris@0
|
19 /**
|
Chris@0
|
20 * Client simulates a browser.
|
Chris@0
|
21 *
|
Chris@0
|
22 * To make the actual request, you need to implement the doRequest() method.
|
Chris@0
|
23 *
|
Chris@0
|
24 * If you want to be able to run requests in their own process (insulated flag),
|
Chris@0
|
25 * you need to also implement the getScript() method.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @author Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
28 */
|
Chris@0
|
29 abstract class Client
|
Chris@0
|
30 {
|
Chris@0
|
31 protected $history;
|
Chris@0
|
32 protected $cookieJar;
|
Chris@0
|
33 protected $server = array();
|
Chris@0
|
34 protected $internalRequest;
|
Chris@0
|
35 protected $request;
|
Chris@0
|
36 protected $internalResponse;
|
Chris@0
|
37 protected $response;
|
Chris@0
|
38 protected $crawler;
|
Chris@0
|
39 protected $insulated = false;
|
Chris@0
|
40 protected $redirect;
|
Chris@0
|
41 protected $followRedirects = true;
|
Chris@0
|
42
|
Chris@0
|
43 private $maxRedirects = -1;
|
Chris@0
|
44 private $redirectCount = 0;
|
Chris@12
|
45 private $redirects = array();
|
Chris@0
|
46 private $isMainRequest = true;
|
Chris@0
|
47
|
Chris@0
|
48 /**
|
Chris@0
|
49 * @param array $server The server parameters (equivalent of $_SERVER)
|
Chris@0
|
50 * @param History $history A History instance to store the browser history
|
Chris@0
|
51 * @param CookieJar $cookieJar A CookieJar instance to store the cookies
|
Chris@0
|
52 */
|
Chris@0
|
53 public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null)
|
Chris@0
|
54 {
|
Chris@0
|
55 $this->setServerParameters($server);
|
Chris@0
|
56 $this->history = $history ?: new History();
|
Chris@0
|
57 $this->cookieJar = $cookieJar ?: new CookieJar();
|
Chris@0
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 /**
|
Chris@0
|
61 * Sets whether to automatically follow redirects or not.
|
Chris@0
|
62 *
|
Chris@0
|
63 * @param bool $followRedirect Whether to follow redirects
|
Chris@0
|
64 */
|
Chris@0
|
65 public function followRedirects($followRedirect = true)
|
Chris@0
|
66 {
|
Chris@0
|
67 $this->followRedirects = (bool) $followRedirect;
|
Chris@0
|
68 }
|
Chris@0
|
69
|
Chris@0
|
70 /**
|
Chris@0
|
71 * Returns whether client automatically follows redirects or not.
|
Chris@0
|
72 *
|
Chris@0
|
73 * @return bool
|
Chris@0
|
74 */
|
Chris@0
|
75 public function isFollowingRedirects()
|
Chris@0
|
76 {
|
Chris@0
|
77 return $this->followRedirects;
|
Chris@0
|
78 }
|
Chris@0
|
79
|
Chris@0
|
80 /**
|
Chris@13
|
81 * Sets the maximum number of redirects that crawler can follow.
|
Chris@0
|
82 *
|
Chris@0
|
83 * @param int $maxRedirects
|
Chris@0
|
84 */
|
Chris@0
|
85 public function setMaxRedirects($maxRedirects)
|
Chris@0
|
86 {
|
Chris@0
|
87 $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects;
|
Chris@0
|
88 $this->followRedirects = -1 != $this->maxRedirects;
|
Chris@0
|
89 }
|
Chris@0
|
90
|
Chris@0
|
91 /**
|
Chris@13
|
92 * Returns the maximum number of redirects that crawler can follow.
|
Chris@0
|
93 *
|
Chris@0
|
94 * @return int
|
Chris@0
|
95 */
|
Chris@0
|
96 public function getMaxRedirects()
|
Chris@0
|
97 {
|
Chris@0
|
98 return $this->maxRedirects;
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 /**
|
Chris@0
|
102 * Sets the insulated flag.
|
Chris@0
|
103 *
|
Chris@0
|
104 * @param bool $insulated Whether to insulate the requests or not
|
Chris@0
|
105 *
|
Chris@0
|
106 * @throws \RuntimeException When Symfony Process Component is not installed
|
Chris@0
|
107 */
|
Chris@0
|
108 public function insulate($insulated = true)
|
Chris@0
|
109 {
|
Chris@0
|
110 if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) {
|
Chris@0
|
111 throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.');
|
Chris@0
|
112 }
|
Chris@0
|
113
|
Chris@0
|
114 $this->insulated = (bool) $insulated;
|
Chris@0
|
115 }
|
Chris@0
|
116
|
Chris@0
|
117 /**
|
Chris@0
|
118 * Sets server parameters.
|
Chris@0
|
119 *
|
Chris@0
|
120 * @param array $server An array of server parameters
|
Chris@0
|
121 */
|
Chris@0
|
122 public function setServerParameters(array $server)
|
Chris@0
|
123 {
|
Chris@0
|
124 $this->server = array_merge(array(
|
Chris@0
|
125 'HTTP_USER_AGENT' => 'Symfony BrowserKit',
|
Chris@0
|
126 ), $server);
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 /**
|
Chris@0
|
130 * Sets single server parameter.
|
Chris@0
|
131 *
|
Chris@0
|
132 * @param string $key A key of the parameter
|
Chris@0
|
133 * @param string $value A value of the parameter
|
Chris@0
|
134 */
|
Chris@0
|
135 public function setServerParameter($key, $value)
|
Chris@0
|
136 {
|
Chris@0
|
137 $this->server[$key] = $value;
|
Chris@0
|
138 }
|
Chris@0
|
139
|
Chris@0
|
140 /**
|
Chris@0
|
141 * Gets single server parameter for specified key.
|
Chris@0
|
142 *
|
Chris@0
|
143 * @param string $key A key of the parameter to get
|
Chris@0
|
144 * @param string $default A default value when key is undefined
|
Chris@0
|
145 *
|
Chris@0
|
146 * @return string A value of the parameter
|
Chris@0
|
147 */
|
Chris@0
|
148 public function getServerParameter($key, $default = '')
|
Chris@0
|
149 {
|
Chris@0
|
150 return isset($this->server[$key]) ? $this->server[$key] : $default;
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 /**
|
Chris@0
|
154 * Returns the History instance.
|
Chris@0
|
155 *
|
Chris@0
|
156 * @return History A History instance
|
Chris@0
|
157 */
|
Chris@0
|
158 public function getHistory()
|
Chris@0
|
159 {
|
Chris@0
|
160 return $this->history;
|
Chris@0
|
161 }
|
Chris@0
|
162
|
Chris@0
|
163 /**
|
Chris@0
|
164 * Returns the CookieJar instance.
|
Chris@0
|
165 *
|
Chris@0
|
166 * @return CookieJar A CookieJar instance
|
Chris@0
|
167 */
|
Chris@0
|
168 public function getCookieJar()
|
Chris@0
|
169 {
|
Chris@0
|
170 return $this->cookieJar;
|
Chris@0
|
171 }
|
Chris@0
|
172
|
Chris@0
|
173 /**
|
Chris@0
|
174 * Returns the current Crawler instance.
|
Chris@0
|
175 *
|
Chris@0
|
176 * @return Crawler|null A Crawler instance
|
Chris@0
|
177 */
|
Chris@0
|
178 public function getCrawler()
|
Chris@0
|
179 {
|
Chris@0
|
180 return $this->crawler;
|
Chris@0
|
181 }
|
Chris@0
|
182
|
Chris@0
|
183 /**
|
Chris@0
|
184 * Returns the current BrowserKit Response instance.
|
Chris@0
|
185 *
|
Chris@0
|
186 * @return Response|null A BrowserKit Response instance
|
Chris@0
|
187 */
|
Chris@0
|
188 public function getInternalResponse()
|
Chris@0
|
189 {
|
Chris@0
|
190 return $this->internalResponse;
|
Chris@0
|
191 }
|
Chris@0
|
192
|
Chris@0
|
193 /**
|
Chris@0
|
194 * Returns the current origin response instance.
|
Chris@0
|
195 *
|
Chris@0
|
196 * The origin response is the response instance that is returned
|
Chris@0
|
197 * by the code that handles requests.
|
Chris@0
|
198 *
|
Chris@0
|
199 * @return object|null A response instance
|
Chris@0
|
200 *
|
Chris@0
|
201 * @see doRequest()
|
Chris@0
|
202 */
|
Chris@0
|
203 public function getResponse()
|
Chris@0
|
204 {
|
Chris@0
|
205 return $this->response;
|
Chris@0
|
206 }
|
Chris@0
|
207
|
Chris@0
|
208 /**
|
Chris@0
|
209 * Returns the current BrowserKit Request instance.
|
Chris@0
|
210 *
|
Chris@0
|
211 * @return Request|null A BrowserKit Request instance
|
Chris@0
|
212 */
|
Chris@0
|
213 public function getInternalRequest()
|
Chris@0
|
214 {
|
Chris@0
|
215 return $this->internalRequest;
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 /**
|
Chris@0
|
219 * Returns the current origin Request instance.
|
Chris@0
|
220 *
|
Chris@0
|
221 * The origin request is the request instance that is sent
|
Chris@0
|
222 * to the code that handles requests.
|
Chris@0
|
223 *
|
Chris@0
|
224 * @return object|null A Request instance
|
Chris@0
|
225 *
|
Chris@0
|
226 * @see doRequest()
|
Chris@0
|
227 */
|
Chris@0
|
228 public function getRequest()
|
Chris@0
|
229 {
|
Chris@0
|
230 return $this->request;
|
Chris@0
|
231 }
|
Chris@0
|
232
|
Chris@0
|
233 /**
|
Chris@0
|
234 * Clicks on a given link.
|
Chris@0
|
235 *
|
Chris@0
|
236 * @return Crawler
|
Chris@0
|
237 */
|
Chris@0
|
238 public function click(Link $link)
|
Chris@0
|
239 {
|
Chris@0
|
240 if ($link instanceof Form) {
|
Chris@0
|
241 return $this->submit($link);
|
Chris@0
|
242 }
|
Chris@0
|
243
|
Chris@0
|
244 return $this->request($link->getMethod(), $link->getUri());
|
Chris@0
|
245 }
|
Chris@0
|
246
|
Chris@0
|
247 /**
|
Chris@0
|
248 * Submits a form.
|
Chris@0
|
249 *
|
Chris@0
|
250 * @param Form $form A Form instance
|
Chris@0
|
251 * @param array $values An array of form field values
|
Chris@0
|
252 *
|
Chris@0
|
253 * @return Crawler
|
Chris@0
|
254 */
|
Chris@0
|
255 public function submit(Form $form, array $values = array())
|
Chris@0
|
256 {
|
Chris@0
|
257 $form->setValues($values);
|
Chris@0
|
258
|
Chris@0
|
259 return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles());
|
Chris@0
|
260 }
|
Chris@0
|
261
|
Chris@0
|
262 /**
|
Chris@0
|
263 * Calls a URI.
|
Chris@0
|
264 *
|
Chris@0
|
265 * @param string $method The request method
|
Chris@0
|
266 * @param string $uri The URI to fetch
|
Chris@0
|
267 * @param array $parameters The Request parameters
|
Chris@0
|
268 * @param array $files The files
|
Chris@0
|
269 * @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does)
|
Chris@0
|
270 * @param string $content The raw body data
|
Chris@0
|
271 * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
Chris@0
|
272 *
|
Chris@0
|
273 * @return Crawler
|
Chris@0
|
274 */
|
Chris@0
|
275 public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true)
|
Chris@0
|
276 {
|
Chris@0
|
277 if ($this->isMainRequest) {
|
Chris@0
|
278 $this->redirectCount = 0;
|
Chris@0
|
279 } else {
|
Chris@0
|
280 ++$this->redirectCount;
|
Chris@0
|
281 }
|
Chris@0
|
282
|
Chris@0
|
283 $uri = $this->getAbsoluteUri($uri);
|
Chris@0
|
284
|
Chris@0
|
285 $server = array_merge($this->server, $server);
|
Chris@0
|
286
|
Chris@0
|
287 if (isset($server['HTTPS'])) {
|
Chris@0
|
288 $uri = preg_replace('{^'.parse_url($uri, PHP_URL_SCHEME).'}', $server['HTTPS'] ? 'https' : 'http', $uri);
|
Chris@0
|
289 }
|
Chris@0
|
290
|
Chris@0
|
291 if (!$this->history->isEmpty()) {
|
Chris@0
|
292 $server['HTTP_REFERER'] = $this->history->current()->getUri();
|
Chris@0
|
293 }
|
Chris@0
|
294
|
Chris@0
|
295 if (empty($server['HTTP_HOST'])) {
|
Chris@0
|
296 $server['HTTP_HOST'] = $this->extractHost($uri);
|
Chris@0
|
297 }
|
Chris@0
|
298
|
Chris@0
|
299 $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME);
|
Chris@0
|
300
|
Chris@0
|
301 $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content);
|
Chris@0
|
302
|
Chris@0
|
303 $this->request = $this->filterRequest($this->internalRequest);
|
Chris@0
|
304
|
Chris@0
|
305 if (true === $changeHistory) {
|
Chris@0
|
306 $this->history->add($this->internalRequest);
|
Chris@0
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 if ($this->insulated) {
|
Chris@0
|
310 $this->response = $this->doRequestInProcess($this->request);
|
Chris@0
|
311 } else {
|
Chris@0
|
312 $this->response = $this->doRequest($this->request);
|
Chris@0
|
313 }
|
Chris@0
|
314
|
Chris@0
|
315 $this->internalResponse = $this->filterResponse($this->response);
|
Chris@0
|
316
|
Chris@0
|
317 $this->cookieJar->updateFromResponse($this->internalResponse, $uri);
|
Chris@0
|
318
|
Chris@0
|
319 $status = $this->internalResponse->getStatus();
|
Chris@0
|
320
|
Chris@0
|
321 if ($status >= 300 && $status < 400) {
|
Chris@0
|
322 $this->redirect = $this->internalResponse->getHeader('Location');
|
Chris@0
|
323 } else {
|
Chris@0
|
324 $this->redirect = null;
|
Chris@0
|
325 }
|
Chris@0
|
326
|
Chris@0
|
327 if ($this->followRedirects && $this->redirect) {
|
Chris@12
|
328 $this->redirects[serialize($this->history->current())] = true;
|
Chris@12
|
329
|
Chris@0
|
330 return $this->crawler = $this->followRedirect();
|
Chris@0
|
331 }
|
Chris@0
|
332
|
Chris@0
|
333 return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type'));
|
Chris@0
|
334 }
|
Chris@0
|
335
|
Chris@0
|
336 /**
|
Chris@0
|
337 * Makes a request in another process.
|
Chris@0
|
338 *
|
Chris@0
|
339 * @param object $request An origin request instance
|
Chris@0
|
340 *
|
Chris@0
|
341 * @return object An origin response instance
|
Chris@0
|
342 *
|
Chris@0
|
343 * @throws \RuntimeException When processing returns exit code
|
Chris@0
|
344 */
|
Chris@0
|
345 protected function doRequestInProcess($request)
|
Chris@0
|
346 {
|
Chris@12
|
347 $deprecationsFile = tempnam(sys_get_temp_dir(), 'deprec');
|
Chris@12
|
348 putenv('SYMFONY_DEPRECATIONS_SERIALIZE='.$deprecationsFile);
|
Chris@12
|
349 $_ENV['SYMFONY_DEPRECATIONS_SERIALIZE'] = $deprecationsFile;
|
Chris@0
|
350 $process = new PhpProcess($this->getScript($request), null, null);
|
Chris@0
|
351 $process->run();
|
Chris@0
|
352
|
Chris@12
|
353 if (file_exists($deprecationsFile)) {
|
Chris@12
|
354 $deprecations = file_get_contents($deprecationsFile);
|
Chris@12
|
355 unlink($deprecationsFile);
|
Chris@12
|
356 foreach ($deprecations ? unserialize($deprecations) : array() as $deprecation) {
|
Chris@12
|
357 if ($deprecation[0]) {
|
Chris@12
|
358 trigger_error($deprecation[1], E_USER_DEPRECATED);
|
Chris@12
|
359 } else {
|
Chris@12
|
360 @trigger_error($deprecation[1], E_USER_DEPRECATED);
|
Chris@12
|
361 }
|
Chris@12
|
362 }
|
Chris@12
|
363 }
|
Chris@12
|
364
|
Chris@0
|
365 if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) {
|
Chris@0
|
366 throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s', $process->getOutput(), $process->getErrorOutput()));
|
Chris@0
|
367 }
|
Chris@0
|
368
|
Chris@0
|
369 return unserialize($process->getOutput());
|
Chris@0
|
370 }
|
Chris@0
|
371
|
Chris@0
|
372 /**
|
Chris@0
|
373 * Makes a request.
|
Chris@0
|
374 *
|
Chris@0
|
375 * @param object $request An origin request instance
|
Chris@0
|
376 *
|
Chris@0
|
377 * @return object An origin response instance
|
Chris@0
|
378 */
|
Chris@0
|
379 abstract protected function doRequest($request);
|
Chris@0
|
380
|
Chris@0
|
381 /**
|
Chris@0
|
382 * Returns the script to execute when the request must be insulated.
|
Chris@0
|
383 *
|
Chris@0
|
384 * @param object $request An origin request instance
|
Chris@0
|
385 *
|
Chris@0
|
386 * @throws \LogicException When this abstract class is not implemented
|
Chris@0
|
387 */
|
Chris@0
|
388 protected function getScript($request)
|
Chris@0
|
389 {
|
Chris@0
|
390 throw new \LogicException('To insulate requests, you need to override the getScript() method.');
|
Chris@0
|
391 }
|
Chris@0
|
392
|
Chris@0
|
393 /**
|
Chris@0
|
394 * Filters the BrowserKit request to the origin one.
|
Chris@0
|
395 *
|
Chris@0
|
396 * @param Request $request The BrowserKit Request to filter
|
Chris@0
|
397 *
|
Chris@0
|
398 * @return object An origin request instance
|
Chris@0
|
399 */
|
Chris@0
|
400 protected function filterRequest(Request $request)
|
Chris@0
|
401 {
|
Chris@0
|
402 return $request;
|
Chris@0
|
403 }
|
Chris@0
|
404
|
Chris@0
|
405 /**
|
Chris@0
|
406 * Filters the origin response to the BrowserKit one.
|
Chris@0
|
407 *
|
Chris@0
|
408 * @param object $response The origin response to filter
|
Chris@0
|
409 *
|
Chris@0
|
410 * @return Response An BrowserKit Response instance
|
Chris@0
|
411 */
|
Chris@0
|
412 protected function filterResponse($response)
|
Chris@0
|
413 {
|
Chris@0
|
414 return $response;
|
Chris@0
|
415 }
|
Chris@0
|
416
|
Chris@0
|
417 /**
|
Chris@0
|
418 * Creates a crawler.
|
Chris@0
|
419 *
|
Chris@0
|
420 * This method returns null if the DomCrawler component is not available.
|
Chris@0
|
421 *
|
Chris@0
|
422 * @param string $uri A URI
|
Chris@0
|
423 * @param string $content Content for the crawler to use
|
Chris@0
|
424 * @param string $type Content type
|
Chris@0
|
425 *
|
Chris@0
|
426 * @return Crawler|null
|
Chris@0
|
427 */
|
Chris@0
|
428 protected function createCrawlerFromContent($uri, $content, $type)
|
Chris@0
|
429 {
|
Chris@0
|
430 if (!class_exists('Symfony\Component\DomCrawler\Crawler')) {
|
Chris@0
|
431 return;
|
Chris@0
|
432 }
|
Chris@0
|
433
|
Chris@0
|
434 $crawler = new Crawler(null, $uri);
|
Chris@0
|
435 $crawler->addContent($content, $type);
|
Chris@0
|
436
|
Chris@0
|
437 return $crawler;
|
Chris@0
|
438 }
|
Chris@0
|
439
|
Chris@0
|
440 /**
|
Chris@0
|
441 * Goes back in the browser history.
|
Chris@0
|
442 *
|
Chris@0
|
443 * @return Crawler
|
Chris@0
|
444 */
|
Chris@0
|
445 public function back()
|
Chris@0
|
446 {
|
Chris@12
|
447 do {
|
Chris@12
|
448 $request = $this->history->back();
|
Chris@12
|
449 } while (array_key_exists(serialize($request), $this->redirects));
|
Chris@12
|
450
|
Chris@12
|
451 return $this->requestFromRequest($request, false);
|
Chris@0
|
452 }
|
Chris@0
|
453
|
Chris@0
|
454 /**
|
Chris@0
|
455 * Goes forward in the browser history.
|
Chris@0
|
456 *
|
Chris@0
|
457 * @return Crawler
|
Chris@0
|
458 */
|
Chris@0
|
459 public function forward()
|
Chris@0
|
460 {
|
Chris@12
|
461 do {
|
Chris@12
|
462 $request = $this->history->forward();
|
Chris@12
|
463 } while (array_key_exists(serialize($request), $this->redirects));
|
Chris@12
|
464
|
Chris@12
|
465 return $this->requestFromRequest($request, false);
|
Chris@0
|
466 }
|
Chris@0
|
467
|
Chris@0
|
468 /**
|
Chris@0
|
469 * Reloads the current browser.
|
Chris@0
|
470 *
|
Chris@0
|
471 * @return Crawler
|
Chris@0
|
472 */
|
Chris@0
|
473 public function reload()
|
Chris@0
|
474 {
|
Chris@0
|
475 return $this->requestFromRequest($this->history->current(), false);
|
Chris@0
|
476 }
|
Chris@0
|
477
|
Chris@0
|
478 /**
|
Chris@0
|
479 * Follow redirects?
|
Chris@0
|
480 *
|
Chris@0
|
481 * @return Crawler
|
Chris@0
|
482 *
|
Chris@0
|
483 * @throws \LogicException If request was not a redirect
|
Chris@0
|
484 */
|
Chris@0
|
485 public function followRedirect()
|
Chris@0
|
486 {
|
Chris@0
|
487 if (empty($this->redirect)) {
|
Chris@0
|
488 throw new \LogicException('The request was not redirected.');
|
Chris@0
|
489 }
|
Chris@0
|
490
|
Chris@0
|
491 if (-1 !== $this->maxRedirects) {
|
Chris@0
|
492 if ($this->redirectCount > $this->maxRedirects) {
|
Chris@12
|
493 $this->redirectCount = 0;
|
Chris@0
|
494 throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects));
|
Chris@0
|
495 }
|
Chris@0
|
496 }
|
Chris@0
|
497
|
Chris@0
|
498 $request = $this->internalRequest;
|
Chris@0
|
499
|
Chris@12
|
500 if (in_array($this->internalResponse->getStatus(), array(301, 302, 303))) {
|
Chris@0
|
501 $method = 'GET';
|
Chris@0
|
502 $files = array();
|
Chris@0
|
503 $content = null;
|
Chris@0
|
504 } else {
|
Chris@0
|
505 $method = $request->getMethod();
|
Chris@0
|
506 $files = $request->getFiles();
|
Chris@0
|
507 $content = $request->getContent();
|
Chris@0
|
508 }
|
Chris@0
|
509
|
Chris@0
|
510 if ('GET' === strtoupper($method)) {
|
Chris@0
|
511 // Don't forward parameters for GET request as it should reach the redirection URI
|
Chris@0
|
512 $parameters = array();
|
Chris@0
|
513 } else {
|
Chris@0
|
514 $parameters = $request->getParameters();
|
Chris@0
|
515 }
|
Chris@0
|
516
|
Chris@0
|
517 $server = $request->getServer();
|
Chris@0
|
518 $server = $this->updateServerFromUri($server, $this->redirect);
|
Chris@0
|
519
|
Chris@0
|
520 $this->isMainRequest = false;
|
Chris@0
|
521
|
Chris@0
|
522 $response = $this->request($method, $this->redirect, $parameters, $files, $server, $content);
|
Chris@0
|
523
|
Chris@0
|
524 $this->isMainRequest = true;
|
Chris@0
|
525
|
Chris@0
|
526 return $response;
|
Chris@0
|
527 }
|
Chris@0
|
528
|
Chris@0
|
529 /**
|
Chris@0
|
530 * Restarts the client.
|
Chris@0
|
531 *
|
Chris@0
|
532 * It flushes history and all cookies.
|
Chris@0
|
533 */
|
Chris@0
|
534 public function restart()
|
Chris@0
|
535 {
|
Chris@0
|
536 $this->cookieJar->clear();
|
Chris@0
|
537 $this->history->clear();
|
Chris@0
|
538 }
|
Chris@0
|
539
|
Chris@0
|
540 /**
|
Chris@0
|
541 * Takes a URI and converts it to absolute if it is not already absolute.
|
Chris@0
|
542 *
|
Chris@0
|
543 * @param string $uri A URI
|
Chris@0
|
544 *
|
Chris@0
|
545 * @return string An absolute URI
|
Chris@0
|
546 */
|
Chris@0
|
547 protected function getAbsoluteUri($uri)
|
Chris@0
|
548 {
|
Chris@0
|
549 // already absolute?
|
Chris@0
|
550 if (0 === strpos($uri, 'http://') || 0 === strpos($uri, 'https://')) {
|
Chris@0
|
551 return $uri;
|
Chris@0
|
552 }
|
Chris@0
|
553
|
Chris@0
|
554 if (!$this->history->isEmpty()) {
|
Chris@0
|
555 $currentUri = $this->history->current()->getUri();
|
Chris@0
|
556 } else {
|
Chris@0
|
557 $currentUri = sprintf('http%s://%s/',
|
Chris@0
|
558 isset($this->server['HTTPS']) ? 's' : '',
|
Chris@0
|
559 isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost'
|
Chris@0
|
560 );
|
Chris@0
|
561 }
|
Chris@0
|
562
|
Chris@0
|
563 // protocol relative URL
|
Chris@0
|
564 if (0 === strpos($uri, '//')) {
|
Chris@0
|
565 return parse_url($currentUri, PHP_URL_SCHEME).':'.$uri;
|
Chris@0
|
566 }
|
Chris@0
|
567
|
Chris@0
|
568 // anchor or query string parameters?
|
Chris@0
|
569 if (!$uri || '#' == $uri[0] || '?' == $uri[0]) {
|
Chris@0
|
570 return preg_replace('/[#?].*?$/', '', $currentUri).$uri;
|
Chris@0
|
571 }
|
Chris@0
|
572
|
Chris@0
|
573 if ('/' !== $uri[0]) {
|
Chris@0
|
574 $path = parse_url($currentUri, PHP_URL_PATH);
|
Chris@0
|
575
|
Chris@0
|
576 if ('/' !== substr($path, -1)) {
|
Chris@0
|
577 $path = substr($path, 0, strrpos($path, '/') + 1);
|
Chris@0
|
578 }
|
Chris@0
|
579
|
Chris@0
|
580 $uri = $path.$uri;
|
Chris@0
|
581 }
|
Chris@0
|
582
|
Chris@0
|
583 return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri;
|
Chris@0
|
584 }
|
Chris@0
|
585
|
Chris@0
|
586 /**
|
Chris@0
|
587 * Makes a request from a Request object directly.
|
Chris@0
|
588 *
|
Chris@0
|
589 * @param Request $request A Request instance
|
Chris@0
|
590 * @param bool $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload())
|
Chris@0
|
591 *
|
Chris@0
|
592 * @return Crawler
|
Chris@0
|
593 */
|
Chris@0
|
594 protected function requestFromRequest(Request $request, $changeHistory = true)
|
Chris@0
|
595 {
|
Chris@0
|
596 return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory);
|
Chris@0
|
597 }
|
Chris@0
|
598
|
Chris@0
|
599 private function updateServerFromUri($server, $uri)
|
Chris@0
|
600 {
|
Chris@0
|
601 $server['HTTP_HOST'] = $this->extractHost($uri);
|
Chris@0
|
602 $scheme = parse_url($uri, PHP_URL_SCHEME);
|
Chris@0
|
603 $server['HTTPS'] = null === $scheme ? $server['HTTPS'] : 'https' == $scheme;
|
Chris@0
|
604 unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']);
|
Chris@0
|
605
|
Chris@0
|
606 return $server;
|
Chris@0
|
607 }
|
Chris@0
|
608
|
Chris@0
|
609 private function extractHost($uri)
|
Chris@0
|
610 {
|
Chris@0
|
611 $host = parse_url($uri, PHP_URL_HOST);
|
Chris@0
|
612
|
Chris@0
|
613 if ($port = parse_url($uri, PHP_URL_PORT)) {
|
Chris@0
|
614 return $host.':'.$port;
|
Chris@0
|
615 }
|
Chris@0
|
616
|
Chris@0
|
617 return $host;
|
Chris@0
|
618 }
|
Chris@0
|
619 }
|