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.
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 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.
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.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.
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 {
}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.
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.
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.
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
newis 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.
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');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.
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.
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.
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.
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.
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.
If both, a set* and a
inject* method exist for the same property,
the inject* method has precendence.
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.
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.
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.
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:
If it exists, the file
will be included. PHP code in this file must follow the general rules
for PHP configuration files in FLOW3.PackageName/Configuration/Components.php
Additional configuration defined in the global
Configuration/ directory is applied.
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
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.
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'; ?>
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.
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 {
}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';
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.
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.
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.
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';
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.
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.
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.
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
| Scope | Description |
|---|---|
| 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. |
| prototype | The component instance is not unique - each
getComponent call returns a fresh
instance. |
| session Not yet implemented | The component instance is unique during the whole user
session - each getComponent call returns
the same instance. |
| content Not yet implemented | The 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;
Unlike in the above example, the recommended way to define the scope of a component is the @scope annotation.
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);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;
The lifecycle of a component object goes through different stages. It boils down to the following order:
Solve dependencies for constructor injection
Create an instance of the component class
Solve and inject dependencies for setter injection
Live a happy component-life and solve exciting tasks
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:
No action after this step
During instantiation the function
__construct() is called (by PHP itself),
dependencies are passed to the constructor arguments
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
During the life of a component no special lifecycle methods are called
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';