selene/config

This package is abandoned and no longer maintained. No replacement package was suggested.

Selene configration component

dev-development 2014-08-22 17:46 UTC

This package is not auto-updated.

Last update: 2015-12-05 10:10:40 UTC


README

Build Status Code Climate Coverage Status

License

Installation

The component can be installed via composer.

{
    "require":{
        "selene/config":"dev-development"
    }
}

Then run

$ composer install

Loaders

The config package contains three different base loaders, the PhpFileLoader, the XmlFileLoader, and the CallableLoader. Both, the PhpFileLoader and the XmlFileLoader extend FileLoader and require a resource locator instance.

The loading implementation is up to you. e.g. let's say you have a class Config that sores all configuration values as an associative array.

<?php

namespace Acme\Config;

class Config
{
    protected $config;

    public function __construct(array $config = [])
    {
        $this->config = $config;
    }

    public function addValues(array $values)
    {
        $this->config = array_merge((array)$this->config, $values);
    }

    public function get($key, $default = null)
    {
        if (…) {
            return $value;
        }

        return $default;
    }

    public function all()
    {
        return $this->config;
    }
}

Your php loader would loock something like this:

<?php

namespace Acme\Config;

use \Selene\Components\Config\Loader\PhpFileLoader;
use \Selene\Components\Config\Resource\LocatorInterface;

class PhpLoader extends PhpFileLoader
{
    public function __construct(Config $config, LocatorInterface $locator)
    {
        $this->config = $config;
        parent__construct($locator);
    }

    protected function doLoad($file)
    {
        $values = parent::doLoad($file);

        $this->config->addValues($values);
    }
}

Loading files

With your new loader, you're now able to load configuration files like this:

<?php

use \Acme\Config\Config;
use \Acme\Config\PhpLoader;
use \Selene\Components\Config\Resource\Locator;

$config  = new Config;
$locator = new Locator(['path/config/a', 'path/config/b']);
$loader  = new PhpLoader($config, $locator;

$loader->load('config.php');

Note, that the locator takes an array of paths where to locate the configuration files. By default, the first result is used and the remaining paths are being discared, however if you pass a boolean (true) to the load() mehtod as a second argument, files from all paths will be located and loaded.

<?php

$loader->load('config.php', PhpLoader::LOAD_ALL);

Loading different resource types

It is possible to load different types of configuration using the DelegatingLoader class.

<?php

use \Acme\Config\PhpLoader;
use \Acme\Config\JsonLoader;
use \Selene\Components\Config\Loader\Resolver;
use \Selene\Components\Config\Loader\DelegatingLoader;

$loader = new DelegatingLoader(new Resolver(
    new PhpLoader($config, $locator),   
    new JsonLoader($config, $locator)  
));

$loader->load('config.php', PhpLoader::LOAD_ALL);
$loader->load('config.json', PhpLoader::LOAD_ALL);

Custom Loaders

In theory, you can create all kinds of loaders, e.g. a JSON loader.

<?php

namespace Acme\Config;

use \Selene\Components\Config\Loader\FileLoader;

class JsonFileLoader extends FileLoader
{
    protected $extension = 'json';

    /**
     * {@inheritdoc}
     */
    protected function doLoad($file)
    {
        $contents = json_decode(file_get_contents($file), true);

        // setter logic goes here.
    }
}

Listening to loader events.

Every time a resource is loaded, the loader will notify listeners that have been registered on the loader. Listeners must implements the Selene\Components\Config\Loader\LoaderListener.

For example, you may want to log the loading event to yout application log file.

<?php

namespace Acme\Config;

use \Psr\Log;
use \Selene\Components\Config\Loader\LoaderListener;

class ConfigLoaderLogger extends LoaderListener
{
    public function __construct(Log $logger)
    {
        $this->logger = $logger;
    }

    public function onLoaded($resource)
    {
        $this->logger->info(sprintf('Loaded resource "%s"',  $resource));
    }
}

Simply add the listener to your config loader using the addListener() method.

<?php

$configLoaderLogger = new ConfigLoaderLogger($logger);

$loader->addListener($configLoaderLogger);

If you need to remove the listener, you can user the removeListener method.

<?php

$loader->removeListener($configLoaderLogger);

Caching

Depending on the situation it may be useful to cache configuration, e.g. parsing an xml file on each request will affect performance. Instead you probably want to parse the configuration file once and store its contents in a php array.

<?php

use \Acme\Config\Config;
use \Acme\Config\XmlLoader;
use \Selene\Components\Config\Cache;
use \Selene\Components\Config\Resource\Locator;

$cache = new Cache($file);

if ($cache->isValid()) {
    $config = new Config(include $file);
} else {
    $config = new Config;
    $loader = new XmlLoader($config, new Locator($paths));
    $loader->load('config.xml');

    // …

    $cache->write("<?php\n    return ".var_export($config->all()).';');
}

The above solution is fine for loading a single file. Cache::isValid() will report false if the given cache file has been modified since the last request. However, the actual configuration files won't be taken into account.

The cache is capable of resource checking. Simply pass true to the constructr as a second argument and supply a list of resources to be tracked when writing the cache.

This is where the event capability of the loader comes into play. Lets modify the Acme\Config\Config class by implementing the Loaderlistener interface.

<?php

namespace Acme\Config;

use \Selene\Components\Config\Loader\PhpFileLoader;
use \Selene\Components\Config\Loader\LoaderListener;
use \Selene\Components\Config\Resource\FileResource;
use \Selene\Components\Config\Resource\ObjectResource;
use \Selene\Components\Config\Resource\LocatorInterface;

class PhpLoader extends PhpFileLoader implements LoaderListener
{
    private $resources;

    //…

    public function onLoaded($resource)
    {
        if (is_object($resource)) {
            $this->resources[] = new ObjectResource($resource); 
        } elseif(is_string($file)) {
            $this->resources[] = new FileResource($resource);   
        }
    }

    public function getResources()
    {
        return $this->resources;
    }
}

Every resource that's being loaded is now pushed to an array that we can use as the list of files the cache will use to track for cachnges.

<?php

$cache = new Cache($file, true);

// do the loading proceedure. 

// write the cache file content and pass in the resources to be tracked.
$cache->write(
    "<?php\n    return ".var_export($config->all()).';',
    $config->getResources()
);

Validation

The validator allows you to validate associative arrays. This is useful for validating user defined configuration before loading it.

The validator comes with a Builder class which allows you to define your configuration strcucture as a tree. The tree then will be validated agains an input array.

<?php

use \Selene\Components\Config\Validator\Builder;

$builder = new Builder('config');
$builder
    ->getRoot()
    // build the tree


$validator = $builder->getValidator();
$validator->load($config);
$validator->validate();

Node types

There're two different node types. ScalarNodes and ArrayNodes.

Both share common methods for defining their behaviour on validation time.

  • optional()
    Marks the node to be required.

  • notEmpty()
    Marks the node not to be not empty. The definition of an empty value depends on the node type. e.g. on a string node, an empty string is an empty value.

  • defaultValue($value)
    The value used if the node is optional and missing, or empty.

  • condition()
    Starts a conditional block. See Conditions conditions below.

  • end()
    When in context of the builder, end() will return the parent node of the current one.

Scalar nodes

Scalar nodes represents scalar values like strings, booleans, integers. etc.

Unlike ArrayNodes, scalar nodes cannot have childnodes.

boolean

Represents a boolean value.

<?php

$builder
    ->getRoot()
    ->boolean('required')
    ->end();
string

Represents a string value.

<?php

$builder
    ->getRoot()
    ->string('name')
    ->end();
integer

Represents an integer.

<?php

$builder
    ->getRoot()
    ->integer('port')
    ->end();
float

Represents a float value.

<?php

$builder
    ->getRoot()
    ->float('precission')
    ->end();
Array nodes

ArrayNodes may contain child nodes of both, type scalar and type array.

dict

Represents an associative array.

<?php

$builder
    ->getRoot()
    ->dict('memcached_server')
        ->string('host')->end()
        ->integer('port')->end()
        ->integer('weight')->end()
    ->end();
values

Represents an indexed array.

Note that you only can define one child node on a value node.
The childnode then represents the value type that's supposed to be in that indexed array.

<?php

$builder
    ->getRoot()
    ->values('paths')
        ->string('path')->end()
    ->end();
Node conditions

Node conditions act as a simple if/then block. The "then" part is only executed if the "if" part return true.

<?php

$root
    ->string()
        ->condition()
            ->when(function ($value) {…})
            ->then(function ($value) {…})
        ->end()
    ->end();

There're a couple of predefined "if" conditions.

  • always()
    Will always be execued.

  • when(Closure $expression)
    Evaluates the expression in the closure to be true or false.

  • ifTrue(Closure $expression)
    Same as when.

  • ifMissing()
    Will only trigger if the value (speaking the key of the input) is missing.

  • ifEmpty()
    Will only trigger if the value is emtpy.

  • ifNull()
    Will only trigger if the value is null.

  • ifArray()
    Same as when. Will only trigger if the input value is an array.

  • ifNotArray()
    Will only trigger if the input value is not an array.

  • ifInArray(array $values)
    Will only trigger if the input value is in the $values array.

  • ifNotInArray()
    Same as when. Will only trigger if the input value is not in the $values array.

  • ifString()
    Will only trigger if the input value is a string.

There're also a couple of predefined "then" results.

  • ifInt()
    Will only trigger if the input value is an integer.

  • ifFloat()
    Will only trigger if the input value is a float value.

  • thenMarkInvalid
    Marks the node invalid.

  • thenRemove
    Unsets the current node an removes it from its parent node.