Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/Downloader/Downloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@

namespace RoachPHP\Downloader;

use RoachPHP\Downloader\Middleware\ExceptionMiddlewareInterface;
use RoachPHP\Events\RequestDropped;
use RoachPHP\Events\RequestSending;
use RoachPHP\Exception\Exception;
use RoachPHP\Http\ClientInterface;
use RoachPHP\Http\Request;
use RoachPHP\Http\Response;
Expand Down Expand Up @@ -78,14 +80,16 @@ public function prepare(Request $request): void
$this->requests[] = $event->request;
}

public function flush(?callable $callback = null): void
public function flush(?callable $onFullfilled = null): void
{
$requests = $this->requests;

$this->requests = [];

$this->client->pool($requests, function (Response $response) use ($callback): void {
$this->onResponseReceived($response, $callback);
$this->client->pool($requests, function (Response $response) use ($onFullfilled): void {
$this->onResponseReceived($response, $onFullfilled);
}, function (Exception $exception): void {
$this->onExceptionHandled($exception);
});
}

Expand All @@ -103,4 +107,11 @@ private function onResponseReceived(Response $response, ?callable $callback): vo
$callback($response);
}
}

private function onExceptionHandled(Exception $exception): void
{
foreach ($this->middleware as $middleware) {
$exception = $middleware->handleException($exception);
}
}
}
6 changes: 5 additions & 1 deletion src/Downloader/DownloaderMiddlewareInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@

namespace RoachPHP\Downloader;

use RoachPHP\Downloader\Middleware\ExceptionMiddlewareInterface;
use RoachPHP\Downloader\Middleware\RequestMiddlewareInterface;
use RoachPHP\Downloader\Middleware\ResponseMiddlewareInterface;

interface DownloaderMiddlewareInterface extends RequestMiddlewareInterface, ResponseMiddlewareInterface
interface DownloaderMiddlewareInterface extends
RequestMiddlewareInterface,
ResponseMiddlewareInterface,
ExceptionMiddlewareInterface
{
}
14 changes: 12 additions & 2 deletions src/Downloader/Middleware/DownloaderMiddlewareAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace RoachPHP\Downloader\Middleware;

use RoachPHP\Downloader\DownloaderMiddlewareInterface;
use RoachPHP\Exception\Exception;
use RoachPHP\Http\Request;
use RoachPHP\Http\Response;

Expand All @@ -23,12 +24,12 @@
final class DownloaderMiddlewareAdapter implements DownloaderMiddlewareInterface
{
private function __construct(
private RequestMiddlewareInterface | ResponseMiddlewareInterface $middleware,
private RequestMiddlewareInterface | ResponseMiddlewareInterface | ExceptionMiddlewareInterface $middleware,
) {
}

public static function fromMiddleware(
RequestMiddlewareInterface|ResponseMiddlewareInterface $middleware,
RequestMiddlewareInterface|ResponseMiddlewareInterface|ExceptionMiddlewareInterface $middleware,
): DownloaderMiddlewareInterface {
if ($middleware instanceof DownloaderMiddlewareInterface) {
return $middleware;
Expand All @@ -55,6 +56,15 @@ public function handleResponse(Response $response): Response
return $response;
}

public function handleException(Exception $exception): Exception
{
if ($this->middleware instanceof ExceptionMiddlewareInterface) {
return $this->middleware->handleException($exception);
}

return $exception;
}

public function configure(array $options): void
{
$this->middleware->configure($options);
Expand Down
22 changes: 22 additions & 0 deletions src/Downloader/Middleware/ExceptionMiddlewareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2022 Pavlo Komarov
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*
* @see https://github.com/roach-php/roach
*/

namespace RoachPHP\Downloader\Middleware;

use RoachPHP\Exception\Exception;
use RoachPHP\Support\ConfigurableInterface;

interface ExceptionMiddlewareInterface extends ConfigurableInterface
{
public function handleException(Exception $exception): Exception;
}
24 changes: 22 additions & 2 deletions src/Downloader/Middleware/FakeMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Closure;
use PHPUnit\Framework\Assert;
use RoachPHP\Downloader\DownloaderMiddlewareInterface;
use RoachPHP\Exception\Exception;
use RoachPHP\Http\Request;
use RoachPHP\Http\Response;
use RoachPHP\Support\Configurable;
Expand All @@ -36,12 +37,20 @@ final class FakeMiddleware implements DownloaderMiddlewareInterface
*/
private array $responsesHandled = [];

/**
* @var array|Exception[]
*/
private array $exceptionsHandled = [];

/**
* @param ?Closure(Request): Request $requestHandler
* @param ?Closure(Response): Response $responseHandler
*/
public function __construct(private ?Closure $requestHandler = null, private ?Closure $responseHandler = null)
{
public function __construct(
private ?Closure $requestHandler = null,
private ?Closure $responseHandler = null,
private ?Closure $exceptionHandler = null
) {
}

public function handleRequest(Request $request): Request
Expand All @@ -66,6 +75,17 @@ public function handleResponse(Response $response): Response
return $response;
}

public function handleException(Exception $exception): Exception
{
$this->exceptionsHandled[] = $exception;

if (null !== $this->exceptionHandler) {
return ($this->exceptionHandler)($exception);
}

return $exception;
}

public function assertRequestHandled(Request $request): void
{
Assert::assertContains($request, $this->requestsHandled);
Expand Down
78 changes: 78 additions & 0 deletions src/Downloader/Middleware/RetryMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2022 Pavlo Komarov
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*
* @see https://github.com/roach-php/roach
*/

namespace RoachPHP\Downloader\Middleware;

use JetBrains\PhpStorm\ArrayShape;
use RoachPHP\Events\RequestDropped;
use RoachPHP\Events\RequestScheduling;
use RoachPHP\Exception\Exception;
use RoachPHP\Http\Request;
use RoachPHP\Scheduling\RequestSchedulerInterface;
use RoachPHP\Support\Configurable;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class RetryMiddleware implements ExceptionMiddlewareInterface, RequestMiddlewareInterface
{
use Configurable;

private RequestSchedulerInterface $requestScheduler;
private EventDispatcherInterface $eventDispatcher;

public function __construct(RequestSchedulerInterface $requestScheduler, EventDispatcherInterface $eventDispatcher)
{
$this->requestScheduler = $requestScheduler;
$this->eventDispatcher = $eventDispatcher;
}

public function handleRequest(Request $request): Request
{
return $request->withMeta('max_retries', $this->option('max_retries'));
}


public function handleException(Exception $exception): Exception
{
$request = $exception->getRequest();

$retries = $request->getMeta('retries', 0);
if ($retries < $request->getMeta('max_retries')) {
$request = $request->withMeta('retries', ++$retries);
$this->eventDispatcher->dispatch(
new RequestScheduling($request),
RequestScheduling::NAME,
);

if ($request->wasDropped()) {
$this->eventDispatcher->dispatch(
new RequestDropped($request),
RequestDropped::NAME,
);

return $exception;
}

$this->requestScheduler->schedule($request);
}

return $exception;
}

#[ArrayShape(['max_retries' => "int"])]
private function defaultOptions(): array
{
return [
'max_retries' => 5,
];
}
}
39 changes: 39 additions & 0 deletions src/Exception/Exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2022 Pavlo Komarov
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*
* @see https://github.com/roach-php/roach
*/

namespace RoachPHP\Exception;

use RoachPHP\Http\Request;

class Exception extends \Exception
{
private Request $request;

public function __construct(Request $request, string $message = "", int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->request = $request;
}

public function getRequest(): Request
{
return $this->request;
}

public function setRequest(Request $request): static
{
$this->request = $request;

return $this;
}
}
15 changes: 13 additions & 2 deletions src/Http/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

use Generator;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Pool;
use Psr\Http\Message\ResponseInterface;
use RoachPHP\Exception\Exception;

final class Client implements ClientInterface
{
Expand All @@ -30,21 +32,30 @@ public function __construct(?GuzzleClient $client = null)
/**
* @param Request[] $requests
*/
public function pool(array $requests, ?callable $onFulfilled = null): void
public function pool(array $requests, ?callable $onFulfilled = null, ?callable $onRejected = null): void
{
$makeRequests = function () use ($requests): Generator {
foreach ($requests as $request) {
yield function () use ($request) {
return $this->client
->sendAsync($request->getPsrRequest(), $request->getOptions())
->then(static fn (ResponseInterface $response) => new Response($response, $request));
->then(
static fn (ResponseInterface $response) => new Response($response, $request),
static fn (GuzzleException $e) => throw new Exception(
$request,
$e->getMessage(),
$e->getCode(),
$e
),
);
};
}
};

$pool = new Pool($this->client, $makeRequests(), [
'concurrency' => 0,
'fulfilled' => $onFulfilled,
'rejected' => $onRejected,
]);

$pool->promise()->wait();
Expand Down
3 changes: 2 additions & 1 deletion src/Http/ClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface ClientInterface
/**
* @param Request[] $requests
* @param ?callable(Response): void $onFulfilled
* @param ?callable(Response): void $onRejected
*/
public function pool(array $requests, ?callable $onFulfilled = null): void;
public function pool(array $requests, ?callable $onFulfilled = null, ?callable $onRejected = null): void;
}
2 changes: 1 addition & 1 deletion src/Http/FakeClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class FakeClient implements ClientInterface
*/
private array $sentRequestUrls = [];

public function pool(array $requests, ?callable $onFulfilled = null): void
public function pool(array $requests, ?callable $onFulfilled = null, ?callable $onRejected = null): void
{
foreach ($requests as $request) {
$this->sentRequestUrls[] = $request->getUri();
Expand Down
2 changes: 1 addition & 1 deletion src/Roach.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private static function defaultContainer(): ContainerInterface
$container->share(EventDispatcher::class, EventDispatcher::class);
$container->share(EventDispatcherInterface::class, EventDispatcher::class);
$container->add(ClockInterface::class, SystemClock::class);
$container->add(
$container->share(
RequestSchedulerInterface::class,
/** @psalm-suppress MixedReturnStatement, MixedInferredReturnType */
static fn (): RequestSchedulerInterface => $container->get(ArrayRequestScheduler::class),
Expand Down
6 changes: 6 additions & 0 deletions tests/Downloader/DownloaderMiddlewareAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use RoachPHP\Downloader\Middleware\DownloaderMiddlewareAdapter;
use RoachPHP\Downloader\Middleware\RequestMiddlewareInterface;
use RoachPHP\Downloader\Middleware\ResponseMiddlewareInterface;
use RoachPHP\Exception\Exception;
use RoachPHP\Http\Request;
use RoachPHP\Http\Response;
use RoachPHP\Support\Configurable;
Expand All @@ -45,6 +46,11 @@ public function handleResponse(Response $response): Response
{
return $response;
}

public function handleException(Exception $exception): Exception
{
return $exception;
}
};

$class = DownloaderMiddlewareAdapter::fromMiddleware($middleware);
Expand Down