diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce3aa65 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +phpunit.xml diff --git a/.gitmodules b/.gitmodules index 593766f..36dbd34 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,12 +10,12 @@ [submodule "vendor/Symfony/Component/Process"] path = vendor/Symfony/Component/Process url = https://github.com/symfony/Process -[submodule "vendor/zend"] - path = vendor/zend - url = https://github.com/zendframework/zf2 [submodule "vendor/Symfony/Component/ClassLoader"] path = vendor/Symfony/Component/ClassLoader url = https://github.com/symfony/ClassLoader [submodule "vendor/Symfony/Component/Finder"] path = vendor/Symfony/Component/Finder url = https://github.com/symfony/Finder +[submodule "vendor/Guzzle"] + path = vendor/Guzzle + url = git://github.com/guzzle/guzzle.git diff --git a/README.md b/README.md index 363fc6b..30ec098 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Goutte is a thin wrapper around the following fine PHP libraries: * Symfony Components: BrowserKit, ClassLoader, CssSelector, DomCrawler, Finder, and Process - * Zend libraries: Date, Uri, Http, and Validator + * [Guzzle](http://www.guzzlephp.org) License ------- @@ -79,3 +79,4 @@ License Goutte is licensed under the MIT license. [1]: https://raw.github.com/fabpot/Goutte/master/goutte.phar + diff --git a/autoload.php b/autoload.php index f1a79ca..e5d8b03 100644 --- a/autoload.php +++ b/autoload.php @@ -9,7 +9,7 @@ $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => __DIR__.'/vendor', - 'Zend' => __DIR__.'/vendor/zend/library', + 'Guzzle' => __DIR__.'/vendor/Guzzle/src', 'Goutte' => __DIR__.'/src', )); $loader->register(); diff --git a/phpunit.xml.dst b/phpunit.xml.dst new file mode 100644 index 0000000..3bcbd37 --- /dev/null +++ b/phpunit.xml.dst @@ -0,0 +1,18 @@ + + + + + + ./tests/Goutte/Tests + + + diff --git a/src/Goutte/Client.php b/src/Goutte/Client.php index a050c8c..4ff45ce 100644 --- a/src/Goutte/Client.php +++ b/src/Goutte/Client.php @@ -8,8 +8,11 @@ use Symfony\Component\BrowserKit\Request; use Symfony\Component\BrowserKit\Response; -use Zend\Http\Client as ZendClient; -use Zend\Http\Response as ZendResponse; +use Guzzle\Http\Curl\CurlException; +use Guzzle\Http\Message\RequestInterface as GuzzleRequestInterface; +use Guzzle\Http\Message\Response as GuzzleResponse; +use Guzzle\Service\ClientInterface as GuzzleClientInterface; +use Guzzle\Service\Client as GuzzleClient; /* * This file is part of the Goutte package. @@ -25,68 +28,61 @@ * * @package Goutte * @author Fabien Potencier + * @author Michael Dowling */ class Client extends BaseClient { const VERSION = '0.1'; - protected $zendConfig; protected $headers = array(); protected $auth = null; + protected $client; - public function __construct(array $zendConfig = array(), array $server = array(), History $history = null, CookieJar $cookieJar = null) + public function setClient(GuzzleClientInterface $client) { - $this->zendConfig = $zendConfig; + $this->client = $client; - parent::__construct($server, $history, $cookieJar); + return $this; + } + + public function getClient() + { + if (!$this->client) { + $this->client = new GuzzleClient(); + } + + return $this->client; } public function setHeader($name, $value) { $this->headers[$name] = $value; + + return $this; } - public function setAuth($user, $password = '', $type = ZendClient::AUTH_BASIC) + public function setAuth($user, $password = '', $type = GuzzleRequestInterface::AUTH_BASIC) { $this->auth = array( - 'user' => $user, + 'user' => $user, 'password' => $password, 'type' => $type ); - } - protected function doRequest($request) - { - $client = $this->createClient($request); - - $response = $client->send($client->getRequest()); - - return $this->createResponse($response); + return $this; } - protected function createClient(Request $request) + protected function doRequest($request) { - $client = $this->createZendClient(); - $client->setUri($request->getUri()); - $client->setConfig(array_merge(array( - 'maxredirects' => 0, - 'timeout' => 30, - 'useragent' => $this->server['HTTP_USER_AGENT'], - 'adapter' => 'Zend\\Http\\Client\\Adapter\\Socket', - ), $this->zendConfig)); - $client->setMethod(strtoupper($request->getMethod())); - - if ($request->getContent() !== null) { - $client->setRawBody($request->getContent()); - } - - if ('POST' == $request->getMethod()) { - $client->setParameterPost($request->getParameters()); - } - $client->setHeaders($this->headers); + $guzzleRequest = $this->getClient()->createRequest( + strtoupper($request->getMethod()), + $request->getUri(), + $this->headers, + $request->getParameters() + ); if ($this->auth !== null) { - $client->setAuth( + $guzzleRequest->setAuth( $this->auth['user'], $this->auth['password'], $this->auth['type'] @@ -94,46 +90,47 @@ protected function createClient(Request $request) } foreach ($this->getCookieJar()->allValues($request->getUri()) as $name => $value) { - $client->addCookie($name, $value); + $guzzleRequest->addCookie($name, $value); } - $this->addFileUploadsRecursively($client, $request->getFiles()); - - return $client; - } - - /** - * Goes recursively through the files array and adds uploads to the ZendClient - */ - protected function addFileUploadsRecursively(ZendClient $client, array $files, $arrayName = '') - { - foreach ($files as $name => $info) { - if (!empty($arrayName)) { - $name = $arrayName . '[' . $name . ']'; + if ($request->getMethod() == 'POST') { + foreach ($request->getFiles() as $name => $info) { + if (isset($info['tmp_name']) && '' !== $info['tmp_name']) { + $guzzleRequest->addPostFiles(array( + $name => $info['tmp_name'] + )); + } } - if (isset($info['tmp_name']) && isset($info['name'])) { - if ('' !== $info['tmp_name'] && '' !== $info['name']) { - $filename = $info['name']; - - if (false === ($data = @file_get_contents($info['tmp_name']))) { - throw new \RuntimeException("Unable to read file '{$filename}' for upload"); - } + } - $client->setFileUpload($filename, $name, $data); - } - } elseif (is_array($info)) { - $this->addFileUploadsRecursively($client, $info, $name); + $guzzleRequest->setHeader('User-Agent', $this->server['HTTP_USER_AGENT']); + + $guzzleRequest->getCurlOptions()->merge(array( + CURLOPT_MAXREDIRS => 0, + CURLOPT_TIMEOUT => 30 + )); + + // Let BrowserKit handle redirects + try { + $response = $guzzleRequest->send(); + } catch (CurlException $e) { + if (strpos($e->getMessage(), 'redirects')) { + $response = $e->getResponse(); + } else { + throw $e; } } - } - protected function createResponse(ZendResponse $response) - { - return new Response($response->getBody(), $response->getStatusCode(), $response->headers()->toArray()); + return $this->createResponse($response); } - protected function createZendClient() + protected function createResponse(GuzzleResponse $response) { - return new ZendClient(null, array('encodecookies' => false)); + return new Response( + $response->getBody(true), + $response->getStatusCode(), + $response->getHeaders()->getAll() + ); } } + diff --git a/src/Goutte/Compiler.php b/src/Goutte/Compiler.php index 4993311..3108ad5 100644 --- a/src/Goutte/Compiler.php +++ b/src/Goutte/Compiler.php @@ -37,9 +37,7 @@ public function compile($pharFile = 'goutte.phar') foreach ($this->getFiles() as $file) { $path = str_replace(__DIR__.'/', '', $file); - $content = preg_replace("#require_once 'Zend/.*?';#", '', php_strip_whitespace($file)); - - $phar->addFromString($path, $content); + $phar->addFromString($path, file_get_contents($file)); } // Stubs @@ -82,28 +80,7 @@ protected function getFiles() $files = array( 'LICENSE', 'autoload.php', - 'vendor/Symfony/Component/ClassLoader/UniversalClassLoader.php', - 'vendor/zend/library/Zend/Registry.php', - //'vendor/zend/library/Zend/Date.php', - 'vendor/zend/library/Zend/Uri/Uri.php', - 'vendor/zend/library/Zend/Validator/Validator.php', - 'vendor/zend/library/Zend/Validator/AbstractValidator.php', - 'vendor/zend/library/Zend/Validator/Hostname.php', - 'vendor/zend/library/Zend/Validator/Ip.php', - //'vendor/zend/library/Zend/Validator/Hostname/Biz.php', - //'vendor/zend/library/Zend/Validator/Hostname/Cn.php', - 'vendor/zend/library/Zend/Validator/Hostname/Com.php', - 'vendor/zend/library/Zend/Validator/Hostname/Jp.php', - 'vendor/zend/library/Zend/Stdlib/Dispatchable.php', - 'vendor/zend/library/Zend/Stdlib/Message.php', - 'vendor/zend/library/Zend/Stdlib/MessageDescription.php', - 'vendor/zend/library/Zend/Stdlib/RequestDescription.php', - 'vendor/zend/library/Zend/Stdlib/Parameters.php', - 'vendor/zend/library/Zend/Stdlib/ParametersDescription.php', - 'vendor/zend/library/Zend/Stdlib/ResponseDescription.php', - 'vendor/zend/library/Zend/Loader/PluginClassLoader.php', - 'vendor/zend/library/Zend/Loader/PluginClassLocator.php', - 'vendor/zend/library/Zend/Loader/ShortNameLocator.php', + 'vendor/Symfony/Component/ClassLoader/UniversalClassLoader.php' ); $dirs = array( @@ -112,9 +89,7 @@ protected function getFiles() 'vendor/Symfony/Component/DomCrawler', 'vendor/Symfony/Component/CssSelector', 'vendor/Symfony/Component/Process', - //'vendor/zend/library/Zend/Date', - 'vendor/zend/library/Zend/Uri', - 'vendor/zend/library/Zend/Http', + 'vendor/Guzzle/src/Guzzle' ); $finder = new Finder(); @@ -123,3 +98,4 @@ protected function getFiles() return array_merge($files, iterator_to_array($iterator)); } } + diff --git a/tests/Goutte/Tests/ClientTest.php b/tests/Goutte/Tests/ClientTest.php new file mode 100644 index 0000000..353491d --- /dev/null +++ b/tests/Goutte/Tests/ClientTest.php @@ -0,0 +1,167 @@ + + */ +class ClientTest extends \PHPUnit_Framework_TestCase +{ + protected $history; + + protected function getGuzzle() + { + $this->history = new HistoryPlugin(); + $mock = new MockPlugin(); + $mock->addResponse(new GuzzleResponse(200, null, '

Hi

')); + $guzzle = new GuzzleClient(); + $guzzle->getEventManager()->attach($mock); + $guzzle->getEventManager()->attach($this->history); + + return $guzzle; + } + + public function testCreatesDefaultClient() + { + $client = new Client(); + $this->assertInstanceOf('Guzzle\\Service\\Client', $client->getClient()); + } + + public function testUsesCustomClient() + { + $guzzle = new GuzzleClient(); + $client = new Client(); + $this->assertSame($client, $client->setClient($guzzle)); + $this->assertSame($guzzle, $client->getClient()); + } + + public function testUsesCustomHeaders() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->setHeader('X-Test', 'test'); + $crawler = $client->request('GET', 'http://test.com/'); + $this->assertEquals('test', $this->history->getLastRequest()->getHeader('X-Test')); + } + + public function testUsesAuth() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->setAuth('me', '**'); + $crawler = $client->request('GET', 'http://test.com/'); + $request = $this->history->getLastRequest(); + $this->assertEquals('me', $request->getUsername()); + $this->assertEquals('**', $request->getPassword()); + } + + public function testUsesCookies() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $client->getCookieJar()->set(new Cookie('test', '123')); + $crawler = $client->request('GET', 'http://test.com/'); + $request = $this->history->getLastRequest(); + $this->assertEquals('123', $request->getCookie('test')); + } + + public function testUsesPostFiles() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $files = array( + 'test' => array( + 'name' => 'test.txt', + 'tmp_name' => __FILE__ + ) + ); + + $crawler = $client->request('POST', 'http://test.com/', array(), $files); + $request = $this->history->getLastRequest(); + + $this->assertEquals(array( + 'test' => __FILE__ + ), $request->getPostFiles()); + } + + public function testUsesCurlOptions() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $crawler = $client->request('GET', 'http://test.com/'); + $request = $this->history->getLastRequest(); + $this->assertEquals(0, $request->getCurlOptions()->get(CURLOPT_MAXREDIRS)); + $this->assertEquals(30, $request->getCurlOptions()->get(CURLOPT_TIMEOUT)); + } + + public function testCreatesResponse() + { + $guzzle = $this->getGuzzle(); + $client = new Client(); + $client->setClient($guzzle); + $crawler = $client->request('GET', 'http://test.com/'); + $this->assertEquals('Hi', $crawler->filter('p')->text()); + } + + public function testHandlesRedirectsCorrectly() + { + $guzzle = $this->getGuzzle(); + $plugins = $guzzle->getEventManager()->getAttached('Guzzle\\Service\\Plugin\\MockPlugin'); + $mock = $plugins[0]; + + $mock->clearQueue(); + $mock->addResponse(new GuzzleResponse(301, array( + 'Location' => 'http://www.test.com/' + ))); + $mock->addResponse(new GuzzleResponse(200, null, '

Test

')); + + $client = new Client(); + $client->setClient($guzzle); + + $crawler = $client->request('GET', 'http://test.com/'); + $this->assertEquals('Test', $crawler->filter('p')->text()); + + // Ensure that two requests were sent + $this->assertEquals(2, count($this->history)); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..ea4e660 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,6 @@ +