eliashaeussler/cache-warmup

View on GitHub
src/Http/Message/RequestPoolFactory.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

declare(strict_types=1);

/*
 * This file is part of the Composer package "eliashaeussler/cache-warmup".
 *
 * Copyright (C) 2020-2024 Elias Häußler <elias@haeussler.dev>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

namespace EliasHaeussler\CacheWarmup\Http\Message;

use Generator;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Pool;
use GuzzleHttp\Promise;
use Psr\Http\Message;
use Throwable;

use function array_values;

/**
 * RequestPoolFactory.
 *
 * @author Elias Häußler <elias@haeussler.dev>
 * @license GPL-3.0-or-later
 */
final class RequestPoolFactory
{
    private ClientInterface $client;
    private int $concurrency = 5;

    /**
     * @var array<string, mixed>
     */
    private array $options = [];

    /**
     * @var list<Handler\ResponseHandler>
     */
    private array $handlers = [];
    private bool $stopOnFailure = false;

    /**
     * @var array<int, Message\RequestInterface>
     */
    private array $visited = [];

    /**
     * @param iterable<int, Message\RequestInterface> $requests
     */
    private function __construct(
        private readonly iterable $requests,
    ) {
        $this->client = new Client();
    }

    /**
     * @param iterable<int, Message\RequestInterface> $requests
     */
    public static function create(iterable $requests): self
    {
        return new self($requests);
    }

    public function createPool(): Pool
    {
        return new Pool(
            $this->client,
            $this->decorateRequests(),
            [
                'concurrency' => $this->concurrency,
                'options' => $this->options,
                'fulfilled' => $this->onFulfilled(...),
                'rejected' => $this->onRejected(...),
            ],
        );
    }

    private function onFulfilled(Message\ResponseInterface $response, int $index): void
    {
        foreach ($this->handlers as $handler) {
            $handler->onSuccess($response, $this->visited[$index]->getUri());
        }
    }

    private function onRejected(Throwable $throwable, int $index, Promise\Promise $aggregate): void
    {
        foreach ($this->handlers as $handler) {
            $handler->onFailure($throwable, $this->visited[$index]->getUri());
        }

        if ($this->stopOnFailure) {
            $aggregate->cancel();
        }
    }

    /**
     * @return Generator<Message\RequestInterface>
     */
    private function decorateRequests(): Generator
    {
        foreach ($this->requests as $index => $request) {
            yield $this->visited[$index] = $request;
        }
    }

    public function withClient(ClientInterface $client): self
    {
        $clone = clone $this;
        $clone->client = $client;

        return $clone;
    }

    public function withConcurrency(int $concurrency): self
    {
        $clone = clone $this;
        $clone->concurrency = $concurrency;

        return $clone;
    }

    /**
     * @param array<string, mixed> $options
     */
    public function withOptions(array $options): self
    {
        $clone = clone $this;
        $clone->options = $options;

        return $clone;
    }

    public function withResponseHandler(Handler\ResponseHandler ...$handler): self
    {
        $clone = clone $this;
        $clone->handlers = [...$clone->handlers, ...array_values($handler)];

        return $clone;
    }

    public function withStopOnFailure(bool $stopOnFailure = true): self
    {
        $clone = clone $this;
        $clone->stopOnFailure = $stopOnFailure;

        return $clone;
    }
}