Services

1. Introduction

Encapsulation of functionality within separate components is common in the object orientated world. The advantages of this method is described by the tree tier architecture pattern. Thereby, the different layers have different duties concerning their layer.

Due to the fact, that the layers or services are commonly represented by one or more classes each developer is faced with the task to create classes for a dedicated layer or a part of a layer. Further, according to the DRY principle each developer is told to design reusable components - Services - that have a clear representation and configuration interface. For example data layers often require a component, that has access to databases and a business component needs knowledge about the context it is executed in. Besides, there are dependencies between different tiers as well.

2. Basics

As described in the Page controller chapter the Adventure PHP Framework provides a special way of creating objects that are responsible for creating UI elements. Since business and data layer object must also know about the context they are executed in (see configuration scheme) it is necessary to transport this information into the service objects.

The framework provides two components for object creation that automatically inject the required data of the surrounding environment (context, language).

The subsequent chapters describe the mentioned components and their meaning to development.

3. Object creation mechanism

The Adventure PHP Framework has several mechanisms to create and initialize objects. To provide the necessary information of the surrounding environment mentioned above for each object (DOM nodes, business services, etc.) the environment attributes are passed to the new object using dependency injection.

Due to the fact, that the framework includes a generic way to create singleton and session singleton objects the implementation avoids constructor injection to be able to provide a generic way of object creation and to preserve testability. For this reason, initialization is only done using method injection / setter injection. The required data or dependencies are provided after creation of the object.

3.1. GUI objects

Creation of presentation layer objects is done by factory methods of the Page controller. Each object is injected the context, the current language and the attributes of the DOM node by interface methods.

In case you just apply taglibs or parser methods provided by the release you don't have to care about that mechanism. Notes on custom taglib implementation can be read about in chapter Implementation of tags.

3.2. Service objects

To ease development concerning creation of services or further objects representing an application layer and to not have to deal with the internal mechanisms of the framework's dependency injection the developer is provided three different possibilities to create pre-configured objects. Therefore the APFObject class provides wrapper methods for the ServiceManager and the DIServiceManager.

Details on the method signatures can be found within the API documentation or by the code completion functions of your IDE (see Recommended development tools).

3.2.1. Simple services using the ServiceManager

Using getServiceObject() you will receive an object, that is initialized with the context and the language of the current environment. Within this method, the ServiceManagers is used (see API documentation). Due to the fact, that the wrapper function uses the context and the language of the current object you are able to configure different contexts within one application (e.g. different contexts for different modules).

3.2.2. Initialized services using the ServiceManager

The getAndInitServiceObject() enhances the method from chapter 3.2.1 with dynamic initialization. You can pass the initialization parameter as a the third argument that is passed to the init() method of the service. This parameter is typeless what means that you can pass any type of data type.

This method of initialization included one disadvantage: the initialization parameter must be known by the service consumer. For this reason you should use this mechanism in conjunction with service initialization by application specific information (e.g. providing an application id defined within a tag attribute).

3.2.3. Complex services using the DIServiceManager

The getDIServiceObject() method is a wrapper function for the DIServiceManager call, that provides a user-level dependency injection container for the APF. Services can be initialized by other services or static parameters.

Using the DIServiceManager better separates application code and configuration. Parameters are injected from outside the source code and service creation must not ne part of the code. This leads to loosely coupled services that are easily reusable. Further, testability is much more better, because database access can easily be replaced by a MOCK layer.

4. Samples

Applying the theoretical knowledge described above is really a piece of cake. The subsequent chapters give you an example of the application of the methods.

4.1. Simple services

As described in chapter 3.2.1 getServiceObject() returns a simple service object:

PHP code
class MyObject extends APFObject { public function doSomething(){ $myService = &$this->getServiceObject( $namespace, $serviceName, [$type = APFService::SERVICE_TYPE_SINGLETON], [$instanceId = null] ); $myService->doSomethingElse(); }

The APFObject::getServiceObject() method takes the following arguments:

  • $namespace: Refers to the namespace of the service implementation. (Example: namespace::of::my::service).
  • $serviceName: Defines the name of the service that in turn is the name of the service implementation class (Example: MyServiceName).
  • $type: The $type parameter defines the service type that describes the scope of the service instance. Possible values are SINGLETON, SESSIONSINGLETON, and NORMAL. It is recommended to use the constants defined in APFService, e.g. APFService::SERVICE_TYPE_SESSIONSINGLETON. This parameter is optional and it's default is SINGLETON. See also Singleton / SessionSingleton.
    Starting with 1.16 services with type SINGLETON or SESSIONSINGLETON are unique by a combined key of context and language. This enables you to use services or entire modules multiple times within one application but independently using different contexts. Further information can be found within article Migration from 1.15 to 1.16.
  • $instanceId: Allows you to apply a custom instance identifier using SINGLETON and SESSIONSINGLETON services. This parameter is optional and is auto-generated from namespace, service name, as well as - since 1.16 - language and context by the ServiceManager.
The service implementation is directly included by the ServiceManager. Hence, there is no need to add an import() before usage.

4.2. Initialized services

The creation of a pre-configured service is as follows:

PHP code
class MyObject extends APFObject { public function doSomething() { $initParam = 'foo'; $myService = &$this->getAndInitServiceObject( $namespace, $serviceName, $initParam, [$type = APFService::SERVICE_TYPE_SINGLETON], [$instanceId = null] ); $myService->doSomethingElse(); } }

The APFObject::getAndInitServiceObject() method takes the following arguments:

  • The $namespace, $serviceName, $type, and $instanceId parameters are the same as in APFObject::getServiceObject().
  • $initParam: This parameter is passed to the init() method of the created object. You can pass all types of data such as other objects (services), arrays, or scalar data types.

4.3. Complex services

Creating services with the dependency injection container of the APF is different to the object initialization described in the last two chapters. First of all, each DI service is represented by a configuration. This is due to the fact, that one service is no standalone object but can be used for initialization of other services, too.

This allows you to define services using the DIServiceManager that are dependent on other services.

4.3.1. Configuration

In order to create a service named MyService for namespace modules::mymodule::services the configuration file must be created:

Code
/config/modules/mymodule/services/{CONTEXT}/{ENVIRONMENT}_serviceobjects.ini

The definition of the service itself (including implementation and initialization) is done as follows:

APF configuration
[{ServiceName}] servicetype = "" namespace = "" class = "" [init.{INITKEY}.method = "" init.{INITKEY}.namespace = "" init.{INITKEY}.name = ""] [conf.{INITKEY}.method = "" conf.{INITKEY}.value = ""]
Definition:
  • servicetype: The servicetype directive defines the type of service. Allowed values are SINGLETON, SESSIONSINGLETON, NORMAL und CACHED (since 1.16). Details can be taken from the ServiceManager usage chapter.
    Since release 1.16 objects defined as SINGLETON and SESSIONSINGLETON are created uniquely for each context-language combination. Service type CACHED has been added in this release and equals service type NORMAL for version <= 1.15. This means that the service object is cached by the DIServiceManager and delivered to the user without new initialization on the next attempt. Using the NORMAL type causes the DIServiceManager to create the requested service on every request. Further information can be found in article Migration from 1.15 to 1.16.
  • namespace defines the namespace of the service implementation.
  • class defines the class name of the service implementation.
  • The init section is responsible for dynamic initialization using dependency injection the conf section can inject static configuration params.
  • The method attribute of an init sub section defines the method that is used to inject the desired service. namespace and name is used to refer a desired service to initialize the current with. Please note, that these two parameters do refer to a service definition not a service implementation (i.e. class)! The referred service must be described by a configuration section as described above.
  • The method attribute of the conf sub section defines the method that is used to inject the desired configuration parameter. The service is thus provided the content that is defined within the value attribute.
4.3.2. Example

As an example a business component should be initialized with static configuration parameters, a data layer component, and a provider that is responsible for configuration issues. For convenience, the name of the service is GuestbookService and the namespace is modules::guestbook. The implementation of the service is provided by the GuestbookManager class located in namespace modules::guestbook::biz.

In order to use the dependency injection mechanism we have to create a configuration file that represents the GuestbookService. Having the last few lines in mind the file must be named as shown in the next code box:

Code
/config/modules/guestbook/{CONTEXT}/{ENVIRONMENT}_serviceobjects.ini

For a first instance, the service definition is as follows:

APF configuration
[GuestbookService] class = "GuestbookManager" namespace = "modules::guestbook::biz" servicetype = "SINGLETON"

Please note, that the config provider must be represented by another configuration section. Thereby namespace of the data layer component DataService is modules::guestbook and the ExtendedConfigProvider's namespace is modules::guestbook::provider. Due to the fact, that the namespaces of the GuestbookService and the data layer service are are equal, they can be placed within the same configuration file. The ExtendedConfigProvider must be configures within another configuration file:

Code
/config/modules/guestbook/provider/{CONTEXT}/{ENVIRONMENT}_serviceobjects.ini

To sum things up, the content of the service definition file {ENVIRONMENT}_serviceobjects.ini located in the modules::guestbook namespace looks like this:

APF configuration
[GuestbookService] namespace = "modules::guestbook::biz" class = "GuestbookManager" servicetype = "..." [DataService] namespace = "modules::guestbook::biz" class = "GuestbookMapper" servicetype = "..."

The ExtendedConfigProvider is configured within the service definition file {ENVIRONMENT}_serviceobjects.ini (namespace: modules::guestbook::provider):

APF configuration
[ExtendedConfigProvider] namespace = "modules::guestbook::biz::provider" class = "SpecialConfigProvider" servicetype = "..."

To inform the DIServiceManager to initialize the GuestbookService using the services described above it's configuration section must be added two dynamic and two static initialization sub sections:

APF configuration
init.database.method = "setDBService" init.database.name = "modules::guestbook" init.database.namespace = "DataService" init.exconf.method = "setConfigProvider" init.exconf.name = "modules::guestbook::provider" init.exconf.namespace = "ExtendedConfigProvider" conf.appname.method = "setAppId" conf.appname.value = "123" conf.cache.method = "setCacheActive" conf.cache.value = "false"

As you can take from the above code box you can define various initialization sub sections. Please note, that each sub section must includes a unique sub section key.

Using the GuestbookService is as follows:

PHP code
class ServiceConsumer extends APFObject { public function doSomething(){ $service = &$this->getDIServiceObject( 'modules::guestbook', 'GuestbookService' ); $service->doSomethingElse(); }

Concerning the service definition, the service implementation (class: GuestbookManager) must include at least the listed methods:

PHP code
class GuestbookManager extends APFObject { public function setDBService($dbService){ $this->dbService = $dbService; } public function setConfigProvider($provider){ $this->configProvider = $provider; } public function setAppId($appId){ $this->appId = $appId; } public function setCacheActive($cacheActive){ $this->cacheActive = $cacheActive; } public function doSomethingElse(){ } ... }

Within doSomething() you can access all the services and configuration values that are injected into the service.

Another application sample is described on Erzeugen des GORM mit dem DIServiceManager (German).

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.