From 8dd1f100335662aa6f37d8659301baa7cf81d5be Mon Sep 17 00:00:00 2001 From: Phan An Date: Fri, 31 May 2024 15:21:11 +0800 Subject: [PATCH] feat: add optional ClientInterface parameter --- README.md | 13 ++++++++++--- composer.json | 6 ++++-- composer.lock | 2 +- src/Poddle.php | 10 ++++++++-- tests/PoddleTest.php | 36 +++++++++++++++++++++++++++++++++++- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 718423d..61179b7 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,22 @@ composer require phanan/poddle ## Usage -To parse a podcast feed, call the `fromUrl` method with the feed URL: +### Parse from a URL + +To parse a podcast feed from its URL, call the `fromUrl` method with the feed URL: ```php $poddle = \PhanAn\Poddle::fromUrl('https://example.com/feed.xml'); ``` -It is possible to configure the timeout value for the request by passing an integer as the second parameter for `Poddle::fromUrl`. +This method also accepts two additional parameters: + +* `timeoutInSeconds`: The number of seconds to wait while trying to connect. Defaults to 30. Note that the `max_execution_time` value in your PHP configuration may still limit the maximum timeout value. +* `client`: A PSR-7-compliant client to make the request. If not provided, Poddle will use a default client. This parameter may come in handy during testing or if you need to heavily customize the request. + +### Parse from XML -For total control, you can make the request yourself (for example using an HTTP client) and pass the response body to `Poddle::fromXml` instead: +If you already have the XML string, you can parse it using `Poddle::fromXml` instead: ```php use Illuminate\Support\Facades\Http; diff --git a/composer.json b/composer.json index 2b3a94f..83ac681 100644 --- a/composer.json +++ b/composer.json @@ -23,14 +23,16 @@ "illuminate/collections": "^10.48", "illuminate/http": "^10.48", "illuminate/support": "^10.48", - "guzzlehttp/guzzle": "^7.8" + "guzzlehttp/guzzle": "^7.8", + "psr/http-client": "^1.0" }, "require-dev": { "phpunit/phpunit": ">=10.5", "laravel/pint": "^1.15", "larastan/larastan": "^2.9", "orchestra/testbench": "*", - "laravel/tinker": "^2.9" + "laravel/tinker": "^2.9", + "mockery/mockery": "^1.6" }, "scripts": { "test": "phpunit tests", diff --git a/composer.lock b/composer.lock index c02b21b..4848ed9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "760a5029772edb21a4251ecdd6a76717", + "content-hash": "9373572486ba56fccddfcae08ead7372", "packages": [ { "name": "azjezz/psl", diff --git a/src/Poddle.php b/src/Poddle.php index 7ebd73d..1b606ac 100644 --- a/src/Poddle.php +++ b/src/Poddle.php @@ -3,6 +3,7 @@ namespace PhanAn\Poddle; use Generator; +use GuzzleHttp\Psr7\Request; use Illuminate\Support\Facades\Http; use Illuminate\Support\Str; use PhanAn\Poddle\Enums\PodcastType; @@ -13,6 +14,7 @@ use PhanAn\Poddle\Values\EpisodeCollection; use PhanAn\Poddle\Values\FundingCollection; use PhanAn\Poddle\Values\TxtCollection; +use Psr\Http\Client\ClientInterface; use Saloon\XmlWrangler\Exceptions\QueryAlreadyReadException; use Saloon\XmlWrangler\Exceptions\XmlReaderException; use Saloon\XmlWrangler\XmlReader; @@ -28,9 +30,13 @@ public function __construct(public readonly string $xml) $this->xmlReader = XmlReader::fromString($xml); } - public static function fromUrl(string $url, int $timeoutInSeconds = 30): self + public static function fromUrl(string $url, int $timeoutInSeconds = 30, ClientInterface $client = null): self { - return new self(Http::timeout($timeoutInSeconds)->get($url)->body()); + $xml = $client + ? $client->sendRequest(new Request('GET', $url, ['connect_timeout' => $timeoutInSeconds]))->getBody() + : Http::timeout($timeoutInSeconds)->get($url)->body(); + + return new self((string) $xml); } public static function fromXml(string $xml): self diff --git a/tests/PoddleTest.php b/tests/PoddleTest.php index 23163db..97408ec 100644 --- a/tests/PoddleTest.php +++ b/tests/PoddleTest.php @@ -2,12 +2,18 @@ namespace Tests; +use GuzzleHttp\Psr7\Stream; +use GuzzleHttp\Psr7\Utils; use Illuminate\Support\Facades\Http; use Illuminate\Support\LazyCollection; +use Mockery; use PhanAn\Poddle\Poddle; use PhanAn\Poddle\Values\Channel; use PhanAn\Poddle\Values\Episode; use PhanAn\Poddle\Values\EpisodeCollection; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; class PoddleTest extends TestCase { @@ -35,6 +41,34 @@ public function testParseUrl(): void self::assertEpisodes($parser->getEpisodes()); } + public function testParseUrlUsingClientInterface(): void + { + $clientMock = Mockery::mock(ClientInterface::class); + + $responseMock = Mockery::mock(ResponseInterface::class, [ + 'getBody' => Utils::streamFor(file_get_contents(__DIR__ . '/fixtures/sample.xml')), + ]); + + $clientMock->shouldReceive('sendRequest')->with( + Mockery::on(static function (RequestInterface $request): bool { + self::assertSame('GET', $request->getMethod()); + self::assertSame('https://mypodcast.com/feed', (string) $request->getUri()); + self::assertSame('45', $request->getHeader('connect_timeout')[0]); + + return true; + }) + )->andReturn($responseMock); + + $parser = Poddle::fromUrl( + url: 'https://mypodcast.com/feed', + timeoutInSeconds: 45, + client: $clientMock + ); + + self::assertChannel($parser->getChannel()); + self::assertEpisodes($parser->getEpisodes()); + } + private static function assertChannel(Channel $channel): void { self::assertEquals([ @@ -85,7 +119,7 @@ private static function assertChannel(Channel $channel): void ], $channel->toArray()); } - public function assertEpisodes(EpisodeCollection $episodes): void + private function assertEpisodes(EpisodeCollection $episodes): void { self::assertInstanceOf(LazyCollection::class, $episodes); self::assertSame(8, $episodes->count());