Object Framework

The lifecycle of objects is managed centrally by the object framework. It offers convenient support for Dependency Injection and provides some additional features such as a caching mechanism for objects. Because all packages are built on this foundation it is important to understand the general concept of objects in Flow. Note that the object management features of Flow are by default only enabled for classes in packages belonging to one of the neos-*` package types. All other classes are not considered by default. If you need that (see Enabling Other Package Classes For Object Management).

Tip

A very good start to understand the idea of Inversion of Control and Dependency Injection is reading Martin Fowler’s article on the topic.

Creating Objects

In simple, self-contained applications, creating objects is as simple as using the new operator. However, as the program gets more complex, a developer is confronted with solving dependencies to other objects, make classes configurable (maybe through a factory method) and finally assure a certain scope for the object (such as Singleton or Prototype). Howard Lewis Ship explained this circumstances nicely in his blog (quite some time ago):

Once you start thinking in terms of large numbers of objects, and a whole lot of just in time object creation and configuration, the question of how to create a new object doesn’t change (that’s what new is for) … but the questions when and who become difficult to tackle. Especially when the when is very dynamic, due to just-in-time instantiation, and the who is unknown, because there are so many places a particular object may be used.

The Object Manager is responsible for object building and dependency resolution (we’ll discover shortly why dependency injection makes such a difference to your application design). In order to fulfill its task, it is important that all objects are instantiated only through the object framework.

Important

As a general rule of thumb for those not developing the Flow core itself but your very own packages:

Use Dependency Injection whenever possible for retrieving singletons.

Object Scopes

Objects live in a specific scope. The most commonly used are prototype and singleton:

Scope

Description

singleton

The object instance is unique during one request - each injection by the Object Manager or explicit call of get() returns the same instance. A request can be an HTTP request or a run initiated from the command line.

prototype (default)

The object instance is not unique - each injection or call of the Object Factory’s create method returns a fresh instance.

session

The object instance is unique during the whole user session - each injection or get() call returns the same instance.

Background: Objects in PHP

In PHP, objects of the scope prototype are created with the new operator:

$myFreshObject = new \MyCompany\MyPackage\MyClassName();

In contrast to Prototype, the Singleton design pattern ensures that only one instance of a class exists at a time. In PHP the Singleton pattern is often implemented by providing a static function (usually called getInstance), which returns a unique instance of the class:

/**
 * Implementation of the Singleton pattern
 */
class ASingletonClass {

        protected static $instance;

        static public function getInstance() {
                if (!is_object(self::$instance)) {
                        self::$instance = $this;
                }
                return self::$instance;
        }
}

Although this way of implementing the singleton will possibly not conflict with the Object Manager, it is counterproductive to the integrity of the system and might raise problems with unit testing (sometimes Singleton is referred to as an Anti Pattern). The above examples are not recommended for the use within Flow applications.

The scope of an object is determined from its configuration (see also Configuring objects). The recommended way to specify the scope is the @scope attribute or annotation:

      namespace MyCompany\MyPackage;

use Neos\Flow\Annotations as Flow;

      /**
       * @Flow\Scope("singleton")
       */
      #[Flow\Scope("singleton")]
      class SomeClass {
      }

Note that only either the annotation or attribute is needed, not both. Prototype is the default scope and is therefore assumed if no Scope attribute or other configuration was found.

Creating Prototypes

To create prototype objects, just use the new operator as you are used to:

$myFreshObject = new \MyCompany\MyPackage\MyClassName();

When you do this, some magic is going on behind the scenes which still makes sure the object you get back is managed by the object framework. Thus, all dependencies are properly injected into the object, lifecycle callbacks are fired, and you can use Aspect-Oriented Programming, etc.

Behind the scenes of the Object Framework

In order to provide the functionality that you can just use new to create new prototype objects, a lot of advanced things happen behind the scenes.

Flow internally copies all classes to another file, and appends _Original to their class name. Then, it creates a new class under the original name where all the magic is happening.

However, you as a user do not have to deal with that. The only thing you need to remember is using new for creating new Prototype objects. And you might know this from PHP ;-)

Retrieving Singletons

The Object Manager maintains a registry of all instantiated singletons and ensures that only one instance of each class exists. The preferred way to retrieve a singleton object is dependency injection.

Example: Retrieving the Object Manager through dependency injection

namespace MyCompany\MyPackage;

use Neos\Flow\ObjectManagement\ObjectManagerInterface;

/**
 * A sample class
 */
class SampleClass {

        protected ObjectManagerInterface $objectManager;

        /**
         * The Object Manager will automatically be passed (injected) by the object
         * framework on instantiating this class.
         */
        public function __construct(ObjectManagerInterface $objectManager) {
                $this->objectManager = $objectManager;
        }
}

Once the SampleClass is being instantiated, the object framework will automagically pass a reference to the Object Manager (which is an object of scope singleton) as an argument to the constructor. This kind of dependency injection is called Constructor Injection and will be explained - together with other kinds of injection - in one of the later sections.

Although dependency injection is what you should strive for, it might happen that you need to retrieve object instances directly. The ObjectManager provides methods for retrieving object instances for these rare situations. First, you need an instance of the ObjectManager itself, again by taking advantage of constructor injection:

public function __construct(ObjectManagerInterface $objectManager) {
        $this->objectManager = $objectManager;
}

Note

In the text, we commonly refer to the ObjectManager. However, in your code, you should always use the ObjectManagerInterface if you need an instance of the Object Manager injected.

To explicitly retrieve an object instance use the get() method:

$myObjectInstance = $objectManager->get('MyCompany\MyPackage\MyClassName');

It is not possible to pass arguments to the constructor of the object, as the object might be already instantiated when you call get(). If the object needs constructor arguments, these must be configured in Objects.yaml.

Lifecycle methods

The lifecycle of an object goes through different stages. It boils down to the following order:

  1. Solve dependencies for constructor injection

  2. Create an instance of the object class, injecting the constructor dependencies

  3. Solve and inject dependencies for setter injection

  4. Live a happy object-life and solve exciting tasks

  5. Dispose the object instance

Your object might want to take some action after certain of the above steps. Whenever one of the following methods exists in the object class, it will be invoked after the related lifecycle step:

  1. No action after this step

  2. During instantiation the function __construct() is called (by PHP itself), dependencies are passed to the constructor arguments

  3. After all dependencies have been injected (through constructor- or setter injection) the object’s initializeObject() method is called. The name of this method is configurable inside Objects.yaml. initializeObject() is also called if no dependencies were injected.

  4. During the life of an object no special lifecycle methods are called

  5. Before destruction of the object, the function shutdownObject() is called. The name of this method is also configurable.

  6. On disposal, the function __destruct() is called (by PHP itself)

We strongly recommend that you use the shutdownObject method instead of PHP’s __destruct method for shutting down your object. If you used __destruct it might happen that important parts of the framework are already unavailable. Here’s a simple example with all kinds of lifecycle methods:

Example: Sample class with lifecycle methods

class Foo {

        protected $bar;
        protected $identifier = 'Untitled';

        public function __construct() {
                echo ('Constructing object ...');
        }

        public function injectBar(\MyCompany\MyPackage\BarInterface $bar) {
                $this->bar = $bar;
        }

        public function setIdentifier($identifier) {
                $this->identifier = $identifier;
        }

        public function initializeObject() {
                echo ('Initializing object ...');
        }

        public function shutdownObject() {
                echo ('Shutting down object ...')
        }

        public function __destruct() {
                echo ('Destructing object ...');
        }
}

Output:

Constructing object ...
Initializing object ...
Shutting down object ...
Destructing object ...

Object Registration and API

Object Framework API

The object framework provides a lean API for registering, configuring and retrieving instances of objects. Some of the methods provided are exclusively used within Flow package or in test cases and should possibly not be used elsewhere. By offering Dependency Injection, the object framework helps you to avoid creating rigid interdependencies between objects and allows for writing code which is hardly or even not at all aware of the framework it is working in. Calls to the Object Manager should therefore be the exception.

For a list of available methods please refer to the API documentation of the interface Neos\Flow\ObjectManagement\ObjectManagerInterface.

Object Names vs. Class Names

We first need to introduce some namings: A class name is the name of a PHP class, while an object name is an identifier which is used inside the object framework to identify a certain object.

By default, the object name is identical to the PHP class which contains the object’s code. A class called MyCompany\MyPackage\MyImplementation will be automatically available as an object with the exact same name. Every part of the system which asks for an object with a certain name will therefore - by default - get an instance of the class of that name.

It is possible to replace the original implementation of an object by another one. In that case the class name of the new implementation will naturally differ from the object name which stays the same at all times. In these cases it is important to be aware of the fine difference between an object name and a class name.

All PHP interfaces for which only one implementation class exist are also automatically registered as object names, with the implementation class being returned when asked for an instance of the interface.

Thus, you can also ask for interface implementations:

$objectTypeInstance = $objectManager->get('MyCompany\MyPackage\MyInterface');

Note

If zero or more than one class implements the interface, the Object Manager will throw an exception.

The advantage of programming against interfaces is the increased flexibility: By referring to interfaces rather than classes it is possible to write code depending on other classes without the need to be specific about the implementation. Which implementation will actually be used can be set at a later point in time by simple means of configuration.

With Flow version 6.2 it’s also possible to use “virtual object names” that don’t represent an interface or class name (see Virtual Objects).

Object Dependencies

The intention to base an application on a combination of packages and objects is to force a clean separation of domains which are realized by dedicated objects. The less each object knows about the internals of another object, the easier it is to modify or replace one of them, which in turn makes the whole system flexible. In a perfect world, each of the objects could be reused in a variety of contexts, for example independently from certain packages and maybe even outside the Flow framework.

Dependency Injection

An important prerequisite for reusable code is already met by encouraging encapsulation through object orientation. However, the objects are still aware of their environment as they need to actively collaborate with other objects and the framework itself: An authentication object will need a logger for logging intrusion attempts and the code of a shop system hopefully consists of more than just one class. Whenever an object refers to another directly, it adds more complexity and removes flexibility by opening new interdependencies. It is very difficult or even impossible to reuse such hardwired classes and testing them becomes a nightmare.

By introducing Dependency Injection, these interdependencies are minimized by inverting the control over resolving the dependencies: Instead of asking for the instance of an object actively, the depending object just gets one injected by the Object Manager. This methodology is also referred to as the “Hollywood Principle”: Don’t call us, we’ll call you. It helps in the development of code with loose coupling and high cohesion — or in short: It makes you a better programmer.

In the context of the previous example it means that the authentication object announces that it needs a logger which implements a certain PHP interface (for example the Psr\Log\LoggerInterface). The object itself has no control over what kind of logger (file-logger, sms-logger, …) it finally gets and it doesn’t have to care about it anyway as long as it matches the expected API. As soon as the authentication object is instantiated, the object manager will resolve these dependencies, prepare an instance of a logger and inject it to the authentication object.

Reading Tip

An article by Jonathan Amsterdam discusses the difference between creating an object and requesting one (i.e. using new versus using dependency injection). It demonstrates why new should be considered as a low-level tool and outlines issues with polymorphism. He doesn’t mention dependency injection though …

Dependencies on other objects can be declared in the object’s configuration (see Configuring objects) or they can be solved automatically (so called autowiring). Generally there are two modes of dependency injection supported by Flow: Constructor Injection and Setter Injection.

Note

Please note that Flow removes all injected properties before serializing an object. Then after deserializing injections happen again. That means that injected properties are fresh instances and do not keep any state from before the serialization. That holds true also for prototypes. If you want to keep a prototype instance with its state throughout a serialize/unserialize cycle you should not inject the Prototype but rather create it in constructor of the object.

Constructor Injection

With constructor injection, the dependencies are passed as constructor arguments to the depending object while it is instantiated. Here is an example of an object Foo which depends on an object Bar:

Example: A simple example for Constructor Injection

namespace MyCompany\MyPackage;

class Foo {

        protected $bar;

        public function __construct(\MyCompany\MyPackage\BarInterface $bar) {
                $this->bar = $bar;
        }

        public function doSomething() {
                $this->bar->doSomethingElse();
        }
}

So far there’s nothing special about this class, the type hint just makes sure that an instance of a class implementing the \MyCompany\MyPackage\BarInterface is passed to the constructor. However, this is already a quite flexible approach because the type of $bar can be determined from outside by just passing one or the another implementation to the constructor.

Now the Flow Object Manager does some magic: By a mechanism called Autowiring all dependencies which were declared in a constructor will be injected automagically if the constructor argument provides a type definition (i.e. \MyCompany\MyPackage\BarInterface in the above example). Autowiring is activated by default (but can be switched off), therefore all you have to do is to write your constructor method.

The object framework can also be configured manually to inject a certain object or object type. You’ll have to do that either if you want to switch off autowiring or want to specify a configuration which differs from would be done automatically.

Example: Objects.yaml file for Constructor Injection

MyCompany\MyPackage\Foo:
  arguments:
    1:
      object: 'MyCompany\MyPackage\Bar'

The three lines above define that an object instance of \MyCompany\MyPackage\Bar must be passed to the first argument of the constructor when an instance of the object MyCompany\MyPackage\Foo is created.

Setter Injection

With setter injection, the dependencies are passed by calling setter methods of the depending object right after it has been instantiated. Here is an example of the Foo class which depends on a Bar object - this time with setter injection:

Example: A simple example for Setter Injection

namespace MyCompany\MyPackage;

class Foo {

        protected $bar;

        public function setBar(\MyCompany\MyPackage\BarInterface $bar) {
                $this->bar = $bar;
        }

        public function doSomething() {
                $this->bar->doSomethingElse();
        }
}

Analog to the constructor injection example, a BarInterface compatible object is injected into the Foo object. In this case, however, the injection only takes place after the class has been instantiated and a possible constructor method has been called. The necessary configuration for the above example looks like this:

Example: Objects.yaml file for Setter Injection

MyCompany\MyPackage\Foo:
  properties:
    bar:
      object: 'MyCompany\MyPackage\BarInterface'

Unlike constructor injection, setter injection like in the above example does not offer the autowiring feature. All dependencies have to be declared explicitly in the object configuration.

To save you from writing large configuration files, Flow supports a second type of setter methods: By convention all methods whose name start with inject are considered as setters for setter injection. For those methods no further configuration is necessary, dependencies will be autowired (if autowiring is not disabled):

Example: The preferred way of Setter Injection, using an inject method

namespace MyCompany\MyPackage;

class Foo {

        protected $bar;

        public function injectBar(\MyCompany\MyPackage\BarInterface $bar) {
                $this->bar = $bar;
        }

        public function doSomething() {
                $this->bar->doSomethingElse();
        }
}

Note the new method name injectBar - for the above example no further configuration is required. Using inject* methods is the preferred way for setter injection in Flow.

Note

If both, a set* and an inject* method exist for the same property, the inject* method has precedence.

Constructor- or Setter Injection?

The natural question which arises at this point is Should I use constructor- or setter injection? There is no answer across-the-board — it mainly depends on the situation and your preferences. The authors of the Java-based Spring Framework for example prefer Setter Injection for its flexibility. The more puristic developers of PicoContainer strongly plead for using Constructor Injection for its cleaner approach. Reasons speaking in favor of constructor injections are:

  • Constructor Injection makes a stronger dependency contract

  • It enforces a determinate state of the depending object: using setter Injection, the injected object is only available after the constructor has been called

However, there might be situations in which constructor injection is not possible or even cumbersome:

  • If an object has many dependencies and maybe even many optional dependencies, setter injection is a better solution.

  • Subclasses are not always in control over the arguments passed to the constructor or might even be incapable of overriding the original constructor. Then setter injection is your only chance to get dependencies injected.

  • Setter injection can be helpful to avoid circular dependencies between objects.

  • Setters provide more flexibility to unit tests than a fixed set of constructor arguments

Property Injection

Setter injection is the academic, clean way to set dependencies from outside. However, writing these setters can become quite tiresome if all they do is setting the property. For these cases Flow provides support for Property Injection:

Example: Example for Property Injection

      namespace MyCompany\MyPackage;

use Neos\Flow\Annotations as Flow;

      class Foo {

              /**
               * An instance of a BarInterface compatible object.
               *
               * @var \MyCompany\MyPackage\BarInterface
               * @Flow\Inject
               */
              protected $bar;

              public function doSomething() {
                      $this->bar->doSomethingElse();
              }
      }

You could say that property injection is the same like setter injection — just without the setter. The Inject annotation tells the object framework that the property is supposed to be injected and the @var annotation specifies the type. Note that property injection even works (and should only be used) with protected properties. The Objects.yaml configuration for property injection is identical to the setter injection configuration.

Note

If a setter method exists for the same property, it has precedence.

Setting properties directly, without a setter method, surely is convenient - but is it clean enough? In general it is a bad idea to allow direct access to mutable properties because you never know if at some point you need to take some action while a property is set. And if thousands of users (or only five) use your API, it’s hard to change your design decision in favor of a setter method.

However, we don’t consider injection methods as part of the public API. As you’ve seen, Flow takes care of all the object dependencies and the only other code working with injection methods directly are unit tests. Therefore we consider it safe to say that you can still switch back from property injection to setter injection without problems if it turns out that you really need it.

Lazy Dependency Injection

Using Property Injection is, in its current implementation, the most performant way to inject a dependency. As an important additional benefit you also get Lazy Dependency Injection: instead of loading the class of the dependency, instantiating and intializing it, a proxy is injected instead. This object waits until it will be accessed the first time. Once you start using the dependency, the proxy will build or retrieve the real dependency, call the requested method and return the result. On all following method calls, the real object will be used.

By default all dependencies injected through Property Injection are lazy. Usually this process is fully transparent to the user, unless you start passing around dependencies to other objects:

Example: Passing a dependency around

      namespace MyCompany\MyPackage;

use Neos\Flow\Annotations as Flow;

      class Foo {

              /**
               * A dependency, injected lazily:
               *
               * @var \MyCompany\MyPackage\BarInterface
               * @Flow\Inject
               */
              protected $bar;

              ...

              public function doSomething() {
                      $this->baz->doSomethingElse($this->bar);
              }

      }

      class Baz {

              public function doSomethingElse(Bar $bar) {
                      ...
              }

      }

The above example will break: at the time you pass $this->bar to the doSomethingElse() method, it is not yet a Bar object but a DependencyProxy object. Because doSomethingElse() has a type hint requiring a Bar object, PHP will issue a fatal error.

There are two ways to solve this:

  • activating the dependency manually

  • turning off lazy dependency injection for this property

Example: Manually activating a dependency

      namespace MyCompany\MyPackage;

use Neos\Flow\Annotations as Flow;

      class Foo {

              /**
               * A dependency, injected lazily:
               *
               * @var \MyCompany\MyPackage\BarInterface
               * @Flow\Inject
               */
              protected $bar;

              ...

              public function doSomething() {
                      if ($this->bar instanceof \Neos\Flow\ObjectManagement\DependencyInjection\DependencyProxy) {
                              $this->bar->_activateDependency();
                      }
                      $this->baz->doSomethingElse($this->bar);
              }

      }

In the example above, $this->bar is activated before it is passed to the next method. It’s important to check if the object still is a proxy because otherwise calling _activateDependency() will fail.

Example: Turning off lazy dependency injection

      namespace MyCompany\MyPackage;

use Neos\Flow\Annotations as Flow;

      class Foo {

              /**
               * A dependency, injected eagerly
               *
               * @var \MyCompany\MyPackage\BarInterface
               * @Flow\Inject(lazy = false)
               */
              protected $bar;

              ...

              public function doSomething() {
                      $this->baz->doSomethingElse($this->bar);
              }

      }

In the second solution, lazy dependency injection is turned off. This way you can be sure that $this->bar always contains the object you expected, but you don’t benefit from the speed optimizations.

Settings Injection

No, this headline is not misspelled. Flow offers some convenient feature which allows for automagically injecting the settings of the current package without the need to configure the injection. If a class contains a method called injectSettings and autowiring is not disabled for that object, the Object Builder will retrieve the settings of the package the object belongs to and pass it to the injectSettings method.

Example: the magic injectSettings method

namespace MyCompany\MyPackage;

class Foo {

        protected $settings = array();

        public function injectSettings(array $settings) {
                $this->settings = $settings;
        }

        public function doSomething() {
                var_dump($this->settings);
        }
}

The doSomething method will output the settings of the MyPackage package.

In case you only need a specific setting, there’s an even more convenient way to inject a single setting value into a class property:

      namespace Acme\Demo;

use Neos\Flow\Annotations as Flow;

      class SomeClass {

              /**
               * @var string
               * @Flow\InjectConfiguration("administrator.name")
               */
              protected $name;

              /**
               * @var string
               * @Flow\InjectConfiguration(path="email", package="SomeOther.Package")
               */
              protected $emailAddress;

      }

The InjectConfiguration annotation also supports for injecting all settings of a package. And it can also be used to inject any other registered configuration type:

namespace Acme\Demo;

class SomeClass {

        /**
         * @var array
         * @Flow\InjectConfiguration(package="SomeOther.Package")
         */
        protected $allSettingsOfSomeOtherPackage;

        /**
         * @var array
         * @Flow\InjectConfiguration(type="Views")
         */
        protected $viewsConfiguration;

}

Required Dependencies

All dependencies defined in a constructor are, by its nature, required. If a dependency can’t be solved by autowiring or by configuration, Flow’s object builder will throw an exception.

Also autowired setter-injected dependencies are, by default, required. If the object builder can’t autowire an object for an injection method, it will throw an exception.

Dependency Resolution

The dependencies between objects are only resolved during the instantiation process. Whenever a new instance of an object class needs to be created, the object configuration is checked for possible dependencies. If there is any, the required objects are built and only if all dependencies could be resolved, the object class is finally instantiated and the dependency injection takes place.

During the resolution of dependencies it might happen that circular dependencies occur. If an object A requires an object B to be injected to its constructor and then again object B requires an object A likewise passed as a constructor argument, none of the two classes can be instantiated due to the mutual dependency. Although it is technically possible (albeit quite complex) to solve this type of reference, Flow’s policy is not to allow circular constructor dependencies at all. As a workaround you can use setter injection instead for either one or both of the objects causing the trouble.

Configuring objects

The behavior of objects significantly depends on their configuration. During the initialization process all classes found in the various Classes/ directories are registered as objects and an initial configuration is prepared. In a second step, other configuration sources are queried for additional configuration options. Definitions found at these sources are added to the base configuration in the following order:

  • If they exist, the <PackageName>/Configuration/Objects.yaml will be included.

  • Additional configuration defined in the global Configuration/Objects.yaml directory is applied.

  • Additional configuration defined in the global Configuration/<ApplicationScope>/Objects.yaml directory is applied.

Currently there are three important situations in which you want to configure objects:

  • Override one object implementation with another

  • Set the active implementation for an object type

  • Explicitly define and configure dependencies to other objects

Configuring Objects Through Objects.yaml

If a file named Objects.yaml exists in the Configuration directory of a package, it will be included during the configuration process. The YAML file should stick to Flow’s general rules for YAML-based configuration.

Example: Sample Objects.yaml file

#                                                                        #
# Object Configuration for the MyPackage package                         #
#                                                                        #

# @package MyPackage

MyCompany\MyPackage\Foo:
  arguments:
    1:
      object: 'MyCompany\MyPackage\Baz'
    2:
      value: "some string"
    3:
      value: false
  properties:
    bar:
      object: 'MyCompany\MyPackage\BarInterface'
    enableCache:
      setting: MyPackage.Cache.enable

Configuring Objects Through Annotations

A very convenient way to configure certain aspects of objects are annotations. You write down the configuration directly where it takes effect: in the class file. However, this way of configuring objects is not really flexible, as it is hard coded. That’s why only those options can be set through annotations which are part of the class design and won’t change afterwards. Currently scope, inject and autowiring are the only supported annotations.

It’s up to you defining the scope in the class directly or doing it in a Objects.yaml configuration file – both have the same effect. We recommend using annotations in this case, as the scope usually is a design decision which is very unlikely to be changed.

Example: Sample scope annotation

/**
 * This is my great class.
 *
 * @Flow\Scope("singleton")
 */
class SomeClass {

}

Example: Sample autowiring annotation for a class

/**
 * This turns off autowiring for the whole class:
 *
 * @Flow\Autowiring(false)
 */
class SomeClass {

}

Example: Sample autowiring annotation for a method

/**
 * This turns off autowiring for a single method:
 *
 * @param \Neos\Foo\Bar $bar
 * @Flow\Autowiring(false)
 */
public function injectMySpecialDependency(\Neos\Foo\Bar $bar) {

}

Overriding Object Implementations

One advantage of componentry is the ability to replace objects by others without any bad impact on those parts depending on them.

A prerequisite for replaceable objects is that their classes implement a common interface which defines the public API of the original object. Other objects which implement the same interface can then act as a true replacement for the original object without the need to change code anywhere in the system. If this requirement is met, the only necessary step to replace the original implementation with a substitute is to alter the object configuration and set the class name to the new implementation.

To illustrate this circumstance, consider the following classes.

Example: The Greeter object type

namespace MyCompany\MyPackage;

interface GreeterInterface {
        public function sayHelloTo($name);
}

class Greeter implements GreeterInterface {
        public function sayHelloTo($name) {
                echo 'Hello ' . $name;
        }
}

During initialization the above Greeter class will automatically be registered as the default implementation of MyCompany\MyPackage\GreeterInterface and is available to other objects. In the class code of another object you might find the following lines.

Example: Using the Greeter object type

// Use setter injection for fetching an instance
// of \MyCompany\MyPackage\GreeterInterface:
public function injectGreeter(\MyCompany\MyPackage\GreeterInterface $greeter) {
        $this->greeter = $greeter;
}

public function someAction() {
        $this->greeter->sayHelloTo('Heike');
}

If we want to use the much better object \Neos\OtherPackage\GreeterWithCompliments, the solution is to let the new implementation implement the same interface.

Example: The improved Greeter object type

namespace Neos\OtherPackage;

class GreeterWithCompliments implements \MyCompany\MyPackage\GreeterInterface {
        public function sayHelloTo($name) {
                echo('Hello ' . $name . '! You look so great!');
        }
}

Then we have to set which implementation of the MyCompany\MyPackage\GreeterInterface should be active and are done:

Example: Objects.yaml file for object type definition

MyCompany\MyPackage\GreeterInterface:
  className: 'Neos\OtherPackage\GreeterWithCompliments'

The the same code as above will get the improved GreeterWithCompliments instead of the simple Greeter now.

Configuring Injection

The object framework allows for injection of straight values, objects (i.e. dependencies) or settings either by passing them as constructor arguments during instantiation of the object class or by calling a setter method which sets the wished property accordingly. The necessary configuration for injecting objects is usually generated automatically by the autowiring capabilities of the Object Builder. Injection of straight values or settings, however, requires some explicit configuration.

Injection Values

Regardless of what injection type is used (constructor or setter injection), there are three kinds of value which can be injected:

  • value: static value of a simple type. Can be string, integer, boolean or array and is passed on as is.

  • object: object name which represents a dependency. Dependencies of the injected object are resolved and an instance of the object is passed along.

  • setting: setting defined in one of the Settings.yaml files. A path separated by dots specifies which setting to inject.

Constructor Injection

Arguments for constructor injection are defined through the arguments option. Each argument is identified by its position, counting starts with 1.

Example: Sample class for Constructor Injection

namespace MyCompany\MyPackage;

class Foo {

        protected $bar;
        protected $identifier;
        protected $enableCache;

        public function __construct(\MyCompany\MyPackage\BarInterface $bar, $identifier,
                    $enableCache) {
                $this->bar = $bar;
                $this->identifier = $identifier;
                $this->enableCache = $enableCache;
        }

        public function doSomething() {
                $this->bar->doSomethingElse();
        }
}

Example: Sample configuration for Constructor Injection

MyCompany\MyPackage\Foo:
  arguments:
    1:
      object: 'MyCompany\MyPackage\Bar'
    2:
      value: "some string"
    3:
      setting: "MyPackage.Cache.enable"

Note

It is usually not necessary to configure injection of objects explicitly. It is much more convenient to just declare the type of the constructor arguments (like MyCompany\MyPackage\BarInterface in the above example) and let the autowiring feature configure and resolve the dependencies for you.

Setter Injection

The following class and the related Objects.yaml file demonstrate the syntax for the definition of setter injection:

Example: Sample class for Setter Injection

namespace MyCompany\MyPackage;

class Foo {

        protected $bar;
        protected $identifier = 'Untitled';
        protected $enableCache = false;

        public function injectBar(\MyCompany\MyPackage\BarInterface $bar) {
                $this->bar = $bar;
        }

        public function setIdentifier($identifier) {
                $this->identifier = $identifier;
        }

        public function setEnableCache($enableCache) {
                $this->enableCache = $enableCache;
        }

        public function doSomething() {
                $this->bar->doSomethingElse();
        }
}

Example: Sample configuration for Setter Injection

MyCompany\MyPackage\Foo:
  properties:
    bar:
      object: 'MyCompany\MyPackage\Bar'
    identifier:
      value: 'some string'
    enableCache:
      setting: 'MyPackage.Cache.enable'

As you can see, it is important that a setter method with the same name as the property, preceded by inject or set exists. It doesn’t matter though, if you choose inject or set, except that inject has the advantage of being autowireable. As a rule of thumb we recommend using inject for required dependencies and values and set for optional properties.

Injection of Objects Specified in Settings

In some cases it might be convenient to specify the name of the object to be injected in the settings rather than in the objects configuration. This can be achieved by specifying the settings path instead of the object name:

Example: Injecting an object specified in the settings

MyCompany\MyPackage\Foo:
  properties:
    bar:
      object: 'MyCompany.MyPackage.fooStuff.barImplementation'

Example: Settings.yaml of MyPackage

MyCompany:
  MyPackage:
    fooStuff:
      barImplementation: 'MyCompany\MyPackage\Bars\ASpecialBar'

Nested Object Configuration

While autowiring and automatic dependency injection offers a great deal of convenience, it is sometimes necessary to have a fine grained control over which objects are injected with which third objects injected.

Consider a Flow cache object, a VariableCache for example: the cache itself depends on a cache backend which on its part requires a few settings passed to its constructor - this readily prepared cache should now be injected into another object. Sounds complex? With the objects configuration it is however possible to configure even that nested object structure:

Example: Nesting object configuration

MyCompany\MyPackage\Controller\StandardController:
  properties:
    cache:
      object:
        name: 'Neos\Cache\VariableCache'
        arguments:
          1:
            value: MyCache
          2:
            object:
              name: 'Neos\Cache\Backend\File'
              properties:
                cacheDirectory:
                  value: /tmp/

Disabling Autowiring

Injecting dependencies is a common task. Because Flow can detect the type of dependencies a constructor needs, it automatically configures the object to ensure that the necessary objects are injected. This automation is called autowiring and is enabled by default for every object. As long as autowiring is in effect, the Object Builder will try to autowire all constructor arguments and all methods named after the pattern inject*.

If, for some reason, autowiring is not wanted, it can be disabled by setting an option in the object configuration:

Example: Turning off autowiring support in Objects.yaml

MyCompany\MyPackage\MyObject:
  autowiring: false

Autowiring can also be switched off through the @Flow\Autowiring(false) annotation or the #[Flow\Autowiring(false)] attribute - either in the documentation block of a whole class or of a single method. For the latter the annotation or attribute only has an effect when used for a constructor or a method whose name starts with inject.

Finally, autowiring for constructors can also be disabled through a setting. The setting accepts an array of fully qualified class names, where each entry is a regular expression:

Neos:
  Flow:
    object:
      dependencyInjection:
        excludeClassesFromConstructorAutowiring:
          - '^Neos\\SomePackage\\Domain\\.*\\Command\\.*$'

Classes of scope prototype which expect objects to be passed to their constructor are usually considered for autowiring which results in a proxy class being generated. This option allows to exclude classes from this process. This is useful for classes like data transfer objects, read models, commands, events and value objects which usually don’t rely on dependency injection.

Note

Flow cannot reliably detect weather a prototype class depends on autowiring for constructor arguments or not. Use this option to optimize your application to avoid the small but measurable overhead of proxy generation for those kinds of classes. Note that if there are other reasons than constructor injection which require a proxy class to be generated, the proxy class will be generated no matter what.

Custom Factories

Complex objects might require a custom factory which takes care of all important settings and dependencies. As we have seen previously, a logger consists of a frontend, a backend and configuration options for that backend. Instead of creating and configuring these objects on your own, you should use the Neos\Flow\Log\PsrLoggerFactory which provides a convenient get method taking care of all the rest:

$myCache = $loggerFactory->get('systemLogger');

It is possible to specify for each object if it should be created by a custom factory rather than the Object Builder. Consider the following configuration:

Example: Sample configuration for a Custom Factory

Neos\Flow\Log\PsrSystemLoggerInterface:
  scope: singleton
  factoryObjectName: Neos\Flow\Log\PsrLoggerFactory
  factoryMethodName: get

From now on the LoggerFactory’s get method will be called each time an object of type PsrSystemLoggerInterface needs to be instantiated. If arguments were passed to the ObjectManagerInterface::get() method or defined in the configuration, they will be passed through to the custom factory method:

Example: YAML configuration for a Custom Factory with default arguments

Neos\Flow\Log\PsrSystemLoggerInterface:
  scope: singleton
  factoryObjectName: Neos\Flow\Log\PsrLoggerFactory
  factoryMethodName: get
  arguments:
    1:
      value: 'systemLogger'

Example: YAML configuration for a static custom factory method

Acme\Foo\Object:
  scope: prototype
  factoryMethodName: Acme\Foo\ObjectFactory::fromValue
  arguments:
    1:
      settings: 'Acme.Foo.Object.ConfigurableValue'

Note that if you only specify the factoryMethodName, it needs to be the fully qualified name.

Example: PHP code using the custom factory

$myCache = $objectManager->get(\Neos\Flow\Log\PsrSystemLoggerInterface::class);

$objectManager is a reference to the Neos\Flow\ObjectManagement\ObjectManager. The required arguments are automatically built from the values defined in the object configuration.

Name of Lifecycle Methods

The default name of a lifecycle methods is initializeObject and shutdownObject. If these methods exist, the initialization method will be called after the object has been instantiated or recreated and all dependencies are injected and the shutdown method is called before the Object Manager quits its service.

As the initialization method is being called after creating an object and after recreating/reconstituting an object, there are cases where different code should be executed. That is why the initialization method gets a parameter, which is one of the \Neos\Flow\ObjectManagement\ObjectManagerInterface::INITIALIZATIONCAUSE_* constants:

\Neos\Flow\ObjectManagement\ObjectManagerInterface::INITIALIZATIONCAUSE_CREATED

If the object is newly created (i.e. the constructor has been called)

\Neos\Flow\ObjectManagement\ObjectManagerInterface::INITIALIZATIONCAUSE_RECREATED

If the object has been recreated/reconstituted (i.e. the constructor has not been called)

The name of both methods is configurable per object for situations you don’t have control over the name of your initialization method (maybe, because you are integrating legacy code):

Example: Objects.yaml configuration of the initialization and shutdown method

MyCompany\MyPackage\MyObject:
  lifecycleInitializationMethod: myInitializeMethodName
  lifecycleShutdownMethod: myShutdownMethodName

Static Method Result Compilation

Some part of a Flow application may rely on data which is static during runtime, but which cannot or should not be hardcoded.

One example is the validation rules generated by the MVC framework for arguments of a controller action: the base information (PHP methods for the actions, type hints and arguments of these methods) is static. However, the validation rules should be determined automatically by the framework instead of being configured or hardcoded elsewhere. On the other hand, generating validation rules during runtime unnecessarily slows down the application. The solution is static method result compilation.

A method which generates data based on information already known at compile time can usually be made static. Consider the following example:

/**
 * Returns a map of action method names and their parameters.
 *
 * @return array Array of method parameters by action name
 */
public function getActionMethodParameters() {
        $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
        foreach ($methodParameters as $parameterName => $parameterInfo) {
          ...
        }
        return $methodParameters;
}

In the example above, getActionMethodParameters() returns data needed during runtime which could easily be pre-compiled.

By annotating the method with @Flow\CompileStatic and transforming it into a static method which does not depend on runtime services like persistence, security and so on, the performance in production context can be improved:

/**
 * Returns a map of action method names and their parameters.
 *
 * @param \Neos\Flow\ObjectManagement\ObjectManagerInterface $objectManager
 * @return array Array of method parameters by action name
 * @Flow\CompileStatic
 */
static protected function getActionMethodParameters($objectManager) {
        $reflectionService = $objectManager->get(\Neos\Flow\Reflection\ReflectionService::class);
        $className = get_called_class();
        $methodParameters = $reflectionService->getMethodParameters($className, get_class_methods($className));
        foreach ($methodParameters as $parameterName => $parameterInfo) {
          ...
        }
        return $methodParameters;
}

The results of methods annotated with CompileStatic will only be compile in Production context. When Flow is started in a different context, the method will be executed during each run.

Enabling Other Package Classes For Object Management

As stated in the beginning of this part, all classes in packages not in one of the neos-* types is not recognized for object management by default. If you still want that you can include those classes via configuration in settings. The configuration consists of a map of package keys to arrays of expressions which match classes to be included. In the following example we include all classes of the Acme.Objects package:

Neos:
  Flow:
    object:
      includeClasses:
        'Acme.Objects' : ['.*']

Note

If you use the includeClasses setting on a flow package (which is already enabled for object management) then only the classes that match at least one of the filter expressions are going to be object managed. This can also be used to remove classes inside flow packages from object management by specifying a non-matching expression or an empty array.

Note

The static method must except exactly one argument which is the Flow Object Manager. You cannot use a type hint at this point (for the $objectManager argument) because the argument passed could actually be a DependencyProxy and not the real ObjectManager. Please refer to the section about Lazy Dependency Injection for more information about DependencyProxy.

Virtual Objects

With the Objects.yaml configuration, the default behavior of classes can be changed globally. Sometimes it can be useful to configure the same class for multiple different use cases. For example two logger implementations that use the same instance but are configured separately.

With Flow version 6.2 and above it’s possible to configure “Virtual Objects”. The syntax is the same as described above (see Configuring objects) with two differences:

  • The object name has to contain a colon (to tell it apart from regular object names)

  • The className configuration is required (since it can’t be inferred from the object name)

Example: Objects.yaml for two virtual logger objects

'Some.Package:SystemLogger':
  className: Psr\Log\LoggerInterface
  scope: singleton
  factoryObjectName: Neos\Flow\Log\PsrLoggerFactoryInterface
  factoryMethodName: get
  arguments:
    1:
      value: systemLogger

'Some.Package:SecurityLogger':
  className: Psr\Log\LoggerInterface
  scope: singleton
  factoryObjectName: Neos\Flow\Log\PsrLoggerFactoryInterface
  factoryMethodName: get
  arguments:
    1:
      value: securityLogger

With those objects configured, the respective loggers can be instantiated via:

$systemLogger = $objectManager->get('Some.Package:SystemLogger');
$securityLogger = $objectManager->get('Some.Package:SecurityLogger');

Injecting Virtual Objects

To inject a Virtual Object you can simply use the name property of the Inject annotation to refer to the Virtual Object name:

Example: SomeClass.php

class SomeClass {
    /**
     * @Flow\Inject(name="Some.Package:SystemLogger")
     * @var LoggerInterface
     */
    protected $systemLogger;

Alternatively you can use constructor- or setter injection with a corresponding configuration:

Example: Objects.yaml

# ...
'Some\Package\SomeClass':
  properties:
    'systemLogger':
      object: 'Some.Package:SystemLogger'

Example: SomeClass.php

class SomeClass {

    public function injectSystemLogger(LoggerInterface $systemLogger): void
    {
        $this->systemLogger = $systemLogger;
    }