Skip to content

Commit

Permalink
feat(Context): Add and use ContextServiceContract instead of a implem…
Browse files Browse the repository at this point in the history
…entation class + improve phpstan support

- Add ContextServiceContractAssert
- ContextServiceContract is a singleton

BREAKING CHANGE: AbstractContext uses ContextServiceContract instead of a class.
  • Loading branch information
pionl committed Dec 22, 2022
1 parent 7dfab17 commit 4f8557c
Show file tree
Hide file tree
Showing 23 changed files with 710 additions and 51 deletions.
3 changes: 3 additions & 0 deletions src/Context/ContextServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
namespace LaraStrict\Context;

use Illuminate\Support\ServiceProvider;
use LaraStrict\Context\Contracts\ContextServiceContract;
use LaraStrict\Context\Services\ContextEventsService;
use LaraStrict\Context\Services\ContextService;

class ContextServiceProvider extends ServiceProvider
{
Expand All @@ -16,5 +18,6 @@ public function register(): void
// Make the service context singleton - if we are using heavy dependency injection it will slow down
// resolving if not singleton
$this->app->singleton(ContextEventsService::class, ContextEventsService::class);
$this->app->singleton(ContextServiceContract::class, ContextService::class);
}
}
4 changes: 2 additions & 2 deletions src/Context/Contexts/AbstractContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace LaraStrict\Context\Contexts;

use LaraStrict\Context\Contracts\ContextServiceContract;
use LaraStrict\Context\Contracts\ContextValueContract;
use LaraStrict\Context\Services\ContextEventsService;
use LaraStrict\Context\Services\ContextService;

/**
* Context allows us to access data across multiple services / actions without loading data again using dependency
Expand All @@ -22,7 +22,7 @@ public function getCacheTtl(): int
return 3600;
}

abstract public function get(ContextService $contextService): ContextValueContract;
abstract public function get(ContextServiceContract $contextService): ContextValueContract;

abstract public function getCacheKey(): string;

Expand Down
7 changes: 4 additions & 3 deletions src/Context/Contexts/AbstractIsContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace LaraStrict\Context\Contexts;

use Closure;
use LaraStrict\Context\Services\ContextService;
use LaraStrict\Context\Contracts\ContextServiceContract;
use LaraStrict\Context\Values\BoolContextValue;

/**
Expand All @@ -14,13 +14,14 @@
*/
abstract class AbstractIsContext extends AbstractContext
{
public function get(ContextService $contextService): BoolContextValue
public function get(ContextServiceContract $contextService): BoolContextValue
{
return $contextService->is($this, $this->is());
}

/**
* @return Closure():bool
* @return Closure(mixed...):bool
* @phpstan-return Closure(mixed,mixed,mixed,mixed,mixed):bool
*/
abstract public function is(): Closure;
}
46 changes: 46 additions & 0 deletions src/Context/Contracts/ContextServiceContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Context\Contracts;

use Closure;
use LaraStrict\Context\Contexts\AbstractContext;
use LaraStrict\Context\Contexts\AbstractIsContext;
use LaraStrict\Context\Values\BoolContextValue;

/**
* Shareable context values between logic that needs same data. Stored in memory and if context supports its it will
* stores in cache repository (only usable with boot).
*/
interface ContextServiceContract
{
public function delete(AbstractContext $context): void;

/**
* Stores state to memory (and cache if supported).
*/
public function set(AbstractContext $context, ContextValueContract $value): void;

public function setWithoutCache(AbstractContext $context, ContextValueContract $value): void;

/**
* @template T of ContextValueContract
*
* @param Closure(mixed...):T $createState
* @phpstan-param Closure(mixed,mixed,mixed,mixed,mixed,mixed):T $createState
*
* @return T
*/
public function get(AbstractContext $context, Closure $createState): ContextValueContract;

/**
* Returns bool state of the context.
*
* @param Closure():bool $is
* @phpstan-param Closure(mixed,mixed,mixed,mixed,mixed,mixed):bool $is
*/
public function is(AbstractIsContext $context, Closure $is): BoolContextValue;

public function getCacheKey(AbstractContext $context): string;
}
22 changes: 0 additions & 22 deletions src/Context/Services/ContextCallService.php

This file was deleted.

3 changes: 2 additions & 1 deletion src/Context/Services/ContextEventsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Model;
use LaraStrict\Context\Contexts\AbstractContext;
use LaraStrict\Context\Contracts\ContextServiceContract;

class ContextEventsService
{
public function __construct(
private readonly Dispatcher $eventsDispatcher,
private readonly ContextService $contextService,
private readonly ContextServiceContract $contextService,
private readonly Container $container
) {
}
Expand Down
27 changes: 5 additions & 22 deletions src/Context/Services/ContextService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,23 @@
namespace LaraStrict\Context\Services;

use Closure;
use Illuminate\Cache\Repository;
use Illuminate\Contracts\Container\Container;
use LaraStrict\Cache\Contracts\CacheMeServiceContract;
use LaraStrict\Cache\Enums\CacheMeStrategy;
use LaraStrict\Context\Concerns\UseCache;
use LaraStrict\Context\Concerns\UseCacheWithTags;
use LaraStrict\Context\Contexts\AbstractContext;
use LaraStrict\Context\Contexts\AbstractIsContext;
use LaraStrict\Context\Contracts\ContextServiceContract;
use LaraStrict\Context\Contracts\ContextValueContract;
use LaraStrict\Context\Values\BoolContextValue;
use LaraStrict\Core\Services\ImplementsService;

/**
* Shareable context values between logic that needs same data. Stored in memory and if context supports its it will
* stores in cache repository (only usable with boot).
*/
class ContextService
class ContextService implements ContextServiceContract
{
protected const TAG = 'context';

public function __construct(
private readonly ContextCallService $callService,
private readonly CacheMeServiceContract $cacheMeManager,
private readonly ImplementsService $implementsService
) {
Expand Down Expand Up @@ -67,36 +62,24 @@ public function setWithoutCache(AbstractContext $context, ContextValueContract $
);
}

/**
* @template T of ContextValueContract
*
* @param Closure(mixed,mixed,mixed): T $createState Create the state
*
* @return T
*/
public function get(AbstractContext $context, Closure $createState): ContextValueContract
{
$fullCacheKey = $this->getCacheKey($context);

return $this->cacheMeManager->get(
key: $fullCacheKey,
getValue: fn () => $this->callService->createState($context, $createState),
getValue: $createState,
tags: $this->getTags($context),
minutes: $context->getCacheTtl(),
strategy: $this->cacheStrategy($context)
);
}

/**
* Returns bool state of the context.
*
* @param Closure():bool $is
*/
public function is(AbstractIsContext $context, Closure $is): BoolContextValue
{
return $this->get(
$context,
static fn (Container $container) => new BoolContextValue((bool) $container->call($is))
context: $context,
createState: static fn (Container $container) => new BoolContextValue((bool) $container->call($is))
);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Testing/Cache/Contracts/CacheMeServiceContractAssert.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ public function get(
Assert::assertEquals($expectation->minutes, $minutes, $message);
Assert::assertEquals($expectation->strategy, $strategy, $message);

return $getValue();
$callGetValueHook = $expectation->callGetValueHook;

return $callGetValueHook instanceof Closure === false ? $getValue() : $callGetValueHook($getValue);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@

namespace LaraStrict\Testing\Cache\Contracts;

use Closure;
use LaraStrict\Cache\Constants\CacheExpirations;
use LaraStrict\Cache\Enums\CacheMeStrategy;

final class CacheMeServiceContractGetExpectation
{
/**
* @param Closure(Closure):mixed|null $callGetValueHook
*/
public function __construct(
public readonly string $key,
public readonly array $tags = [],
public readonly int $minutes = CacheExpirations::HalfDay,
public readonly CacheMeStrategy $strategy = CacheMeStrategy::MemoryAndRepository,
public readonly ?Closure $callGetValueHook = null
) {
}
}
130 changes: 130 additions & 0 deletions src/Testing/Context/Contracts/ContextServiceContractAssert.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Testing\Context\Contracts;

use Closure;
use LaraStrict\Context\Contexts\AbstractContext;
use LaraStrict\Context\Contexts\AbstractIsContext;
use LaraStrict\Context\Contracts\ContextServiceContract;
use LaraStrict\Context\Contracts\ContextValueContract;
use LaraStrict\Context\Values\BoolContextValue;
use LaraStrict\Testing\AbstractExpectationCallsMap;
use PHPUnit\Framework\Assert;

class ContextServiceContractAssert extends AbstractExpectationCallsMap implements ContextServiceContract
{
/**
* @param array<ContextServiceContractDeleteExpectation> $delete
* @param array<ContextServiceContractSetExpectation> $set
* @param array<ContextServiceContractSetWithoutCacheExpectation> $setWithoutCache
* @param array<ContextServiceContractGetExpectation> $get
* @param array<ContextServiceContractIsExpectation> $is
* @param array<ContextServiceContractGetCacheKeyExpectation> $getCacheKey
*/
public function __construct(
array $delete = [],
array $set = [],
array $setWithoutCache = [],
array $get = [],
array $is = [],
array $getCacheKey = [],
) {
$this->setExpectations(ContextServiceContractDeleteExpectation::class, array_values(array_filter($delete)));
$this->setExpectations(ContextServiceContractSetExpectation::class, array_values(array_filter($set)));
$this->setExpectations(
ContextServiceContractSetWithoutCacheExpectation::class,
array_values(array_filter($setWithoutCache))
);
$this->setExpectations(ContextServiceContractGetExpectation::class, array_values(array_filter($get)));
$this->setExpectations(ContextServiceContractIsExpectation::class, array_values(array_filter($is)));
$this->setExpectations(
ContextServiceContractGetCacheKeyExpectation::class,
array_values(array_filter($getCacheKey))
);
}

public function delete(AbstractContext $context): void
{
$expectation = $this->getExpectation(ContextServiceContractDeleteExpectation::class);
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->context, $context, $message);

if (is_callable($expectation->hook)) {
call_user_func($expectation->hook, $context, $expectation);
}
}

public function set(AbstractContext $context, ContextValueContract $value): void
{
$expectation = $this->getExpectation(ContextServiceContractSetExpectation::class);
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->context, $context, $message);
Assert::assertEquals($expectation->value, $value, $message);

if (is_callable($expectation->hook)) {
call_user_func($expectation->hook, $context, $value, $expectation);
}
}

public function setWithoutCache(AbstractContext $context, ContextValueContract $value): void
{
$expectation = $this->getExpectation(ContextServiceContractSetWithoutCacheExpectation::class);
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->context, $context, $message);
Assert::assertEquals($expectation->value, $value, $message);

if (is_callable($expectation->hook)) {
call_user_func($expectation->hook, $context, $value, $expectation);
}
}

public function get(AbstractContext $context, Closure $createState): ContextValueContract
{
$expectation = $this->getExpectation(ContextServiceContractGetExpectation::class);
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->context, $context, $message);
Assert::assertEquals($expectation->createState, $createState, $message);

if (is_callable($expectation->hook)) {
call_user_func($expectation->hook, $context, $createState, $expectation);
}

/** @phpstan-ignore-next-line */
return $expectation->return;
}

public function is(AbstractIsContext $context, Closure $is): BoolContextValue
{
$expectation = $this->getExpectation(ContextServiceContractIsExpectation::class);
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->context, $context, $message);
Assert::assertEquals($expectation->is, $is, $message);

if (is_callable($expectation->hook)) {
call_user_func($expectation->hook, $context, $is, $expectation);
}

return $expectation->return;
}

public function getCacheKey(AbstractContext $context): string
{
$expectation = $this->getExpectation(ContextServiceContractGetCacheKeyExpectation::class);
$message = $this->getDebugMessage();

Assert::assertEquals($expectation->context, $context, $message);

if (is_callable($expectation->hook)) {
call_user_func($expectation->hook, $context, $expectation);
}

return $expectation->return;
}
}
Loading

0 comments on commit 4f8557c

Please sign in to comment.