aimfeld/zend-di-compiler

This package is abandoned and no longer maintained. No replacement package was suggested.

A Zend Framework 2 module that uses auto-generated factory code for dependency-injection.

3.1 2020-01-10 11:03 UTC

README

NB: This repo is no longer actively maintained. Since laminas-di has changed a lot as of version 3 and now has better support for ahead-of-time (aot) factory code generation, I suggest using laminas-di directly.

Table of Contents

Introduction

Are you tired of writing tons of factory code (closures) for the Laminas\ServiceManager in your Zend Framework 2 application? Are outdated factory methods causing bugs? This can all be avoided by using ZendDiCompiler!

ZendDiCompiler is a Zend Framework 2 module that uses auto-generated factory code for dependency-injection. It saves you a lot of work, since there's no need anymore for writing Laminas\ServiceManager factory closures and keeping them up-to-date manually.

ZendDiCompiler scans your code using Laminas\Di and creates factory methods automatically. If the factory methods are outdated, ZendDiCompiler updates them in the background. Therefore, you develop faster, avoid bugs due to outdated factory methods, and experience great performance in production!

Features

  • Code scanning for creating DI definitions and automatic factory code generation.
  • Can deal with shared instances and type preferences.
  • Allows for custom code introspection strategies (by default, only constructors are scanned).
  • Can be used as a complement to Laminas\ServiceManager.
  • Detection of outdated generated factory code and automatic rescanning (great for development).
  • Can create new instances or reuse instances created before.
  • Can be used as a factory for runtime objects combining DI and passing of runtime parameters.
  • Greater perfomance and less memory consumption, as compared to using Laminas\Di\Di with cached definitions.

Caveats

  • Setter injection and interface injection are not supported yet. Instances must be injected via constructor injection (which I recommend over the two other methods anyway).
  • Using ZendDiCompiler makes sense if you develop a large application or a framework. For smaller applications, ZendDiCompiler may be overkill and you should handle instantiation using Laminas\ServiceManager callback methods.

Installation

This module is available on Packagist. In your project's composer.json use:

{
    "require": {
        "aimfeld/zend-di-compiler": "1.*"
    }
}

For PHP 7, install version 2.x, for PHP 5.4-5.6, use version 1.x.

Make sure you have a writable data folder in your application root directory, see ZendSkeletonApplication. Put a .gitignore file in it with the following content:

*
!.gitignore

Add 'ZendDiCompiler' to the modules array in your application.config.php. ZendDiCompiler must be loaded after the modules where it is used:

'modules' => array(
    'SomeModule',
    'Application',
    'ZendDiCompiler',
),

Usage

Dependency injection container vs. service locator

Is ZendDiCompiler a dependency injection container (DiC) or a service locator (SL)? Well, that depends on where you use it. ZendDiCompiler can be used as a DiC to create your controllers in your module class and inject the controller dependencies from outside. Pure DiC usage implies that ZendDiCompiler is used only during the bootstrap process and disposed before the controller is dispatched. This has been coined the "Register Resolve Release pattern" and is the recommended way by experts like Mark Seemann and others.

As soon as you inject the ZendDiCompiler itself into your controllers and other classes, you are using it as a service locator. The ZF2 MVC architecture is based on controller classes with action methods. Given this architecture, controller dependencies become numerous very quickly. In order to avoid bloated controller constructors, it makes sense to inject ZendDiCompiler as a single dependency into ZF2 controller classes and use it to pull the other dependencies from inside the controllers. This means using it as a service locator, just like Laminas\ServiceManager is typically used.

ZendDiCompiler is also used as a service locator inside of the provided ZendDiCompiler\DiFactory which is very useful for creating runtime objects with dependencies. This avoids a lot of abstract factory code you would otherwise have to write. Besides ZF2 controllers, I recommend not to inject ZendDiCompiler directly anywhere. If you need a service in one of your classes, just ask for it in the constructor. If you need to create runtime objects with dependencies, inject DiFactory or your extended version of it with custom creation methods.

Configuration

ZendDiCompiler uses standard Laminas\Di configuration (which is not well documented yet). To make things easier, see example.config.php for examples of how to specify:

  • Directories for the code scanner
  • Instance configuration
  • Type preferences

For a full list of configuration options, see module.config.php

ZendDiCompiler creates a GeneratedServiceLocator class in the data directory and automatically refreshes it when constructors change during development. However, if you e.g. change parameters in the instance configuration, you have to manually delete data/GeneratedServiceLocator.php to force a refresh. In your staging and production deployment/update process, make sure that data/GeneratedServiceLocator.php is deleted!

Shared instances

You need to provide shared instances to ZendDiCompiler::addSharedInstances() in your application module's onBootstrap() method in the following cases (also see example below):

  • The object to be injected is an instance of a class outside of the scanned directories.
  • The object to be injected requires some special bootstrapping (e.g. a session object).

Note that ZendDiCompiler provides some default shared instances automatically (see ZendDiCompiler::getDefaultSharedInstances()). The following default shared instances can be constructor-injected without explicitly adding them:

  • ZendDiCompiler\ZendDiCompiler
  • ZendDiCompiler\DiFactory
  • Laminas\Mvc\MvcEvent
  • Laminas\Config\Config
  • Laminas\View\Renderer\PhpRenderer
  • Laminas\Mvc\ApplicationInterface
  • Laminas\ServiceManager\ServiceLocatorInterface
  • Laminas\EventManager\EventManagerInterface
  • Laminas\Mvc\Router\RouteStackInterface

Type preferences

It is common to inject interfaces or abstract classes. Let's have a look at interface injection (for abstract classes, it works the same).

class ServiceF
{
    public function __construct(ExampleInterface $example)
    {
        // ExampleImplementor is injected since it is a type preference for ExampleInterface
        $this->example = $example;
    }
}

We need to tell ZendDiCompiler which implementing class to inject for ExampleInterface. We specify ExampleImplementor as a type preference for ExampleInterface in our example config:

'di' => array(
    'instance' => array(
        'preference' => array(
            'ZendDiCompiler\Example\ExampleInterface' => 'ZendDiCompiler\Example\ExampleImplementor',
        ),
    ),
),

ZendDiCompiler will now always inject ExampleImplementor for ExampleInterface. Calling ZendDiCompiler::get('ZendDiCompiler\Example\ExampleInterface') will return the ExampleImplementor.

Type preferences can not only be used for interfaces and abstract classes, but for substituting classes in general. They can even be used to deal with non-existing classes:

'di' => array(
    'instance' => array(
        'preference' => array(
            'ZendDiCompiler\Example\NotExists' => 'ZendDiCompiler\Example\Exists',
        ),
    ),
),

Calling ZendDiCompiler::get('ZendDiCompiler\Example\NotExists') will return a ZendDiCompiler\Example\Exists instance. Believe it or not, there are actually some good use cases for this.

Examples

All examples sources listed here are included as source code.

Using ZendDiCompiler to create a controller

Let's say we want to use the ZendDiCompiler to create a controller class and inject some dependencies. For illustriation, we also inject the ZendDiCompiler itself into the controller. As mentioned above, it is a moot topic whether this is a good idea or not. But if we decide to use the ZendDiCompiler inside the controller to get other dependencies, we can either inject it in the constructor or pull it from the ZF2 service locator using $this->serviceLocator->get('ZendDiCompiler').

In our example, we have the following classes:

ExampleController

use Laminas\Mvc\Controller\AbstractActionController;
use ZendDiCompiler\ZendDiCompiler;
use Laminas\Config\Config;

class ExampleController extends AbstractActionController
{
    public function __construct(ZendDiCompiler $zendDiCompiler, ServiceA $serviceA, ServiceC $serviceC, Config $config)
    {
        $this->zendDiCompiler = $zendDiCompiler;
        $this->serviceA = $serviceA;
        $this->serviceC = $serviceC;
        $this->config = $config;
    }
}

ServiceA with a dependency on ServiceB

class ServiceA
{
    public function __construct(ServiceB $serviceB)
    {
        $this->serviceB = $serviceB;
    }
}

ServiceB with a constructor parameter of unspecified type:

class ServiceB
{
    public function __construct($diParam)
    {
        $this->diParam = $diParam;
    }
}

ServiceC which requires complicated runtime initialization and will be added as shared instance.

class ServiceC
{
    public function init(MvcEvent $mvcEvent)
    {
        $sm = $mvcEvent->getApplication()->getServiceManager();
        $router = $mvcEvent->getRouter();
        // Some complicated bootstrapping using e.g. the service manager and the router
    }
}

We add the example source directory as a scan directory for ZendDiCompiler. Since ServiceB has a parameter of unspecified type, we have to specify a value to inject. A better approach for ServiceB would be to require the Config in its constructor and retrieve the parameter from there, so we wouldn't need to specify an instance configuration. The configuration for our example looks like this:

// ZendDiCompiler configuration
'zendDiCompiler' => array(
    // Directories that will be code-scanned
    'scanDirectories' => array(
        // e.g. 'vendor/provider/module/src',
        __DIR__ . '/../src/ZendDiCompiler/Example',
    ),
),
// ZF2 DI definition and instance configuration used by ZendDiCompiler
'di' => array(
    // Instance configuration
    'instance' => array(
        // Type preferences for abstract classes and interfaces.
        'preference' => array(
            'ZendDiCompiler\Example\ExampleInterface' => 'ZendDiCompiler\Example\ExampleImplementor',
        ),
        // Add instance configuration if there are specific parameters to be used for instance creation.
        'ZendDiCompiler\Example\ServiceB' => array('parameters' => array(
            'diParam' => 'Hello',
        )),
    ),
),

Now we can create the ExampleController in our application's module class. For convenience, we retrieve the ZendDiCompiler from the service manager and assign it to a local variable ($this->zendDiCompiler = $sm->get('ZendDiCompiler')). This makes it easier for writing getControllerConfig() or getViewHelperConfig() callbacks.

Since the ServiceC dependency requires some complicated initialization, we need to initialize it and add it as a shared instance to ZendDiCompiler.

class Module
{
    protected $zendDiCompiler;

    public function getControllerConfig()
    {
        return array(
            'factories' => array(
                // Suppose one of our routes specifies a controller named 'ExampleController'
                'ExampleController' => function() {
                    return $this->zendDiCompiler->get('ZendDiCompiler\Example\ExampleController');
                },
            ),
        );
    }

    public function onBootstrap(MvcEvent $mvcEvent)
    {
        $sm = $mvcEvent->getApplication()->getServiceManager();

        // Provide ZendDiCompiler as a local variable for convience
        $this->zendDiCompiler = $sm->get('ZendDiCompiler');

        // Set up shared instance
        $serviceC = new ServiceC;
        $serviceC->init($mvcEvent);

        // Provide shared instance
        $this->zendDiCompiler->addSharedInstances(array(
            'ZendDiCompiler\Example\ServiceC' => $serviceC,
        ));
    }
}

Using the DiFactory to create runtime objects with dependencies

It is useful to distinguish two types of objects: services and runtime objects. For services, all parameters should be specified in the configuration (e.g. a config array wrapped in a Laminas\Config\Config object). If class constructors e.g. in third party code require some custom parameters, they can be specified in the instance configuration).

Runtime objects, on the other hand, require at least one parameter which is determined at runtime only. ZendDiCompiler provides ZendDiCompiler\DiFactory to help you create runtime objects and inject their dependencies.

Passing all runtime parameters in a single array

If you follow the convention of passing runtime parameters in a single array named $zdcParams as in RuntimeA, things are very easy (the array name(s) can be configured in module.config.php):

class RuntimeA
{
    public function __construct(Config $config, ServiceA $serviceA, array $zdcParams = array())
    {
        $this->config = $config;
        $this->serviceA = $serviceA;
        $this->params = $zdcParams;
    }
}

ZendDiCompiler automatically injects ZendDiCompiler\DiFactory as a default shared instance. So we can just use it to create RuntimeA objects in ServiceD. RuntimeA's dependencies (the Config default shared instance and ServiceA) are injected automatically, so you only need to provide the runtime parameters:

use ZendDiCompiler\DiFactory;

class ServiceD
{
    public function __construct(DiFactory $diFactory)
    {
        $this->diFactory = $diFactory;
    }

    public function serviceMethod()
    {
        $runtimeA1 = $this->diFactory->create('ZendDiCompiler\Example\RuntimeA', array('hello', 'world'));
        $runtimeA2 = $this->diFactory->create('ZendDiCompiler\Example\RuntimeA', array('goodbye', 'world'));
    }
}

Passing custom runtime parameters

If you can't or don't want to follow the convention of passing all runtime parameters in a single $zdcParams array, ZendDiCompiler still is very useful. In that case, you can just extend a custom factory from ZendDiCompiler\DiFactory and add your specific creation methods. RuntimeB requires two separate run time parameters:

class RuntimeB
{
    public function __construct(Config $config, ServiceA $serviceA, $runtimeParam1, $runtimeParam2)
    {
        $this->config = $config;
        $this->serviceA = $serviceA;
        $this->runtimeParam1 = $runtimeParam1;
        $this->runtimeParam2 = $runtimeParam2;
    }
}

So we extend ExampleDiFactory from ZendDiCompiler\DiFactory and write a creation method createRuntimeB:

class ExampleDiFactory extends DiFactory
{
    /**
     * @param string $runtimeParam1
     * @param int $runtimeParam2
     * @return RuntimeB
     */
    public function createRuntimeB($runtimeParam1, $runtimeParam2)
    {
        $config = $this->zendDiCompiler->get('Laminas\Config\Config');
        $serviceA = $this->zendDiCompiler->get('ZendDiCompiler\Example\ServiceA');
        return new RuntimeB($config, $serviceA, $runtimeParam1, $runtimeParam2);
    }
}

In ServiceE, we inject our extended factory. If the extended factory is located in a directory scanned by ZendDiCompiler, we don't need to provide it as a shared instance. Now we can create RuntimeB objects as follows:

class ServiceE
{
    public function __construct(ExampleDiFactory $diFactory)
    {
        $this->diFactory = $diFactory;
    }

    public function serviceMethod()
    {
        $runtimeB1 = $this->diFactory->createRuntimeB('one', 1);
        $runtimeB2 = $this->diFactory->createRuntimeB('two', 2);
    }
}

Generated code and info

Factory code

ZendDiCompiler will automatically generate a service locator in the data/ZendDiCompiler directory and update it if constructors are changed during development. Services can be created/retrieved using ZendDiCompiler::get(). If you need a new dependency in one of your classes, you can just put it in the constructor and ZendDiCompiler will inject it for you.

Just for illustration, this is the generated service locator created by ZendDiCompiler and used in ZendDiCompiler::get().

namespace ZendDiCompiler;

use Laminas\Di\ServiceLocator;

/**
 * Generated by ZendDiCompiler\Generator (2013-03-07 21:11:39)
 */
class GeneratedServiceLocator extends ServiceLocator
{
    /**
     * @param string $name
     * @param array $params
     * @param bool $newInstance
     * @return mixed
     */
    public function get($name, array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services[$name])) {
            return $this->services[$name];
        }

        switch ($name) {
            case 'ZendDiCompiler\Example\ExampleController':
                return $this->getZendDiCompilerExampleExampleController($params, $newInstance);

            case 'ZendDiCompiler\Example\ExampleDiFactory':
                return $this->getZendDiCompilerExampleExampleDiFactory($params, $newInstance);

            case 'ZendDiCompiler\Example\ExampleImplementor':
                return $this->getZendDiCompilerExampleExampleImplementor($params, $newInstance);

            case 'ZendDiCompiler\Example\Module':
                return $this->getZendDiCompilerExampleModule($params, $newInstance);

            case 'ZendDiCompiler\Example\RuntimeA':
                return $this->getZendDiCompilerExampleRuntimeA($params, $newInstance);

            case 'ZendDiCompiler\Example\ServiceA':
                return $this->getZendDiCompilerExampleServiceA($params, $newInstance);

            case 'ZendDiCompiler\Example\ServiceB':
                return $this->getZendDiCompilerExampleServiceB($params, $newInstance);

            case 'ZendDiCompiler\Example\ServiceC':
                return $this->getZendDiCompilerExampleServiceC($params, $newInstance);

            case 'ZendDiCompiler\Example\ServiceD':
                return $this->getZendDiCompilerExampleServiceD($params, $newInstance);

            case 'ZendDiCompiler\Example\ServiceE':
                return $this->getZendDiCompilerExampleServiceE($params, $newInstance);

            case 'ZendDiCompiler\Example\ServiceF':
                return $this->getZendDiCompilerExampleServiceF($params, $newInstance);

            default:
                return parent::get($name, $params);
        }
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ExampleController
     */
    public function getZendDiCompilerExampleExampleController(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ExampleController'])) {
            return $this->services['ZendDiCompiler\Example\ExampleController'];
        }

        $object = new \ZendDiCompiler\Example\ExampleController($this->get('ZendDiCompiler\ZendDiCompiler'), $this->getZendDiCompilerExampleServiceA(), $this->getZendDiCompilerExampleServiceC(), $this->get('Laminas\Config\Config'));
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ExampleController'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ExampleDiFactory
     */
    public function getZendDiCompilerExampleExampleDiFactory(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ExampleDiFactory'])) {
            return $this->services['ZendDiCompiler\Example\ExampleDiFactory'];
        }

        $object = new \ZendDiCompiler\Example\ExampleDiFactory($this->get('ZendDiCompiler\ZendDiCompiler'));
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ExampleDiFactory'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ExampleImplementor
     */
    public function getZendDiCompilerExampleExampleImplementor(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ExampleImplementor'])) {
            return $this->services['ZendDiCompiler\Example\ExampleImplementor'];
        }

        $object = new \ZendDiCompiler\Example\ExampleImplementor();
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ExampleImplementor'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\Module
     */
    public function getZendDiCompilerExampleModule(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\Module'])) {
            return $this->services['ZendDiCompiler\Example\Module'];
        }

        $object = new \ZendDiCompiler\Example\Module();
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\Module'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\RuntimeA
     */
    public function getZendDiCompilerExampleRuntimeA(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\RuntimeA'])) {
            return $this->services['ZendDiCompiler\Example\RuntimeA'];
        }

        $object = new \ZendDiCompiler\Example\RuntimeA($this->get('Laminas\Config\Config'), $this->getZendDiCompilerExampleServiceA(), $params);
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\RuntimeA'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ServiceA
     */
    public function getZendDiCompilerExampleServiceA(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ServiceA'])) {
            return $this->services['ZendDiCompiler\Example\ServiceA'];
        }

        $object = new \ZendDiCompiler\Example\ServiceA($this->getZendDiCompilerExampleServiceB());
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ServiceA'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ServiceB
     */
    public function getZendDiCompilerExampleServiceB(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ServiceB'])) {
            return $this->services['ZendDiCompiler\Example\ServiceB'];
        }

        $object = new \ZendDiCompiler\Example\ServiceB('Hello');
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ServiceB'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ServiceC
     */
    public function getZendDiCompilerExampleServiceC(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ServiceC'])) {
            return $this->services['ZendDiCompiler\Example\ServiceC'];
        }

        $object = new \ZendDiCompiler\Example\ServiceC();
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ServiceC'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ServiceD
     */
    public function getZendDiCompilerExampleServiceD(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ServiceD'])) {
            return $this->services['ZendDiCompiler\Example\ServiceD'];
        }

        $object = new \ZendDiCompiler\Example\ServiceD($this->get('ZendDiCompiler\DiFactory'));
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ServiceD'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ServiceE
     */
    public function getZendDiCompilerExampleServiceE(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ServiceE'])) {
            return $this->services['ZendDiCompiler\Example\ServiceE'];
        }

        $object = new \ZendDiCompiler\Example\ServiceE($this->getZendDiCompilerExampleExampleDiFactory());
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ServiceE'] = $object;
        }

        return $object;
    }

    /**
     * @param array $params
     * @param bool $newInstance
     * @return \ZendDiCompiler\Example\ServiceF
     */
    public function getZendDiCompilerExampleServiceF(array $params = array(), $newInstance = false)
    {
        if (!$newInstance && isset($this->services['ZendDiCompiler\Example\ServiceF'])) {
            return $this->services['ZendDiCompiler\Example\ServiceF'];
        }

        $object = new \ZendDiCompiler\Example\ServiceF($this->getZendDiCompilerExampleExampleImplementor());
        if (!$newInstance) {
            $this->services['ZendDiCompiler\Example\ServiceF'] = $object;
        }

        return $object;
    }
}

Code scan log

ZendDiCompiler logs problems found during code scanning in data/ZendDiCompiler/code-scan.log. If you can't retrieve an object from ZendDiCompiler, you will probably find the reason in this log. The most common problem is that you have untyped scalar parameters instead of a parameter array in your constructors without providing values in your Laminas\Di configuration. Here's an example of the code scan log showing some problems:

INFO (6): Start generating service locator by code scanning.
DEBUG (7): Survey\Cache\Zf1CacheAdapter: Class Laminas\Cache\Storage\StorageInterface could not be located in provided definitions.
DEBUG (7): Survey\DataAggregator\Aggregate: Missing instance/object for parameter data for Survey\DataAggregator\Aggregate::__construct
DEBUG (7): Survey\Db\Table\Rowset: Missing instance/object for parameter config for Survey\Db\Table\Rowset::__construct
DEBUG (7): Survey\DbValidate\ValidationResult: Missing instance/object for parameter errorCount for Survey\DbValidate\ValidationResult::__construct
DEBUG (7): Survey\Form\MessageContainer: Missing instance/object for parameter intro for Survey\Form\MessageContainer::__construct
DEBUG (7): Survey\Input\ValidationResult: Missing instance/object for parameter level for Survey\Input\ValidationResult::__construct
DEBUG (7): Survey\Mail\Mailer\MailerResult: Missing instance/object for parameter success for Survey\Mail\Mailer\MailerResult::__construct
DEBUG (7): Survey\Paginator\Adapter\DbSelect: Class Zend_Db_Select could not be located in provided definitions.
DEBUG (7): Survey\Pdf\Prince: Missing instance/object for parameter exePath for Survey\Pdf\Prince::__construct
DEBUG (7): Survey\SkipLogic\ConditionResult: Missing instance/object for parameter isTrue for Survey\SkipLogic\ConditionResult::__construct
DEBUG (7): Survey\System\SystemInfo: Missing instance/object for parameter lastEventFlowUpdate for Survey\System\SystemInfo::__construct
DEBUG (7): Survey\TokenLinker\ActionResult: Missing instance/object for parameter action for Survey\TokenLinker\ActionResult::__construct
DEBUG (7): Survey\UserSurveyManager\UpdateStatusesResult: Missing instance/object for parameter updatesCount for Survey\UserSurveyManager\UpdateStatusesResult::__construct
INFO (6): Code scanning finished.
INFO (6): Writing generated service locator to ./data/ZendDiCompiler/GeneratedServiceLocator.php.
INFO (6): Finished writing generated service locator to ./data/ZendDiCompiler/GeneratedServiceLocator.php.

In case of simple value objects without any service dependencies, I do not use dependency injection but create then with new, e.g. ConditionResult::__construct($isTrue, $isCacheable, $allowFlip = true). These objects are not meant to be created with the DiFactory and therefore, the DEBUG notice can be ignored.

Component dependency info

As a bonus, ZendDiCompiler will write a component-dependency-info.txt file containing information about which of the scanned components depend on which classes.

Scanned classes are grouped into components (e.g. the Laminas\Mvc\MvcEvent class belongs to the Laminas\Mvc component). For every component, all constructor-injected classes are listed. This helps you analyze which components depend on which classes of other components. Consider organizing your components into layers. Each layer should depend on classes of the same or lower layers only. Note that only constructor-injection is considered for this analysis, so the picture might be incomplete.

Here's an example of what you might see:

...
MyLibrary\Mail classes inject:
- MyLibrary\Mail\Transport
- MyLibrary\TextEngine\TextEngine
- Laminas\Config\Config

MyLibrary\Validator classes inject:
- MyLibrary\Db\Tables
- MyLibrary\I18n\Translator\Translator
...