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);