Skip to content
Open
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
7 changes: 5 additions & 2 deletions src/Contracts/Cacheable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Saloon\CachePlugin\Contracts;

use DateTimeImmutable;
use Saloon\Http\Response;

interface Cacheable
{
/**
Expand All @@ -12,7 +15,7 @@ interface Cacheable
public function resolveCacheDriver(): Driver;

/**
* Define the cache expiry in seconds
* Resolve the cache expiry in seconds or as an DateTimeImmutable
*/
public function cacheExpiryInSeconds(): int;
public function resolveCacheExpiry(Response $response): DateTimeImmutable|int;
}
3 changes: 0 additions & 3 deletions src/Contracts/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Saloon\CachePlugin\Contracts;

use Saloon\Data\RecordedResponse;
use Saloon\CachePlugin\Data\CachedResponse;

interface Driver
Expand All @@ -16,8 +15,6 @@ public function set(string $key, CachedResponse $cachedResponse): void;

/**
* Get the cached response from the driver.
*
* @return RecordedResponse|null
*/
public function get(string $cacheKey): ?CachedResponse;

Expand Down
10 changes: 9 additions & 1 deletion src/Data/CachedResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Saloon\CachePlugin\Data;

use DateInterval;
use DateTimeImmutable;
use Saloon\Data\RecordedResponse;
use Saloon\Http\Faking\FakeResponse;
Expand All @@ -17,7 +18,6 @@ class CachedResponse
public function __construct(
readonly public RecordedResponse $recordedResponse,
readonly public DateTimeImmutable $expiresAt,
readonly public int $ttl,
) {
//
}
Expand All @@ -38,6 +38,14 @@ public function hasNotExpired(): bool
return ! $this->hasExpired();
}

/**
* Get the cache TTL as an interval based on the expiry date.
*/
public function getTtl(): DateInterval
{
return (new DateTimeImmutable())->diff($this->expiresAt);
}

/**
* Create a fake response
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/LaravelCacheDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function __construct(
*/
public function set(string $key, CachedResponse $cachedResponse): void
{
$this->store->set($key, serialize($cachedResponse), $cachedResponse->ttl);
$this->store->set($key, serialize($cachedResponse), $cachedResponse->getTtl());
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Drivers/PsrCacheDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function __construct(
*/
public function set(string $key, CachedResponse $cachedResponse): void
{
$this->store->set($key, serialize($cachedResponse), $cachedResponse->ttl);
$this->store->set($key, serialize($cachedResponse), $cachedResponse->getTtl());
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/Http/Middleware/CacheMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class CacheMiddleware implements RequestMiddleware
*/
public function __construct(
protected Driver $driver,
protected int $ttl,
protected ?string $cacheKey,
protected bool $invalidate = false,
) {
Expand Down Expand Up @@ -63,7 +62,7 @@ public function __invoke(PendingRequest $pendingRequest): ?FakeResponse
// the prepend option, so it runs first.

$pendingRequest->middleware()->onResponse(
callable: new CacheRecorderMiddleware($driver, $this->ttl, $cacheKey),
callable: new CacheRecorderMiddleware($driver, $cacheKey),
order: PipeOrder::FIRST
);

Expand Down
20 changes: 17 additions & 3 deletions src/Http/Middleware/CacheRecorderMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
use Saloon\Data\RecordedResponse;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\Contracts\ResponseMiddleware;
use Saloon\CachePlugin\Contracts\Cacheable;
use Saloon\CachePlugin\Data\CachedResponse;
use Saloon\CachePlugin\Exceptions\HasCachingException;

class CacheRecorderMiddleware implements ResponseMiddleware
{
Expand All @@ -18,7 +20,6 @@ class CacheRecorderMiddleware implements ResponseMiddleware
*/
public function __construct(
protected Driver $driver,
protected int $ttl,
protected string $cacheKey,
) {
//
Expand All @@ -35,11 +36,24 @@ public function __invoke(Response $response): void
return;
}

$expiresAt = new DateTimeImmutable('+' . $this->ttl .' seconds');
$request = $response->getRequest();
$connector = $response->getConnector();

if (! $request instanceof Cacheable && ! $connector instanceof Cacheable) {
throw new HasCachingException(sprintf('Your connector or request must implement %s to use the HasCaching plugin', Cacheable::class));
}

$expiresAt = $request instanceof Cacheable
? $request->resolveCacheExpiry($response)
: $connector->resolveCacheExpiry($response);

if (is_int($expiresAt)) {
$expiresAt = new DateTimeImmutable('+' . $expiresAt .' seconds');
}

$this->driver->set(
key: $this->cacheKey,
cachedResponse: new CachedResponse(RecordedResponse::fromResponse($response), $expiresAt, $this->ttl)
cachedResponse: new CachedResponse(RecordedResponse::fromResponse($response), $expiresAt)
);
}
}
8 changes: 2 additions & 6 deletions src/Traits/HasCaching.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,16 @@ public function bootHasCaching(PendingRequest $pendingRequest): void
? $request->resolveCacheDriver()
: $connector->resolveCacheDriver();

$cacheExpiryInSeconds = $request instanceof Cacheable
? $request->cacheExpiryInSeconds()
: $connector->cacheExpiryInSeconds();

// Register a request middleware which wil handle the caching
// and recording of real responses for caching.

$pendingRequest->middleware()->onRequest(function (PendingRequest $middlewarePendingRequest) use ($cacheDriver, $cacheExpiryInSeconds) {
$pendingRequest->middleware()->onRequest(function (PendingRequest $middlewarePendingRequest) use ($cacheDriver) {
// We'll call the cache middleware invokable class with the $middlewarePendingRequest
// because this $pendingRequest has everything loaded, unlike the instance that
// the plugin is provided. This allows us to have access to body and merged
// properties.

return call_user_func(new CacheMiddleware($cacheDriver, $cacheExpiryInSeconds, $this->cacheKey($middlewarePendingRequest), $this->invalidateCache), $middlewarePendingRequest);
return call_user_func(new CacheMiddleware($cacheDriver, $this->cacheKey($middlewarePendingRequest), $this->invalidateCache), $middlewarePendingRequest);
}, order: PipeOrder::FIRST);
}

Expand Down
17 changes: 17 additions & 0 deletions tests/Feature/CacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Saloon\CachePlugin\Tests\Fixtures\Requests\CachedConnectorRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\AllowedCachedPostRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\CustomKeyCachedUserRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\ResponseBasedExpiryRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\ShortLivedCachedUserRequest;
use Saloon\CachePlugin\Tests\Fixtures\Requests\CachedUserRequestWithoutCacheable;
use Saloon\CachePlugin\Tests\Fixtures\Requests\CachedUserRequestOnCachedConnector;
Expand Down Expand Up @@ -249,6 +250,22 @@
expect($responseC->json())->toEqual(['name' => 'Michael']);
});

test('you can define a cache expiry based on a response', function () {
$expectedExpiry = 90;
$mockClient = new MockClient([
MockResponse::make(['expiry' => $expectedExpiry]),
]);

$connector = new TestConnector;

$request = new ResponseBasedExpiryRequest();
$response = $connector->send($request, $mockClient);

$expiry = $request->resolveCacheExpiry($response);

expect($expiry)->toEqual($expectedExpiry);
});

test('you can define a cache on the connector and it returns a cached response', function () {
$mockClient = new MockClient([
MockResponse::make(['name' => 'Sam']),
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Connectors/CachedConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Saloon\CachePlugin\Tests\Fixtures\Connectors;

use Saloon\Http\Response;
use Saloon\Http\Connector;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
Expand All @@ -26,7 +27,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/AllowedCachedPostRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
Expand Down Expand Up @@ -39,7 +40,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/BodyCacheKeyRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Http\PendingRequest;
use League\Flysystem\Filesystem;
use Saloon\Contracts\Body\HasBody;
Expand Down Expand Up @@ -36,7 +37,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/CachedPostRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
Expand Down Expand Up @@ -39,7 +40,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/CachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
Expand Down Expand Up @@ -41,7 +42,7 @@ public function resolveCacheDriver(): Driver
/**
* Define the cache expiry in seconds
*/
public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
Expand All @@ -29,7 +30,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath() . '/custom')));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 30;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/CustomKeyCachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\Http\PendingRequest;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
Expand Down Expand Up @@ -33,7 +34,7 @@ public function resolveCacheDriver(): Driver
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/LaravelCachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Illuminate\Support\Facades\Cache;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
Expand All @@ -28,7 +29,7 @@ public function resolveCacheDriver(): Driver
return new LaravelCacheDriver(Cache::store('file'));
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
3 changes: 2 additions & 1 deletion tests/Fixtures/Requests/PsrCachedUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
use Saloon\CachePlugin\Contracts\Cacheable;
Expand Down Expand Up @@ -33,7 +34,7 @@ public function resolveCacheDriver(): Driver
return new PsrCacheDriver($this->cache);
}

public function cacheExpiryInSeconds(): int
public function resolveCacheExpiry(Response $response): int
{
return 60;
}
Expand Down
37 changes: 37 additions & 0 deletions tests/Fixtures/Requests/ResponseBasedExpiryRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Saloon\CachePlugin\Tests\Fixtures\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Http\Response;
use League\Flysystem\Filesystem;
use Saloon\CachePlugin\Contracts\Driver;
use Saloon\CachePlugin\Traits\HasCaching;
use Saloon\CachePlugin\Contracts\Cacheable;
use Saloon\CachePlugin\Drivers\FlysystemDriver;
use League\Flysystem\Local\LocalFilesystemAdapter;

class ResponseBasedExpiryRequest extends Request implements Cacheable
{
use HasCaching;

protected Method $method = Method::GET;

public function resolveEndpoint(): string
{
return '/user';
}

public function resolveCacheDriver(): Driver
{
return new FlysystemDriver(new Filesystem(new LocalFilesystemAdapter(cachePath())));
}

public function resolveCacheExpiry(Response $response): int
{
return $response->json()['expiry'];
}
}
Loading