Open boesing opened 2 years ago
@boesing
I would like to add the description for the integration in laminas-mvc, with a quick and convenient way.
What is the best option to use multiple storage adapters with ReflectionBasedAbstractFactory
of laminas-servicemanager?
I have no idea. I do not use ReflectionBasedAbstractFactory
at all in my applications and thus, do not have that problem.
We are fetching adapters via caches
config entry.
The key for the caches
entry is stored in a constant like:
final class Caches
{
public const KEY_VALUE_CACHE = 'key-value-cache';
}
Then we do use something like:
use Psr\SimpleCache\CacheInterface;
interface KeyValueCacheInterface extends CacheInterface
{}
In combination with a specific factory:
final class KeyValueCacheFactory
{
public function __invoke(ContainerInterface $container):
{
// returns the storage interface based on the `caches` configuration
$storage = $container->get(Caches::KEY_VALUE_CACHE);
return new SimpleCacheDecorator($storage);
}
}
One could provide an abstract decorator to actually implement the real interface by using something like the following instead of directly returning the simple cache decorator:
return new class(new SimpleCacheDecorator($storage)) extends AbstractSimpleCacheDecorator implements KeyValueCacheInterface
{
}
But as I said, we do not use ReflectionBasedAbstractFactory
so thats only an idea.
I have no idea. I do not use
ReflectionBasedAbstractFactory
at all in my applications and thus, do not have that problem.
It's quite simple, the name for the cache must be an existing class or interface. Example:
return [
'caches' => [
Laminas\Cache\Storage\StorageInterface::class => [
'adapter' => Laminas\Cache\Storage\Adapter\Filesystem::class,
'options' => [
'cache_dir' => __DIR__ . '/../../data/cache',
],
],
],
// …
];
This will work with the ReflectionBasedAbstractFactory
:
namespace Application\Controller;
use Laminas\Cache\Storage\StorageInterface;
use Laminas\Mvc\Controller\AbstractActionController;
final class IndexController extends AbstractActionController
{
private StorageInterface $cache;
public function __construct(StorageInterface $cache)
{
$this->cache = $cache;
}
// …
}
return [
'controllers' => [
'factories' => [
Application\Controller\IndexController::class => Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory::class,
],
],
// …
];
But for multiple storage adapters, a different name is needed and the name of the generic storage interface no longer works. And using the concrete class name of a storage adapter binds it to the type of storage which is also not desired.
Then we do use something like:
use Psr\SimpleCache\CacheInterface; interface KeyValueCacheInterface extends CacheInterface {}
The idea of creating a separate interface is good, but on the other hand it is another step. :thinking:
Another option is ConfigAbstractFactory
of laminas-servicemanager, which needs some more configuration but allows any string as a name for the dependency. Example:
return [
'caches' => [
'default-cache' => [
'adapter' => Laminas\Cache\Storage\Adapter\Filesystem::class,
'options' => [
'cache_dir' => __DIR__ . '/../../data/cache',
],
],
],
// …
];
return [
'controllers' => [
'factories' => [
Application\Controller\IndexController::class => Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory::class,
],
],
Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory::class => [
Application\Controller\IndexController::class => [
'default-cache',
],
],
// …
];
@boesing
How could the Mezzio integration look like if the abstract factory (StorageCacheAbstractServiceFactory
) can not be used with Pimple or Aura.DI?
I am looking again for a simple solution that is user-friendly without having to create classes or interfaces.
How could the Mezzio integration look like if the abstract factory (
StorageCacheAbstractServiceFactory
) can not be used with Pimple or Aura.DI?
If you refer to one of those only allowing objects as services, most of our factories need an array and some are outright asserting config is array. Those factories are not usable with aura.di
How could the Mezzio integration look like if the abstract factory (StorageCacheAbstractServiceFactory) can not be used with Pimple or Aura.DI?
I have no clue, havent ever worked with these containers.
Wasn't even aware that these do not support abstract_factories
or initializers
.
I'd rather implement proper support for these than suggesting people to use stuff in the other way.
Most if not any component provided by laminas is somehow built to be used with the servicemanager.
It will even get installed when requiring this component.
I am not sure if I feel confident when we have projects out there consuming components which are meant to be consumed via the service-manager as the ConfigProvider
does provide service-manager specific config and then it is not consumed via the service-manager but via an implementation which does only support a part of what servicemanager supports.
We could probably decorate these containers and provide abstract_factories
support in the appropriate components?
https://github.com/laminas/laminas-auradi-config https://github.com/laminas/laminas-pimple-config
Something like
use Psr\Container\ContainerInterface;
final class AbstractFactoriesContainerDecorator implements ContainerInterface
{
public function __construct(private readonly ContainerInterface $container, private readonly array $abstractFactories)
{}
public function get(string $id): mixed
{
try {
return $this->container->get($id);
} catch (ServiceNotFoundException $exception) {
foreach ($this->abstractFactories as $factory) {
if ($factory->canCreate($id)) { return $factory($this, $id); );
}
throw $exception;
}
}
public function has(string $id): bool
{
if ($this->container->has($id)) { return true; }
foreach ($this->abstractFactories as $factory) {
if ($factory->canCreate($id)) { return true; }
}
return false;
}
}
Please keep in mind that ReflectionBasedAbstractFactory
is not able to retrieve services from the container which are not identified as string(config)
or class-string
. So these projects you are mentioning above will most likely already have an class-string/interface alias for their code. I personally do not use the ConfigAbstractFactory
but yes, that could be a solution as well.
I have no clue, havent ever worked with these containers. Wasn't even aware that these do not support
abstract_factories
orinitializers
.
I have never used them either and was surprised that there is no full support.
We could probably decorate these containers and provide abstract_factories support in the appropriate components?
I think this would be useful and necessary.
@boesing
For Mezzio the ConfigAbstractFactory
can also be used:
return [
Laminas\Cache\Service\StorageCacheAbstractServiceFactory::CACHES_CONFIGURATION_KEY => [
'default-cache' => [
'adapter' => Laminas\Cache\Storage\Adapter\Filesystem::class,
'options' => [
'cache_dir' => __DIR__ . '/../../data/cache',
],
],
],
];
namespace App;
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Mezzio\Template\TemplateRendererInterface;
final class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
ConfigAbstractFactory::class => $this->getConfigurationMap(),
];
}
public function getDependencies(): array
{
return [
'factories' => [
Handler\ExampleHandler::class => ConfigAbstractFactory::class,
],
];
}
public function getConfigurationMap(): array
{
return [
Handler\ExampleHandler::class => [
'default-cache',
TemplateRendererInterface::class,
],
];
}
// …
}
namespace App\Handler;
use Laminas\Cache\Storage\StorageInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Mezzio\Template\TemplateRendererInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class ExampleHandler implements RequestHandlerInterface
{
public function __construct(
private readonly StorageInterface $cache,
private readonly TemplateRendererInterface $templateRenderer
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
if (! $this->cache->hasItem('example')) {
$this->cache->addItem('example', 'value');
}
$example = $this->cache->getItem('example'); // value;
return new HtmlResponse(
$this->templateRenderer->render('app::example')
);
}
}
What do you think?
Sure, that could be a way and thus could be mentioned for sure 👍🏻
works for me.
Documentation Improvements
Summary
We do actually lacking examples on how to integrate this library into
laminas-mvc
ormezzio
. One of our users found an old blog post written by @samsonasik in 2013 (pointed me on that via Slack).Luckily, I've chose that configuration style (just the
plugins
config is not compatible with v3.0) and thus that helped the user. I would prefer having these examples as part of our documentation.TL;DR
Add examples on how to implement
caches
configuration for theStorageCacheAbstractServiceFactory
andcache
configuration for theStorageCacheFactory
.