"Laminas-DI" parameter resolver misunderstanding

Hi everyone.
Firstly, sorry for my bad English.

I am currently investigating “Laminas” components working in conjunction with “Mezzio”. My general work related to Magento framework and there is a great feature which related to dependencies management. I mean virtual types. In short Virtual types are ability to create multiple instances of the same class with different constructor parameters.

I’ve found in laminas something simillar. In “Laminas-Di” documentation I found this example:

use Laminas\Di\Injector;
use Laminas\Di\Config;

$injector = new Injector(new Config([
    'preferences' => [
        FooInterface::class => Foo::class,
    ],
    'types' => [
        'MyClass.A' => [
            'typeOf' => MyClass::class,
            'preferences' => [
               FooInterface::class => SpecialFoo::class,
            ],
        ],
        'MyClass.B' => [
            'typeOf' => MyClass::class,
            'preferences' => [
               FooInterface::class => Bar::class,
            ],
        ],
    ],
]));

In my case I need make two instances of \Mezzio\LaminasView\LaminasViewRenderer. LaminasViewRenderer has the next constructor specification:

public function __construct(RendererInterface $renderer = null, $layout = null, string $defaultSuffix = null)

I need that the $renderer param will be resolved using psr container, $layout must be ‘admin::layout’ and $suffix may be null for viewRenderer of admin part and the same things for viewRenderer of public part, but $layout must be ‘default::layout’.

inside Admin\ConfigProvider I’ve placed next code:

class ConfigProvider
{
    public function __invoke(): array
    {
        return [
            'dependencies' => $this->getDependencies(),     
        ];
    }

    public function getDependencies(): array
    {
        return [
            'auto' => [
                'types' => [
                    TemplateRenderer\AdminTemplateRendererInterface::class => [
                        'typeOf' => \Mezzio\LaminasView\LaminasViewRenderer::class,
                        'parameters' => [
                            'renderer' => '*',
                            'layout' => 'admin::layout',
                            'defaultSuffix' => '*',
                        ]
                    ]
                ]
            ]
        ];
    }

I expected that I get LaminasViewRenderer instance with ‘admin::layout’ predefined parameter inside auto generated factory. But after I run di-generate-aot (as discribed here https://docs.laminas.dev/laminas-di/cookbook/aot-guide/) script I’ve got next factories class code:

final class AdminTemplateRendererInterfaceFactory implements FactoryInterface
{
    public function create(ContainerInterface $container, array $options = [])
    {
        $args = empty($options)
            ? [
                null, // renderer
                null, // layout     !!! I expected that 'admin::layout' string be here
                null, // defaultSuffix
            ]
            : [
                array_key_exists('renderer', $options) ? $options['renderer'] : null,
                array_key_exists('layout', $options) ? $options['layout'] : null,
                array_key_exists('defaultSuffix', $options) ? $options['defaultSuffix'] : null,
            ];

        return new \Mezzio\LaminasView\LaminasViewRenderer(...$args);
    }

I spent some time for debugging and found out that when DependencyResolver try to configure injection then it search configured params using class name which pointed at the typeOf.
I am not sure but it seems to me a “layout” parameter should be searched by alias to get right result. Currently it returns nothing because there is no key with a LaminasViewRenderer type. And it not make sense. I need create different instances of LaminasViewRenderer that why I used aliases. And then I will get this instances by their aliases.

hope I explained well.

Please explain. Am I doing somethink wrong or maybe it is a wrong behaviour of the dependency params resolver?
Great thanks.

It seems that your Config is not present when the AoT compiler is running. With your config the expected factory should look like this:

final class AdminTemplateRendererInterfaceFactory implements FactoryInterface
{
    public function create(ContainerInterface $container, array $options = [])
    {
        $args = empty($options)
            ? [
                $container->get(\Laminas\View\RendererInterface::class), // renderer
                'admin::layout',
                '*',
            ]
            : [
                array_key_exists('renderer', $options) ? $options['renderer'] : $container->get(\Laminas\View\RendererInterface::class),
                array_key_exists('layout', $options) ? $options['layout'] : 'admin::layout',
                array_key_exists('defaultSuffix', $options) ? $options['defaultSuffix'] : '*',
            ];

        return new \Mezzio\LaminasView\LaminasViewRenderer(...$args);
    }

The "*" for defaultSuffix will not work as you may expect it here. As documented here: https://docs.laminas.dev/laminas-di/config/#parameters then special "*" will only work for parameters that have a class or interface typehint, but $defaultSuffix is string.

Also your $renderer is allowed to be null. That means the resolver will resolve to null when the resolved parameter is not available via the psr container (i.e. $container->has(RendererInterface::class) === false). Does your PSR container provide a RendererInterface::class service?

You should also type hint the $layout parameter as string or use ValueInjection() in the di config to avoid accidential container lookups.

@Andriy_Lozinsky is it possible to share the piece of you project for further investigation? Oh and thanks for the detailed information, of course.

@tux-rampage yes, please:


This is a small pet project which I started in order to experiment with Laminas-Di.
Please let me know if you will get some results. I am very interesting these “Laminas-Di” stuffs

@tux-rampage I continue to investigate this problem and found out that if I used interface as alias it’s not working as I expected. The thing is that when “generate” method is called then first one it generate factories for generic classes, the second one it generate fectories for “configured types” classes. And if we use some interface for type alias after the first generating the factory for this interface factories has already been generated, and exists in factories cache. In second generating in which typed parameters must be filled from “typed config” this factory just returned from cache without generating process and filled config params.


I solved this problem replaced Interface to constant of this interface:

interface AdminTemplateRendererInterface extends \Mezzio\Template\TemplateRendererInterface
{
    public const DI_ALIAS = 'Easy\Admin\Admin\TemplateRenderer\AdminTemplateRenderer';
}
public function getDependencies(): array
    {
        return [
            'auto' => [
                'types' => [
                    TemplateRenderer\AdminTemplateRendererInterface::DI_ALIAS => [
                        'typeOf' => \Mezzio\LaminasView\LaminasViewRenderer::class,
                        'preferences' => [
                            \Laminas\View\Renderer\RendererInterface::class => \Laminas\View\Renderer\PhpRenderer::class,
                        ],
                        'parameters' => [
                            'layout' => 'admin::layout',
                        ]
                    ]
                ]
            ]
        ];
    }

but that didn’t solve my problem definitelly. So I kept investigating. I noticed that if I change $className param to $class when calling resolveParameters method then all works well and I got factorie which I expected.

so my current config looks like:

return [
            'auto' => [
                'types' => [
                    TemplateRenderer\AdminTemplateRendererInterface::DI_ALIAS => [
                        'typeOf' => \Mezzio\LaminasView\LaminasViewRenderer::class,
                        'preferences' => [
                            \Laminas\View\Renderer\RendererInterface::class => \Laminas\View\Renderer\PhpRenderer::class,
                        ],
                        'parameters' => [
                            'layout' => 'admin::layout',
                        ]
                    ]
                ]
            ]
        ];

and the result factory is:

final class AdminTemplateRendererFactory implements FactoryInterface
{
    public function create(ContainerInterface $container, array $options = [])
    {
        $args = empty($options)
            ? [
                $container->get('Laminas\\View\\Renderer\\PhpRenderer'), // renderer
                'admin::layout', // layout
                null, // defaultSuffix
            ]
            : [
                array_key_exists('renderer', $options) ? $options['renderer'] : $container->get('Laminas\\View\\Renderer\\PhpRenderer'),
                array_key_exists('layout', $options) ? $options['layout'] : 'admin::layout',
                array_key_exists('defaultSuffix', $options) ? $options['defaultSuffix'] : null,
            ];

        return new \Mezzio\LaminasView\LaminasViewRenderer(...$args);
    }