Skip to content

Commit

Permalink
Unit test coverage and tweaks (#80)
Browse files Browse the repository at this point in the history
* build: upgrade phpunit config

* tweak: only close if already open

* test: test coverage for curl class

* test: test coverage for curlobjectlookup class

* test: test coverage for curlobjectlookup class

* test: test coverage for curlmulti class

* test: test coverage for curlshare class

* test: test coverage for curlmultiinfo class

* docs: update examples

* docs: update examples to use UploadFile

* docs: update curl multi callback functions

* tweak: phpmd
  • Loading branch information
g105b committed Mar 28, 2023
1 parent 971279f commit 8c80353
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 16 deletions.
16 changes: 16 additions & 0 deletions examples/01-http-get.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
/*
* This is the simplest example of how to perform an HTTP GET request using Curl
*/
use Gt\Curl\Curl;

require(__DIR__ . "/../vendor/autoload.php");

$curl = new Curl("https://ipapi.co/country_name");
$curl->exec();
echo "Your country is: ";
echo $curl->output(), PHP_EOL;

/* Example output:
Your country is: United Kingdom
*/
47 changes: 47 additions & 0 deletions examples/02-http-post-file-upload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/*
* This example downloads a picture of a cat, then uploads it to an example API
* using an HTTP POST upload.
*/
use Gt\Curl\Curl;
use Gt\Curl\UploadFile;

require(__DIR__ . "/../vendor/autoload.php");

$tmpFile = "/tmp/cat.jpg";

// Download a photo of a cat from cataas.com, save it to the $tmpFile
$curl = new Curl("https://cataas.com/cat");
file_put_contents($tmpFile, $curl->output());

// Now POST a form containing the cat photo to the Postman echo test server
$upload = new UploadFile($tmpFile);
$curl = new Curl("https://postman-echo.com/post");
$curl->setOpt(CURLOPT_POSTFIELDS, [
"cat-photo" => $upload
]);
$curl->exec();
echo $curl->output();

// Remove the temporary file before finishing
unlink($tmpFile);

/*
* Example output:
* {
* "args": {},
* "data": {},
* "files": {
* "cat.jpg": "data:application/octet-stream;base64,/9j/4AAQSkZJRgABAQEIAAD"
* },
* "form": {},
* "headers": {
* "host": "postman-echo.com",
* "content-length": "42285",
* "accept": "*\/*",
* "content-type": "multipart/form-data; boundary=--------------d7b86ee9056"
* },
* "json": null,
* "url": "https://postman-echo.com/post"
* }
*/
6 changes: 3 additions & 3 deletions examples/01-json-usage.php → examples/03-json-usage.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php
/*
This example shows how to load a JSON API and interact with the response in a
type-safe way. This utilises https://php.gt/json for the response object.
*/
* This example shows how to load a JSON API and interact with the response in a
* type-safe way. This utilises https://php.gt/json for the response object.
*/
use Gt\Curl\Curl;

require(__DIR__ . "/../vendor/autoload.php");
Expand Down
3 changes: 0 additions & 3 deletions examples/02-curl-multi.php → examples/04-curl-multi.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@
require(__DIR__ . "/../vendor/autoload.php");

$curlCat = new Curl("https://catfact.ninja/fact");
$curlCat->setOpt(CURLOPT_RETURNTRANSFER, true);
$curlIp = new Curl("https://api.ipify.org/?format=json");
$curlIp->setOpt(CURLOPT_RETURNTRANSFER, true);
$curlTimeout = new Curl("https://this-domain-name-does-not-exist.example.com/nothing.json");
$curlTimeout->setOpt(CURLOPT_RETURNTRANSFER, true);

$multi = new CurlMulti();
$multi->add($curlCat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,28 @@
while($stillRunning > 0);

echo PHP_EOL;

/** Example output:
.................................HEADER: HTTP/2 200
HEADER: server: nginx
HEADER: date: Tue, 28 Mar 2023 11:26:46 GMT
HEADER: content-type: application/json
HEADER: vary: Accept-Encoding
HEADER: cache-control: no-cache, private
HEADER: access-control-allow-origin: *
HEADER: set-cookie: XSRF-TOKEN=eyJpdiI6I; expires=Tue, 28-Mar-2023 13:26:46 GMT; path=/; samesite=lax
HEADER: set-cookie: catfacts_session=eyJpdiI6InUzdFY; expires=Tue, 28-Mar-2023 13:26:46 GMT; path=/; httponly; samesite=lax
HEADER: x-frame-options: SAMEORIGIN
HEADER: x-xss-protection: 1; mode=block
HEADER: x-content-type-options: nosniff
HEADER:
BODY: {"fact":"Edward Lear, author of \\The Owl and the Pussycat\\\"\", is said to have had his new house in San Remo built to exactly the same specification as his previous residence, so that his much-loved tabby, Foss, would immediately feel at home.\"\"\"","length":236}
...............HEADER: HTTP/2 200
HEADER: content-type: application/json
HEADER: date: Tue, 28 Mar 2023 11:26:46 GMT
HEADER: vary: Origin
HEADER: content-length: 20
HEADER:
BODY: {"ip":"82.4.210.105"}
.
*/
19 changes: 19 additions & 0 deletions src/Curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
class Curl implements CurlInterface {
protected CurlHandle $ch;
protected ?string $buffer;
/** @var callable */
protected $headerFunction;
/** @var callable */
protected $writeFunction;

public function __construct(string $url = null) {
$this->buffer = null;
Expand Down Expand Up @@ -169,6 +173,13 @@ public function reset():void {
* @see http://php.net/manual/en/function.curl-setopt.php
*/
public function setOpt(int $option, mixed $value):bool {
if($option === CURLOPT_HEADERFUNCTION) {
$this->headerFunction = $value;
}
elseif($option === CURLOPT_WRITEFUNCTION) {
$this->writeFunction = $value;
}

return curl_setopt($this->ch, $option, $value);
}

Expand Down Expand Up @@ -205,4 +216,12 @@ public function getAllInfo():array {
$result = curl_getinfo($this->ch);
return $result ?: [];
}

public function getHeaderFunction():?callable {
return $this->headerFunction ?? null;
}

public function getWriteFunction():?callable {
return $this->writeFunction ?? null;
}
}
4 changes: 4 additions & 0 deletions src/CurlInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,8 @@ public function getHandle():CurlHandle;
* @return array<string, mixed>
*/
public function getAllInfo():array;

public function getHeaderFunction():?callable;

public function getWriteFunction():?callable;
}
36 changes: 30 additions & 6 deletions src/CurlMulti.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
<?php
namespace Gt\Curl;

use CurlHandle;
use CurlMultiHandle;

class CurlMulti implements CurlMultiInterface {
protected CurlMultiHandle $mh;
/** @var array<int, CurlHandle> */
protected array $curlHandleArray;
/** @var array<int, string> */
protected array $responseBodyArray;

public function __construct() {
$this->curlHandleArray = [];
$this->responseBodyArray = [];
$this->init();
}

Expand All @@ -27,9 +34,22 @@ public static function strError(int $errorNum):string {
* @throws CurlException if a CURLM_XXX error is made
*/
public function add(CurlInterface $curl):void {
// The RETURNTRANSFER option must be set to be able to use the internal buffers.
$curl->setOpt(CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($this->mh, $curl->getHandle());
$handle = $curl->getHandle();
array_push($this->curlHandleArray, $handle);
array_push($this->responseBodyArray, "");

$existingWrite = $curl->getWriteFunction();
if(!$existingWrite) {
$curl->setOpt(
CURLOPT_WRITEFUNCTION,
function (CurlHandle $ch, string $rawBody):int {
$index = array_search($ch, $this->curlHandleArray);
$this->responseBodyArray[$index] .= $rawBody;
return strlen($rawBody);
}
);
}
curl_multi_add_handle($this->mh, $handle);
}

/**
Expand Down Expand Up @@ -66,12 +86,16 @@ public function getHandle():CurlMultiHandle {
}

/**
* Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set
* Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set,
* or retrieves the content of the cURL handle from the internal buffer.
* @see http://php.net/manual/en/function.curl-multi-getcontent.php
* @return string the content of a cURL handle if CURLOPT_RETURNTRANSFER is set
* @return string the content of a cURL handle
*/
public function getContent(CurlInterface $curl):string {
return curl_multi_getcontent($curl->getHandle()) ?? "";
$index = array_search($curl->getHandle(), $this->curlHandleArray);
return $this->responseBodyArray[$index]
?? curl_multi_getcontent($curl->getHandle())
?? "";
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/CurlMultiInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @see http://php.net/manual/en/function.curl-multi-info-read.php
*/
class CurlMultiInfo implements CurlMultiInfoInterface {
protected string $message;
protected int $message;
protected int $result;
protected CurlHandle $handle;

Expand Down
6 changes: 6 additions & 0 deletions src/UploadFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
namespace Gt\Curl;

use CURLFile;

class UploadFile extends CURLFile {}
30 changes: 27 additions & 3 deletions test/phpunit/CurlMultiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,39 @@ public function testStrError():void {

public function testAdd():void {
$curlInterface = self::createMock(CurlInterface::class);
$curlInterface->expects(self::once())
->method("setOpt")
->with(CURLOPT_RETURNTRANSFER, true);
$curlInterface->expects(self::once())
->method("getHandle")
->willReturn(curl_init());
$sut = new CurlMulti();
$sut->add($curlInterface);
}

public function testAdd_noExistingWriteFunction():void {
$curlInterface = self::createMock(CurlInterface::class);
$curlInterface->method("getHandle")
->willReturn(curl_init());
$curlInterface->method("getWriteFunction")
->willReturn(null);
$curlInterface->expects(self::once())
->method("setOpt")
->with(CURLOPT_WRITEFUNCTION, self::anything());
$sut = new CurlMulti();
$sut->add($curlInterface);
}

public function testAdd_existingWriteFunction():void {
$curlInterface = self::createMock(CurlInterface::class);
$curlInterface->method("getHandle")
->willReturn(curl_init());
$curlInterface->method("getWriteFunction")
->willReturn(function() {});
$curlInterface->expects(self::never())
->method("setOpt")
->with(CURLOPT_WRITEFUNCTION, self::anything());
$sut = new CurlMulti();
$sut->add($curlInterface);
}

public function testClose():void {
$sut = new CurlMulti();
$exception = null;
Expand Down Expand Up @@ -76,6 +99,7 @@ public function testGetContent():void {
}
}
$curl = new Curl("http://localhost:$port");
$curl->setOpt(CURLOPT_RETURNTRANSFER, true);
$sut = new CurlMulti();
$sut->add($curl);
$stillRunning = 0;
Expand Down
16 changes: 16 additions & 0 deletions test/phpunit/CurlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,20 @@ public function testOutputJson():void {
$sut->exec();
self::assertSame("Hello, PHP.Gt!", $sut->outputJson()->getString("message"));
}

public function testGetHeaderFunction():void {
$func = function() {};
$sut = new Curl();
self::assertNull($sut->getHeaderFunction());
$sut->setOpt(CURLOPT_HEADERFUNCTION, $func);
self::assertSame($func, $sut->getHeaderFunction());
}

public function testGetWriteFunction():void {
$func = function() {};
$sut = new Curl();
self::assertNull($sut->getWriteFunction());
$sut->setOpt(CURLOPT_WRITEFUNCTION, $func);
self::assertSame($func, $sut->getWriteFunction());
}
}

0 comments on commit 8c80353

Please sign in to comment.