1.4. Component Manager

All objects are controlled centrally by the Component Manager. It implements the Inversion of Control (IoC) principle 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 idea of components and the container. This chapter introduces the basic principles behind the Component Manager.

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.

If you remember only one thing

If the IoC principle, Dependency Injection and components are all new to you, you'll surely need some time getting used to these new approaches. But don't worry - you can even ignore the idea behind it and still produce acceptable code. If you remember only one thing about components, please let it be this section. There are a few rules you have to follow, even if you don't use the more advanced features.

  1. Use getComponent() instead of the new operator!

    The instantiation of classes must be handled by the Component Manager. Therefore if you have to instantiate a class or need an existing instance of a class, use the Component Manager's API for retrieving one:

    Example 1.2. Don't use the new operator

       // Avoid this:
    $myObject = new F3_MyPackage_MyClassName;


    Example 1.3. Ask the Component Manager instead

    class F3_MyPackage_SomeClass {
    
       protected $componentManager;
    
       public function __construct(F3_FLOW3_Component_ManagerInterface $componentManager) {
          $this->componentManager = $componentManager;
       }
    
       public function someFunction() {
          $myObject = $this->componentManager->getComponent('F3_MyPackage_MyClassName');
       }
    }


    In the above example you ask the Component Manager for the instance of F3_MyPackage_MyClassName. In order to get an instance of the Component Manager, just add a parameter to your class constructor as seen in the example - you will automatically get a reference injected.

  2. Singletons and Prototypes

    A Singleton is the name of a Design Pattern which ensures that only one instance of a class exists at a time. In PHP you can implement the Singleton pattern by creating a function (usually called getInstance()), which returns a unique instance of your class. Although this way of implementing the Singleton will possibly not conflict with the Component Manager, it is counter productive to the integrity of the system and might raise problems with unit testing (sometimes the Singleton is referred to as a Anti Pattern).

    The Component Manager will, if not configured differently, always return a unique instance of a component. Therefore Singleton is the default behaviour! If you need a fresh instance of your class, you will have to configure the scope of it by adding some annotation to your class:

    Example 1.4. Sample scope annotation

    /**
     * This is my great class.
     *
     * @scope prototype
     */
    class F3_MyPackage_SomeClass {
    
    }


Components and containers

The idea to segment software into reusable components which can be easily composed into a whole, is as old as software development itself. It is a tempting idea to just combine ready-made components instead of sorting out code from earlier projects which can hopefully be reused. Although the initial vision of mass-produced software with prefabricated components did not become reality, software componentry is undoubtedly a good thing, something you want your application framework to have support for.

Since version 3.5, TYPO3 allows anyone to extend the built-in functionality by developing their own extensions. This plugin concept has proven to be very powerful and led to thousands of publicly available extensions contributed by our community. While a plugin based system is usually based on a monolithic core the plugins can hook onto, a component based system is only composed of components itself. FLOW3, the foundation of TYPO3 version 5, is a component based framework giving you lots of opportunities to cleanly extend and modify virtually any part of the system.

Components

All PHP classes which are managed by the Component Manager are called Components. Because nearly all classes are managed by the Component Manager, almost all classes in FLOW3 - including third-party extensions - are used as components.

Tip

All classes which are found in the Classes directory of a package are automatically registered as components (See chapter Package Manager)

In contrast to plain, standalone classes which are probably only used in a project or two, the development of reusable components requires the author to pay more attention on proper documentation, testing and encapsulation. If a component is well designed, it doesn't expose the internal functions and properties of the class but only allows access through a well-thought API. By following this blackbox-principle, it is much easier to modify the internal behaviour of a component at a later time, because it is always known which functions can be used from outside.

Container

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 Shipexplained this circumstances nicely in his blog (quite some time ago):

Garbage collection is the last stage of an object's life cycle, but there's just as much going on at the start of the object's life cycle. That's why component frameworks and dependency injection containers (such as HiveMind, Spring, Picocontainer and Avalon) are so important.

[...] 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.

We as PHP developers don't have to care about garbage collection and we surely wouldn't like to be responsible for it either. However, building objects can be even more complex than destructing them. Therefore the FLOW3 framework manages the whole lifecycle of components for you. The Component Manager is a so called Lightweight Container taking care of object building and dependency resolution. We'll discover shortly why dependency injection makes such a difference to your application design.

Component Manager API

The Component Manager provides a lean API for registering, configuring and retrieving instances of components. Some of the methods provided are exclusively used within the FLOW3 package or in unit tests and should possibly not be used elsewhere. By offering Dependency Injection, the Component Manager helps you to avoid creating rigid interdependencies between components. It allows for writing code which is hardly or even not at all aware of the framework it is working in.

Although Dependency Injection is what you should strive for, it might happen that you need to retrieve component instances directly. Instead of using PHP's new operator, you must ask the Component Manager for the instance of a component.

First, you need an instance of the Component Manager itself and as you have seen in the shortcut example before, one way of getting it is adding a new parameter to your class constructor method:

public function __construct(F3_FLOW3_Component_ManagerInterface $componentManager) {
}

To explicitly retrieve the instance of a component use the getComponent() method:

$myComponentInstance = $componentManager->getComponent('F3_MyPackage_MyClassName');

It is possible to pass arguments to the constructor of the component class just by adding them to the getComponent() call:

$myComponentInstance = $componentManager->getComponent('F3_MyPackage_MyClassName', 'first argument', 'second argument');

Component names and types

By default, the name of a component is identical to the PHP class which implements the component's functions. A class called F3_MyPackage_MyImplementation will be automatically available as a component with the exact same name. Every part of the system which asks for a component instance 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 a component by another one. In that case the class name of the new implementation will naturally differ from the component name which stays the same at all times.

If the component name is the same as the name of a PHP interface, it is often referred to as a component type. An interface called F3_MyPackage_MyInterface will be available as a component name as long as there exists at least one class implemententing that interface.

Component dependencies

The intention to base an application on a combination of packages and components is to force a clean separation of domains which are realized by dedicated components. The less each component knows about the internals of another component, 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 components could be reused in a variety of contexts, for example independently from the TYPO3 package and maybe even outside the FLOW3 framework.

Dependency Injection

An important prerequisite for resuable code is already met by encouraging encapsulation through the components approach. However, the components are still aware of their environment as they need to actively collaborate with other components and the framework itself: An authentication component will need a logger for logging intrusion attempts and the code of a shop system hopefully consists of more than just one class. Whenever a component 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.

By introducing Dependency Injection, these interdependencies are minimized by inverting the control over resolving the dependencies: Instead of asking for the instance of a component actively, the depending component just gets one injected by the Component 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 component announces that it needs a logger which implements a certain PHP interface (eg. the F3_Log_LoggerInterface). The component itself has no control over what kind of logger (simple 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 component is instantiated, the component manager will resolve these dependencies, prepare an instance of a logger and inject it to the authentication component.

Tip

An article by Jonathan Amsterdam discusses the difference between creating an object and requesting one (ie. 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 components can be declared in the components configuration (see section about configuring components) or they can be solved automatically (so called autowiring). Generally there are two modes of dependency injection supported by FLOW3: Constructor Injection and Setter Injection.

Constructor Injection

With constructor injection, the dependencies are passed as constructor arguments to the depending component while it is instantiated. Here is an example of the authentication component which depends on a logger component:

Example 1.5. A simple example for Constructor Injection

public class F3_Authentication_LDAPAuthentication {

   protected $logger;

   public function __construct(F3_Log_LoggerInterface $logger) {
      $this->logger = $logger;
   }

   public function authenticate($credentials) {
      $this->logger->log('tried to authenticate');
   }
}


So far there's nothing special about this class, it just makes sure that an instance of a class implementing the F3_Log_LoggerInterface is passed to the constructor. However, this is already a quite flexible approach because the type of logger can be determined from outside by just passing one or the another implementation to the constructor.

Now the FLOW3 Component 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 (ie. F3_Log_LoggerInterface 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 Component Manager can also be configured manually to inject a certain component or component 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 1.6. Components.php file for Constructor Injection

$c->F3_Authentication_LDAPAuthentication->
constructorArguments[1]->reference = 'F3_Log_ASpecialLogger';


The two lines above define that a component instance of F3_Log_ASpecialLogger must be passed to the first argument of the constructor when an instance of the component F3_Authentication_LDAPAuthentication is created.

Setter Injection

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

Example 1.7. A simple example for Setter Injection

public class F3_Authentication_LDAPAuthentication {

   protected $someLogger;

   public function authenticate($credentials) {
      $this->logger->log('tried to authenticate');
   }

   public function setSomeLogger(F3_Log_LoggerInterface $someLogger) {
      $this->someLogger = $someLogger;
   }
}


Analog to the constructor injection example, a logger component is injected into the authentication component. In this case, however, the injection only takes place after the class has been instantiated and a possible constructor method has been called. The neccessary configuration for the above example looks like this:

Example 1.8. Components.php file for Setter Injection

<?php

$c->F3_Authentication_LDAPAuthentication->properties->someLogger->reference = 'F3_Log_LoggerInterface';

?>


Unlike constructor injection, setter injection like in the above example does not offer the autowiring feature. All depedencies have to be declared explicitly in the component configuration. To save you from writing large configuration files, FLOW3 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 1.9. The preferred way of Setter Injection, using an inject method

public class F3_Authentication_LDAPAuthentication {

   protected $someLogger;

   public function authenticate($credentials) {
      $this->logger->log('tried to authenticate');
   }

   public function injectSomeLogger(F3_Log_LoggerInterface $someLogger) {
      $this->someLogger = $someLogger;
   }
}


Note the new method name injectSomeLogger - for the above example no further configuration is required (but possible). Using inject* methods is the preferred way for setter injection in FLOW3.

Note

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

Required and optional dependencies

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

However, autowired setter-injected dependencies are, by default, optional. If a dependency can't be solved, it just won't be injected and it is the developer's responsability to test for the availability of the desired object. There is a way though to declare a setter-injected dependency as required without the need to configure the dependency in a Components.php file. FLOW3 uses the @required annotation for this purpose:

Example 1.10. Marking a setter-injected dependency as required

public class F3_Authentication_LDAPAuthentication {

   protected $someLogger;

   public function authenticate($credentials) {
      $this->logger->log('tried to authenticate');
   }

   /**
    * Injects a logger
    *
    * @param    F3_Log_LoggerInterface $someLogger A logger which is used for logging LDAP authentication events
    * @return   void
    * @author   Robert Lemke <robert@typo3.org>
    * @required
    */
   public function injectSomeLogger(F3_Log_LoggerInterface $someLogger) {
      $this->someLogger = $someLogger;
   }
}


Due to the @required annotation, the injection of a logger is now required. If the object builder can't autowire a logger for this injection method, it will now throw an exception.

Dependency resolution

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

Circular dependencies

During the resolution of dependencies it might happen that circular dependencies occur. If a component A requires a component B to be injected to its constructor and then again component B requires a component 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, FLOW3's policy is not to allow circular dependencies at all. As a workaround you can use Setter Injection instead of Constructor Injection for either one or both of the components causing the trouble.

Configuring components

The behaviour of components significantly depends on their configuration. During the initialization process all classes found in the various Classes/ directories are registered as components 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:

  1. If it exists, the file PackageName/Configuration/Components.php will be included. PHP code in this file must follow the general rules for PHP configuration files in FLOW3.

  2. Additional configuration defined in the global Configuration/ directory is applied.

  3. Additional configuration defined through the component manager user interface will be applied. This feature has not been implemented yet!

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

  • Override one component implementation with another

  • Set the active implementation for a component type

  • Define dependencies to other components

Configuration sources

As already mentioned, the configuration for each component is compiled from different sources. The Components.php file is the recommended format and is therefore used in most of the examples. However, the names of the configuration options and their possible values are identical to all configuration sources.

Components.php

If a file named Components.php exists in the Configuration directory of a package, it will be included during the configuration process (ie. before the first component class is instantiated!). The PHP file should stick to FLOW3's general rules for PHP-based configuration.

The following code adds the same configuration as in the Constructor Injection example which will be used in the next sections:

Example 1.11. Sample Components.php file

<?php
declare(ENCODING = 'utf-8');

/*                                                                        *
 * Component Configuration for the Authentication package                 *
 * (this package doesn't really exist, and even if so, the configuration  *
 * would probably be different)                                           *
 *                                                                        */

/**
 * @package Authentication
 * @version $Id: Components.php 123 2008-01-01 12:00:00Z robert $
 */

$c->F3_Authentication_LDAPAuthentication->constructorArguments[1]->reference = 'F3_Log_LoggerInterface';
$c->F3_Authentication_LDAPAuthentication->constructorArguments[2]->reference = 'F3_LDAP_LDAPServerInterface';
$c->F3_Authentication_LDAPAuthentication->constructorArguments[3] = 'cn=John Smith,ou=TYPO3 Development,o=TYPO3 Association,c=CH';

?>


Caution

Only use these files for configuration, for example don't register autoloader methods in the Components.php as this code must be invoked in an earlier stage. The Package.php is the right place for registering autoloaders.

Annotations

A very convenient way to configure certain attributes of components are annotations. You write down the configuration directly where it takes effect: in the class file. However, this way of configuring components 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 is the only supported annotation.

It's up to you defining the scope in the class directly or doing it in a Components.php 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 1.12. Sample scope annotation

/**
 * This is my great class.
 *
 * @scope prototype
 */
class F3_MyPackage_SomeClass {

}


Overriding components

One advantage of componentry is the ability to replace components by others without any bad impact on those parts depending on them. A prerequisite for replaceable components is that their classes implement a common interface which defines the public API of the original component. Other components which implement the same interface can then act as a true replacement for the original component 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 component configuration and set the class name to the new implementation.

To illustrate this circumstance, consider the following classes:

Example 1.13. A simple Greeter class

class F3_MyPackage_Greeter {
   public function sayHelloTo($name) {
      echo('Hello ' . $name);
   }
}


During initialization above class will automatically be registered as the component F3_MyPackage_Greeter and is available to other components. In the code of another component you might find these lines:

Example 1.14. Code using the component F3_MyPackage_Greeter

   // Explicitly fetch an instance of the F3_MyPackage_Greeter component:
$greeter = $componentManager->getComponent('F3_MyPackage_Greeter');

   // Say hello to Heike:
$greeter->sayHelloTo('Heike');


Great, that looks all fine and dandy but what if we want to use the much better component F3_OtherPackage_GreeterWithCompliments? Well, you just configure the component F3_MyPackage_Greeter to use a different class:

Example 1.15. Components.php file for component replacement

   # Change the name of the class which represents the component "F3_MyPackage_Greeter":
$c->F3_MyPackage_Greeter->className = 'F3_OtherPackage_GreeterWithCompliments';


Now all components who ask for a traditional greeter will get the more polite version. However, there comes a sour note with the above example: We can't be sure that the GreeterWithCompliments class really provides the necessary sayHello() method. The solution is to let both implementations implement the same interface:

Example 1.16. The Greeter component type

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

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

class F3_OtherPackage_GreeterWithCompliments implements F3_MyPackage_GreeterInterface{
   public function sayHelloTo($name) {
      echo('Hello ' . $name . '! You look so great!');
   }
}


Instead of referring to the original implementation directly we can now refer to the interface. In this case we call the component name a component type because it contains the name of a PHP interface.

Example 1.17. Code using the component type F3_MyPackage_GreeterInterface

   // Explicitly fetch an instance of any implementation of the F3_MyPackage_GreeterInterface component type:
$greeter = $componentManager->getComponent('F3_MyPackage_GreeterInterface');

   // Say hello to Heike:
$greeter->sayHelloTo('Heike');


Finally we have to set which implementation of the F3_MyPackage_GreeterInterface should be active:

Example 1.18. Components.php file for component type definition

$c->F3_MyPackage_GreeterInterface->className = 'F3_OtherPackage_GreeterWithCompliments';


Component types

Any interface found in the Classes/ directory will equally be registered as a component if at least one class within the same package was found that implements this interface. The first class found in the package which implements the interface is considered to be the default implementation and the component's className option is set accordingly. Of course it is still possible that the class name is defined explicitly in the package's Components.php file or any other configuration source. This is especially important if more than one implementing class exists.

Injecting properties and constructor arguments

As mentioned earlier, the Component Manager allows for injection of straight values or references (ie. dependencies) either by passing them as constructor arguments during instantiation of the component class or by calling a setter method which sets the wished property accordingly. The following sections demonstrate how to pass values and define dependencies to other components.

Straight values and references

Regardless of what injection type is used, there are two kinds of value which can be injected:

  • Straight values are static values of a simple type. They can be strings, integers, booleans, array or even custom objects (ie. objects which are not handled by the Component Manager) and are passed on as they are.

  • References are names of components (or component types) which represent dependencies to other components. Dependencies are resolved and an instance of the component is passed along.

Constructor Injection

The following class and the related Components.php file demonstrate the syntax for the definition of Constructor Injection:

Example 1.19. Sample class for Constructor Injection

public class F3_Authentication_LDAPAuthentication {

   protected $logger;
   protected $LDAPServer;
   protected $distinguishedName = '';

   public function __construct(F3_Log_LoggerInterface $logger, F3_LDAP_LDAPServerInterface $LDAPServer, $distinguishedName) {
      $this->logger = $logger;
      $this->LDAPServer = $LDAPServer;
      $this->distinguishedName = $distinguishedName;
   }

   public function authenticate(F3_Authentication_CredentialsInterface $credentials) {
      $this->LDAPServer->doSomeMagic($this->distinguishedName);
      $this->logger->log('tried to authenticate');
   }
}


Example 1.20. Sample configuration for Constructor Injection

  # Inject two component references as the first two arguments:
$c->F3_Authentication_LDAPAuthentication->constructorArguments[1]->reference = 'F3_Log_LoggerInterface';
$c->F3_Authentication_LDAPAuthentication->constructorArguments[2]->reference = 'F3_LDAP_LDAPServerInterface';

  # Inject a straight value as the third argument:
$c->F3_Authentication_LDAPAuthentication->constructorArguments[3] = 'cn=John Smith,ou=TYPO3 Development,o=TYPO3 Association,c=CH';


Note

It is usually not necessary to configure injection of references explicitly. It is much more convent to just declare the type of the constructor arguments (like F3_Log_LoggerInterface and F3_LDAP_LDAPServerInterface in the above example) and let the Autowiring feature configure and resolve the dependencies for you.

Setter Injection

The following class and the related Components.php file demonstrate the syntax for the definition of Setter Injection:

Example 1.21. Sample class for Setter Injection

public class F3_Authentication_LDAPAuthentication {

   protected $logger;
   protected $LDAPServer;
   protected $distinguishedName = '';

   public function authenticate(F3_Authentication_CredentialsInterface $credentials) {
      $this->LDAPServer->doSomeMagic($this->distinguishedName);
      $this->logger->log('tried to authenticate');
   }

   /**
    * @required
    */
   public function injectLogger(F3_Log_LoggerInterface $logger) {
      $this->logger = $logger;
   }

   /**
    * @required
    */
   public function injectLDAPServer(F3_LDAP_LDAPServerInterface $LDAPServer) {
      $this->LDAPServer = $LDAPServer;
   }

   public function setDistinguishedName($distinguishedName) {
      $this->distinguishedName = $distinguishedName;
   }
}


Example 1.22. Sample configuration for Setter Injection

  # Inject two component references:
$c->F3_Authentication_LDAPAuthentication->properties->logger->reference = 'F3_Log_LoggerInterface';
$c->F3_Authentication_LDAPAuthentication->properties->LDAPServer->reference = 'F3_LDAP_LDAPServerInterface';

  # Inject a straight value as the third argument:
$c->F3_Authentication_LDAPAuthentication->properties->distinguishedName = 'cn=John Smith,ou=TYPO3 Development,o=TYPO3 Association,c=CH';


As you can see, it is important that a setter method with the same name as the property, preceded by "inject" or "set" exists.

Instantiating components

In order to accomplish all the tasks connected with Dependency Injection and other advanced features, FLOW3 must take full control over the instantiation of the component classes. Usually it's sufficient to know that you have to either retrieve an instance manually with the getComponent method or get them injected by the Component Manager. From the component's point of view, a few options may be set to take influence on the instantiation of its class. This section explains these configuration options and finally outlines the instantiation process as a whole.

Scopes

The objects created by the Component Manager all exist in a certain scope. By default, an instance of a component class is unique which makes sure that the exact same object is returned whenever the Component Manager is asked for a specific component. This default scope is called Singleton. Of course other scopes are supported as well:

Table 1.1. Supported scopes

ScopeDescription
singleton (default)The component instance is unique during one request - each getComponent call returns the same instance. A request can be an HTTP request or a run initiated from the command line.
prototypeThe component instance is not unique - each getComponent call returns a fresh instance.
session Not yet implementedThe component instance is unique during the whole user session - each getComponent call returns the same instance.
content Not yet implementedThe component instance is persisted in a Content Repository.


A great feature of the Component Manager is that components don't have to implement their own mechanism for administrating their scope - it only has to be configured. The following example contains configurations for three components, all living in a different scope:

Example 1.23. Sample Components.php with different scopes

$c->F3_MyPackage_ASingletonClass->scope = F3_FLOW3_Component_Configuration::SCOPE_SINGLETON;
$c->F3_MyPackage_APrototypeClass->scope = F3_FLOW3_Component_Configuration::SCOPE_PROTOTYPE;
$c->F3_MyPackage_ASessionClass->scope = F3_FLOW3_Component_Configuration::SCOPE_SESSION;


Note

Unlike in the above example, the recommended way to define the scope of a component is the @scope annotation.

Passing constructor arguments

In most cases a component class will live in the Singleton scope and at most requires a few dependencies passed to its constructor. However, there are times when it becomes necessary to pass dynamic values as constructor arguments, especially when the component represents an entity and its instances are not unique (Prototype scope). Consider the following classes:

Example 1.24. A simple addressbook

class F3_Address_Adressbook {

   protected $addresses = array();

   public __construct(F3_iCal_iCalConnectorInterface $iCalConnector) {
      ...
   }

   public addAddress(F3_Address_Address $address) {
      $this->addresses[] = $address;
   }

}

class F3_Address_Address {

   public __construct($street, $zip, $town, $country) {
      ...
   }
}


This is admittedly not the fanciest implementation of an address book, but it should demonstrate two things:

  • The class F3_Address_Addressbook is supposed to be a Singleton and obviously depends on a third component type F3_iCal_iCalConnectorInterface which is possibly solved by Dependency Injection.

  • The class F3_Address_Adress represents the address entity and its instances must not be unique - we surely want more than one address. The Address component also expects a few parameters passed to its constructor.

The following code demonstrates how this address book can be used and constructor arguments are passed to the Address entity:

Example 1.25. Passing constructor arguments

   # Fetch a unique instance of the addressbook:
$myAddressbook = $componentManager->getComponent('F3_Address_Addressbook');

   # Create two new addresses and add them to the addressbook:
$newAddress = $componentManager->getComponent('F3_Address_Address', 'Tryggevældevej', '2720', 'København', 'DK');
$myAddresbook->addAddress($newAddress);

$newAddress = $componentManager->getComponent('F3_Address_Address', 'An den Brodbänken', '21335', 'Lüneburg', 'DE');
$myAddresbook->addAddress($newAddress);


Autowiring

Injecting dependencies to a constructor function is a common task. Because FLOW3 can detect the type of dependencies a constructor needs, it automatically configures the component to assert that the necessary components are injected. This automation is called autowiring and is enabled by default for every component. To repeat our favourite example, imagine that your component class needs some kind of logger. All you need to do in order to get one is writing the following constructor:

Example 1.26. An autowired logger

public function __construct(F3_Log_LoggerInterface $logger) {
   $logger->log("Hooray");
}


The constructor of the above example will get an instance of the component of type F3_Log_LoggerInterface injected and can use it for further operations.

If, for some reason, you need to disable autowiring support, you can do so by setting an option in your component configuration:

Example 1.27. Turning off autowiring support in Components.php

$c->F3_MyPackage_MyComponent->autoWiringMode = FALSE;


Lifecycle methods

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

  1. Solve dependencies for constructor injection

  2. Create an instance of the component class

  3. Solve and inject dependencies for setter injection

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

  5. Dispose the component instance

Your component might want to take some action after certain of the above steps. Whenever one of the following methods exists in the component 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 component's initialization method is called. The name of this method is configurable (see below) and it is called regardless of whether any dependencies have been injected or not

  4. During the life of a component no special lifecycle methods are called

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

As you can see from the above list, there is only one special method which is provided by PHP's own means and that is the initialization method. Here's a simple example:

Example 1.28. A component class with an initialization method

class F3_MyPackage_MyClass {

   protected $logger;

   public function __construct(F3_Log_LoggerInterface $logger) {
      $this->logger = $logger;
   }

   public function intializeComponent() {
      $this->logger->log('MyClass has been initialized.');
   }
}


The above example will just work out of the box without any further configuration. However, if you don't have control over the name of your initialization method (maybe, because you are integrating legacy code), you can configure the name of the method in the component configuration:

Example 1.29. Components.php configuration of the initialization method

$c->F3_MyPackage_MyClass->lifecycleInitializationMethod = 'myInitialize';