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