Configuration

Configuration

Application configuration is handled by implementations of Configuration. To use the configuration throughout the application, an instance of the appropriate implementation is passed to ObjectFactory.

The following code demonstrates how to set up and use the InifileConfiguration configuration in code:

$configPath = WCMF_BASE.'app/config/';
// setup configuration
$configuration = new InifileConfiguration($configPath);
$configuration->addConfiguration('config.ini');
// register with object factory
ObjectFactory::registerInstance('configuration', $configuration);
// get a configuration value
$configuration = ObjectFactory::getInstance('configuration');
$tz = $configuration->getValue('timezone', 'application');

Configuration Format

Currently wCMF provides an implementation for using the INI file format. InifileConfiguration handles ini files that consist of key-value pairs that are grouped in sections:

1 [SectionA]
2 key1 = value1
3 key2 = value2
4 
5 [SectionB]
6 key3 = value3
Note
Configuration section and key names are treated case-insensitive.

Additionally it defines array values (enclosed in curly braces) and inclusion of additional files. Included files are defined in the include key of the Config section. If a configuration section with the same name exists in both files, the values are merged, whereby values in the current file override values from included files. This behavior is comparable to the concept of inheritance and can be disabled by adding the __inherit configuration key to the section and setting it to false.

The following example shows how to include an array of additional configuration files to separate concerns:

1 [Config]
2 include = {server.ini, persistence.ini, presentation.ini, security.ini}

Configuration sections from following files override identically named sections from preceding files.

Note
While path values in the configuration are normally resolved relative to the value of the WCMF_BASE constant, the included files are expected to be located in the directory that is passed to the constructor of InifileConfiguration.

The possibility to include configuration files into others is useful, if you want to separate varying parts from static ones to simplify the deployment process. In the default application for example all server specific configuration (e.g. database connection) are separated into a file called server.ini.

Dependency injection

wCMF supports Dependency injection through ObjectFactory and it's DefaultFactory implementation. This class implements constructor and setter injection, which means that dependencies are either set through the constructor or a setter method of the client class or a public member variable.

Assembly details for client instances are defined in the application configuration in special sections containing a __class key. The value of the key denotes the class to be used to create the client instance. All other keys are tried to be mapped to constructor parameters or setters/members of that class. Complex dependencies of an instance may be set by using the $ notation, which refers to another configuration section. If a section does not contain a __class key it is instantiated as an associative array.

Note
Instance names are treated case-insensitive.

The following example demonstrates the pattern by means of the PersistenceFacade instance:

1 [PersistenceFacade]
2 __class = wcmf\lib\persistence\impl\DefaultPersistenceFacade
3 mappers = $typeMapping
4 logStrategy = $auditingLogStragegy
5 
6 [AuditingLogStragegy]
7 __class = wcmf\lib\persistence\output\impl\AuditingOutputStrategy
8 
9 [TypeMapping]
10 app.src.model.wcmf.DBSequence = $app_src_model_wcmf_DBSequenceRDBMapper
11 app.src.model.wcmf.Lock = $app_src_model_wcmf_LockRDBMapper
12 
13 [app_src_model_wcmf_DBSequenceRDBMapper]
14 __class = app\src\model\wcmf\DBSequenceRDBMapper
15 connectionParams = $database
16 
17 [app_src_model_wcmf_LockRDBMapper]
18 __class = app\src\model\wcmf\LockRDBMapper
19 connectionParams = $database
20 
21 [Database]
22 dbType = sqlite
23 dbHostName = 127.0.0.1
24 dbName = app/test-db.sq3
25 dbUserName =
26 dbPassword =
27 dbCharSet = utf8
28 
29 [EventManager]
30 __class = wcmf\lib\core\impl\DefaultEventManager

The first section defines that the instance named PersistenceFacade is an object of the class DefaultPersistenceFacade.

So let's have a look at the relevant code in DefaultPersistenceFacade:

class DefaultPersistenceFacade implements PersistenceFacade {
public function __construct(EventManager $eventManager,
OutputStrategy $logStrategy) {
...
}
public function setMappers($mappers) {
...
}
}

When requested the first time, the PersistenceFacade instance is created in the following steps:

  1. Constructor parameters:
    • $eventManager is not explicitly defined in the instance configuration, so the factory looks it up in the configuration. Since there is a EventManager section, it uses it to create an instance of DefaultEventManager and injects it into the constructor. If there would be no section with this name, the factory would use the type hint and search for a class called EventManager.
    • $logStrategy is explicitly defined as an instance of AuditingOutputStrategy. This is an example of a complex dependency that is defined in another configuration section.
  2. Setter injection of the remaining parameters:
    • The mappers configuration parameter is not used in the constructor. The factory searches for a setter method (setMappers) or a public member variable ($mappers). Since the class defines a setter method, it is used to inject the dependency. The TypeMapping section does not contain a __class key, which means that the mappers property will be an associative array. In this example it maps class names (e.g. app.src.model.wcmf.DBSequence) to mapper instances (e.g. DBSequenceRDBMapper). Further sections show that the connectionParams property of the DBSequenceRDBMapper instance is set to an associative array describing the database connection (section Database).

The PersistenceFacade instance can be retrieved by using the following code:

$persistenceFacade = ObjectFactory::getInstance('persistenceFacade');
Note
An exception is thrown if a constructor parameter can't be resolved. That means if a constructor parameter is supposed to be optional, a default value must be provided.

Shared and Non-Shared instances

By default all instances created by ObjectFactory are shared instances, which means, that they will be created on the first call and only returned by succeeding calls. If you want to create a non-shared instance, you must use the __shared key in the configuration and set it to false.

The following example shows the configuration of a non-shared View instance:

1 [View]
2 __class = wcmf\lib\presentation\view\impl\SmartyView
3 __shared = false
4 compileCheck = true
5 caching = false
6 cacheLifetime = 3600
7 cacheDir = app/cache/smarty/

Aliases

The same instance may be referenced using different names (aliases). To define an alias, the __ref key is used. Aliases help avoiding duplicate configuration sections.

The following example shows the configuration of different caches, that actually use the same configuration.

1 [Cache]
2 __class = wcmf\lib\io\impl\FileCache
3 cacheDir = app/cache/
4 
5 [SQLCache]
6 __ref = $cache
7 
8 [ActionKeyCache]
9 __ref = $cache

Circular dependencies

Since DefaultFactory tries to resolve all dependencies when constructing an instance, it might happen that a dependency depends on the instance being currently constructed. This is called a circular dependency and would cause infinite recursion. To solve this problem the dependency in question must not be injected in the constructor, but in a setter method or member variable. This allows the factory to construct the initial instance first without requiring the child dependency. It is then available when constructing the child dependency.

Configuration for individual users

wCMF's user base class AbstractUser has a persistent property config which may be used to set a individual configuration for a user. This will be loaded after the standard configuration and extends its settings. If two keys have the same name the one from the user configuration overwrites the standard one. Using the RoleConfig configuration section, you can easily assign configuration files to groups:

1 [RoleConfig]
2 administrators = admin.ini

If a user is added to a listed role, the given configuration file will be automatically assigned to it, if no individual configuration is set yet.

Default application configuration

The default application uses some special configuration sections:

1 [Application]
2 title = WCMF TEST MODEL
3 color = #428BCA
4 rootTypes = {Author, Book, Publisher}
5 timezone = Europe/Berlin
6 listeners = {Search, EventListener}
7 
8 [Media]
9 uploadDir = app/public/media/
  • Application
    • title Application title
    • color Color to be used in the header of the login screen
    • rootTypes List of entity types to create tabs for
    • timezone Application timezone
    • listeners List of listeners to install (see Implicit listener installation)
  • Media
    • uploadDir Directory, to which media files should be uploaded

Logging configuration

Since logging is potentially required right from application startup, it is usually the first service to set up - even before setting up the application configuration. This implies that logging should not depend on other services, especially not on the application configuration.

wCMF provides integrations for log4php and Monolog through implementations of the Logger interface. The concrete classes are configured using a configuration file, that is passed to the constructor. An instance of the concrete class is then passed to the LogManager::configure method.

Monolog

To use the log4php library, instantiate MonologFileLogger in the following way:

$logger = new MonologFileLogger('main', WCMF_BASE.'app/config/log.ini');
LogManager::configure($logger);

MonologFileLogger is a simple wrapper class that allows logging to files or streams. The configuration is defined in the INI file format using the following sections and keys:

  • Root section defines properties valid for all loggers unless otherwise stated
    • level The default log level
    • target The logging target. This could be either a
      • directory (e.g. app/log/) for daily rotating log files or a
      • stream (e.g. php://output) for logging to a stream
  • Logger section defines log levels for individual loggers
    • logger name (e.g. wcmf\lib\presentation\Application) is used as key and the log level (e.g. DEBUG) as value

Since in wCMF each class uses it's own logger named after the class name, setting up the log level for individual classes is straightforward.

The following configuration sets the default log level to ERROR and the log level for the Controller class to DEBUG:

1 [Root]
2 level = ERROR
3 target = app/log/
4 
5 [Logger]
6 wcmf\lib\presentation\Controller = DEBUG

log4php

To use the log4php library, instantiate Log4phpLogger in the following way:

$logger = new Log4phpLogger('main', WCMF_BASE.'app/config/log4php.php');
LogManager::configure($logger);

The configuration file log4php.php uses log4php's PHP configuration format.

The following configuration sets the default log level to ERROR and the log level for the Controller class to DEBUG:

return [
'rootLogger' => [
'level' => 'ERROR',
'appenders' => ['dailyFile', 'echo'],
],
'loggers' => [
'wcmf.lib.presentation.Controller' => ['level' => 'DEBUG', 'appenders' => ['dailyFile']],
],
...
];