Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation: Add integration examples for mezzio and laminas-mvc #168

Open
boesing opened this issue Oct 18, 2021 · 10 comments · Fixed by #250
Open

Documentation: Add integration examples for mezzio and laminas-mvc #168

boesing opened this issue Oct 18, 2021 · 10 comments · Fixed by #250

Comments

@boesing
Copy link
Member

boesing commented Oct 18, 2021

Documentation Improvements

Summary

We do actually lacking examples on how to integrate this library into laminas-mvc or mezzio.
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 the StorageCacheAbstractServiceFactory and cache configuration for the StorageCacheFactory.

@froschdesign
Copy link
Member

@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?

@boesing
Copy link
Member Author

boesing commented Aug 2, 2022

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.

@froschdesign
Copy link
Member

froschdesign commented Aug 2, 2022

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. 🤔

@froschdesign
Copy link
Member

froschdesign commented Aug 2, 2022

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',
        ],
    ],
    // …
];

@froschdesign
Copy link
Member

@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.

@Xerkus
Copy link
Member

Xerkus commented Apr 21, 2023

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

@boesing
Copy link
Member Author

boesing commented Apr 21, 2023

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.

@froschdesign
Copy link
Member

I have no clue, havent ever worked with these containers.
Wasn't even aware that these do not support abstract_factories or initializers.

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.

@froschdesign
Copy link
Member

@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?

@boesing
Copy link
Member Author

boesing commented Dec 13, 2023

Sure, that could be a way and thus could be mentioned for sure 👍🏻

works for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

3 participants