-
-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #236 from Sammyjo20/feature/v2-stream-body
Feature | HasStreamBody
- Loading branch information
Showing
7 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Saloon\Repositories\Body; | ||
|
||
use GuzzleHttp\Psr7\Utils; | ||
use InvalidArgumentException; | ||
use Saloon\Traits\Conditionable; | ||
use Psr\Http\Message\StreamInterface; | ||
use Saloon\Contracts\Body\BodyRepository; | ||
|
||
class StreamBodyRepository implements BodyRepository | ||
{ | ||
use Conditionable; | ||
|
||
/** | ||
* The stream body | ||
* | ||
* @var StreamInterface|null | ||
*/ | ||
protected ?StreamInterface $stream = null; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param StreamInterface|resource|null $value | ||
*/ | ||
public function __construct(mixed $value = null) | ||
{ | ||
$this->set($value); | ||
} | ||
|
||
/** | ||
* Set a value inside the repository | ||
* | ||
* @param StreamInterface|resource|null $value | ||
* @return $this | ||
*/ | ||
public function set(mixed $value): static | ||
{ | ||
if (isset($value) && ! $value instanceof StreamInterface && ! is_resource($value)) { | ||
throw new InvalidArgumentException('The value must a resource or be an instance of ' . StreamInterface::class); | ||
} | ||
|
||
if (is_resource($value)) { | ||
$value = Utils::streamFor($value); | ||
} | ||
|
||
$this->stream = $value; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Retrieve the stream from the repository | ||
* | ||
* @return StreamInterface|null | ||
*/ | ||
public function all(): ?StreamInterface | ||
{ | ||
return $this->stream; | ||
} | ||
|
||
/** | ||
* Retrieve the stream from the repository | ||
* | ||
* Alias of "all" method. | ||
* | ||
* @return StreamInterface|null | ||
*/ | ||
public function get(): ?StreamInterface | ||
{ | ||
return $this->all(); | ||
} | ||
|
||
/** | ||
* Determine if the repository is empty | ||
* | ||
* @return bool | ||
*/ | ||
public function isEmpty(): bool | ||
{ | ||
return is_null($this->stream); | ||
} | ||
|
||
/** | ||
* Determine if the repository is not empty | ||
* | ||
* @return bool | ||
*/ | ||
public function isNotEmpty(): bool | ||
{ | ||
return ! $this->isEmpty(); | ||
} | ||
|
||
/** | ||
* Get the contents of the stream as a string | ||
* | ||
* @return string | ||
*/ | ||
public function __toString(): string | ||
{ | ||
$stream = &$this->stream; | ||
|
||
if (is_null($stream)) { | ||
return ''; | ||
} | ||
|
||
$contents = $stream->getContents(); | ||
|
||
if ($stream->isSeekable()) { | ||
$stream->rewind(); | ||
} | ||
|
||
return $contents; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Saloon\Traits\Body; | ||
|
||
use Psr\Http\Message\StreamInterface; | ||
use Saloon\Repositories\Body\StreamBodyRepository; | ||
|
||
trait HasStreamBody | ||
{ | ||
use ChecksForHasBody; | ||
|
||
/** | ||
* Body Repository | ||
* | ||
* @var \Saloon\Repositories\Body\StreamBodyRepository | ||
*/ | ||
protected StreamBodyRepository $body; | ||
|
||
/** | ||
* Retrieve the data repository | ||
* | ||
* @return \Saloon\Repositories\Body\StreamBodyRepository | ||
*/ | ||
public function body(): StreamBodyRepository | ||
{ | ||
return $this->body ??= new StreamBodyRepository($this->defaultBody()); | ||
} | ||
|
||
/** | ||
* Default body | ||
* | ||
* @return StreamInterface|resource|null | ||
*/ | ||
protected function defaultBody(): mixed | ||
{ | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Saloon\Http\Faking\MockResponse; | ||
use Psr\Http\Message\StreamInterface; | ||
use Psr\Http\Message\RequestInterface; | ||
use GuzzleHttp\Promise\FulfilledPromise; | ||
use Saloon\Tests\Fixtures\Connectors\TestConnector; | ||
use Saloon\Tests\Fixtures\Requests\HasStreamBodyRequest; | ||
|
||
test('the default body is loaded', function () { | ||
$request = new HasStreamBodyRequest; | ||
|
||
expect($request->body()->all())->toBeInstanceOf(StreamInterface::class); | ||
expect($request->body()->get())->toBeInstanceOf(StreamInterface::class); | ||
expect((string)$request->body())->toEqual('Howdy, Partner'); | ||
}); | ||
|
||
test('the guzzle sender properly sends it', function () { | ||
$connector = new TestConnector; | ||
$request = new HasStreamBodyRequest; | ||
|
||
$request->headers()->add('Content-Type', 'application/custom'); | ||
|
||
$connector->sender()->addMiddleware(function (callable $handler) use ($request) { | ||
return function (RequestInterface $guzzleRequest, array $options) use ($request) { | ||
expect($guzzleRequest->getHeader('Content-Type'))->toEqual(['application/custom']); | ||
expect((string)$guzzleRequest->getBody())->toEqual('Howdy, Partner'); | ||
|
||
return new FulfilledPromise(MockResponse::make()->getPsrResponse()); | ||
}; | ||
}); | ||
|
||
$connector->send($request); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Saloon\Tests\Fixtures\Requests; | ||
|
||
use Saloon\Enums\Method; | ||
use Saloon\Http\Request; | ||
use Saloon\Contracts\Body\HasBody; | ||
use Saloon\Traits\Body\HasStreamBody; | ||
|
||
class HasStreamBodyRequest extends Request implements HasBody | ||
{ | ||
use HasStreamBody; | ||
|
||
/** | ||
* Define the method that the request will use. | ||
* | ||
* @var Method | ||
*/ | ||
protected Method $method = Method::GET; | ||
|
||
/** | ||
* Define the endpoint for the request. | ||
* | ||
* @return string | ||
*/ | ||
public function resolveEndpoint(): string | ||
{ | ||
return '/user'; | ||
} | ||
|
||
protected function defaultBody(): mixed | ||
{ | ||
$temp = fopen('php://memory', 'rw'); | ||
|
||
fwrite($temp, 'Howdy, Partner'); | ||
|
||
rewind($temp); | ||
|
||
return $temp; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use GuzzleHttp\Psr7\Utils; | ||
use Saloon\Repositories\Body\StreamBodyRepository; | ||
|
||
test('the store is empty by default', function () { | ||
$body = new StreamBodyRepository(); | ||
|
||
expect($body->all())->toBeNull(); | ||
}); | ||
|
||
|
||
test('the store can have a default stream provided', function () { | ||
$resource = tmpfile(); | ||
|
||
$body = new StreamBodyRepository($resource); | ||
|
||
expect($body->all())->toEqual(Utils::streamFor($resource)); | ||
}); | ||
|
||
test('you can set it', function () { | ||
$resourceA = fopen('php://memory', 'rw+'); | ||
fwrite($resourceA, 'Howdy'); | ||
|
||
$resourceB = fopen('php://memory', 'rw+'); | ||
fwrite($resourceB, 'Yeehaw'); | ||
|
||
$body = new StreamBodyRepository($resourceA); | ||
|
||
$body->set($resourceB); | ||
|
||
expect($body->all())->toEqual(Utils::streamFor($resourceB)); | ||
}); | ||
|
||
test('you can conditionally set on the store', function () { | ||
$body = new StreamBodyRepository(); | ||
|
||
$resourceA = fopen('php://memory', 'rw+'); | ||
fwrite($resourceA, 'Howdy'); | ||
|
||
$resourceB = fopen('php://memory', 'rw+'); | ||
fwrite($resourceB, 'Yeehaw'); | ||
|
||
$body->when(true, fn (StreamBodyRepository $body) => $body->set($resourceA)); | ||
$body->when(false, fn (StreamBodyRepository $body) => $body->set($resourceB)); | ||
|
||
expect($body->all())->toEqual(Utils::streamFor($resourceA)); | ||
}); | ||
|
||
test('you can check if the store is empty or not', function () { | ||
$body = new StreamBodyRepository(); | ||
|
||
expect($body->isEmpty())->toBeTrue(); | ||
expect($body->isNotEmpty())->toBeFalse(); | ||
|
||
$body->set(tmpfile()); | ||
|
||
expect($body->isEmpty())->toBeFalse(); | ||
expect($body->isNotEmpty())->toBeTrue(); | ||
}); | ||
|
||
test('it will throw an exception if the value is not a resource or StreamInterface when instantiating', function (mixed $value) { | ||
$this->expectException(InvalidArgumentException::class); | ||
|
||
new StreamBodyRepository($value); | ||
})->with([ | ||
fn () => 'Howdy', | ||
fn () => 123, | ||
fn () => [], | ||
fn () => false, | ||
]); | ||
|
||
test('it will throw an exception if the value is not a resource or StreamInterface when setting', function (mixed $value) { | ||
$this->expectException(InvalidArgumentException::class); | ||
|
||
new StreamBodyRepository($value); | ||
})->with([ | ||
fn () => 'Howdy', | ||
fn () => 123, | ||
fn () => [], | ||
fn () => false, | ||
]); | ||
|
||
test('it allows null values', function () { | ||
$body = new StreamBodyRepository(null); | ||
|
||
expect($body->all())->toBeNull(); | ||
expect($body->isEmpty())->toBeTrue(); | ||
|
||
$body->set(null); | ||
|
||
expect($body->all())->toBeNull(); | ||
expect($body->isEmpty())->toBeTrue(); | ||
}); | ||
|
||
test('the stream is rewound after converting to a string', function () { | ||
$resource = fopen('php://memory', 'rw+'); | ||
fwrite($resource, 'Howdy'); | ||
rewind($resource); | ||
|
||
$body = new StreamBodyRepository($resource); | ||
$stream = $body->get(); | ||
|
||
expect((string)$body)->toEqual('Howdy'); | ||
expect($stream->getContents())->toEqual('Howdy'); | ||
}); | ||
|
||
test('if the contents of the body is null then an empty string is returned', function () { | ||
$body = new StreamBodyRepository(); | ||
|
||
expect((string)$body)->toEqual(''); | ||
}); |