2

I have a 3rd party API which I use to fetch the users and their details from. How can I achieve repository pattern with caching in silverstripe 4?

I have an interface called UserRepositoryInterface

interface UserRepositoryInterface
{
   public function getAll();
}

UserRepository, which interacts with the API to fetch the user and their details

class UserRepository implements UserRepositoryInterface
{
   protected $client;

   public function __construct(Client $client)
   {
      $this->client = $client;
   }

   public function getAll()
   {
      return $this->client->fetchUsers();
   }
}

I know that we need a CachedUserRepository, to fetch the users from cache if existing, otherwise fetch from API directory. How would I go ahead and implement this?

Trying to achieve something like this https://laracasts.com/discuss/channels/laravel/repository-pattern-with-caching-laravel?#reply=398497 in silverstripe 4

scrowler
  • 24,273
  • 9
  • 60
  • 92

1 Answers1

2

If you don't want to separate your UserRepository and CachedUserRepository implementations, you could simply add caching to your UserRepository:

use Psr\SimpleCache\CacheInterface;

class UserRepository implements UserRepositoryInterface
{
    protected $client;

    protected $cache;

    private static $dependencies = [
        'Cache' => '%$' . CacheInterface::class . '.userrepository',
    ];

    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    public function getAll()
    {
        if (!$this->cache->get('fetchUsers')) {
            $users = $this->client->fetchUsers();
            $this->cache->set('fetchUsers', $users);
        }
        return $this->cache->get('fetchUsers');
    }

    public function setCache(CacheInterface $cache)
    {
        $this->cache = $cache;
        return $this;
    }
}

And some YAML configuration to register the cache:

SilverStripe\Core\Injector\Injector:
  Psr\SimpleCache\CacheInterface.userrepository:
    factory: SilverStripe\Core\Cache\CacheFactory
    constructor:
      namespace: userrepository

If you wanted to separate the implementations a way like that in the article you linked to, you could do something similar to what's in the article, but you'd need to define your own method of interacting with the UserRepository since SilverStripe doesn't have this kind of API out of the box.

For example, something like this:

class CachedUserRepository implements UserRepositoryInterface
{
    protected $repository;

    protected $cache;

    private static $dependencies = [
        'Cache' => '%$' . CacheInterface::class . '.userrepository',
    ];

    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    public function getAll()
    {
        if (!$this->cache->get('fetchUsers')) {
            $users = $this->repository->getAll();
            $this->cache->set('fetchUsers', $users);
        }
        return $this->cache->get('fetchUsers');
    }

    public function setCache(CacheInterface $cache)
    {
        $this->cache = $cache;
        return $this;
    }
}

I guess you'd instantiate it like this:

$repository = Injector::inst()->create(CachedUserRepository::class, [
    Injector::inst()->get(UserRepository::class),
]);

Note that it's important to use Injector to instantiate your classes in order for the dependency injection via $dependencies to be registered after construction.

For consistency with the dependency injection patterns in SilverStripe, you might also want to inject the Client into UserRepository in the same way, as well as inject the UserRepository into the CachedUserRepository the same way (constructors removed but not shown in these examples.

UserRepository:

private static $dependencies = [
    'Client' => '%$' . Client::class,
];

public function setClient(Client $client)
{
    $this->client = $client;
    return $this;
}

CachedUserRepository:

private static $dependencies = [
    'Cache' => '%$' . CacheInterface::class . '.userrepository',
    'Repository' => '%$' . UserRepository::class,
];

public function setRepository(UserRepository $repository)
{
    $this->repository = $repository;
    return $this;
}

Now Injector will take care of all dependency injection for you, so your implementation would look like this:

$repository = Injector::inst()->get(CachedUserRepository::class);

You could go one step further (this is a common pattern in SilverStripe 4) and define a concrete implementation for your interface, so the implementation doesn't need to know which class is going to be used:

# File: app/_config/repositories.yml
SilverStripe\Core\Injector\Injector:
  UserRepositoryInterface:
    # Define the repository you want by default
    class: CachedUserRepository

And now you can get your repository (cached by default) like so:

$repository = Injector::inst()->get(UserRepositoryInterface::class);
scrowler
  • 24,273
  • 9
  • 60
  • 92