Developed with love by KnpLabs Hire us for your project!
99

OpenSkyRuntimeConfigBundle

by opensky

Provides a means for injecting configuration options into your Symfony2 application at runtime.

RuntimeConfigBundle

This bundle provides a way to inject parameters into services at runtime by
exposing a RuntimeParameterBag service, which functions exactly like Symfony2's
own ParameterBags.

As-is, Symfony2's service container is compiled and cached to disk, which makes
it difficult to inject dynamic parameters. By exposing a ParameterBag service,
we can inject values returned from its get() method into other services.

One reason you might want support for dynamic parameters would be to implement
feature flags/flippers, as are used by GitHub and Flickr. More info on
the history behind this bundle may be found on the symfony-devs mailing list.

Installation

Submodule Creation

Add RuntimeConfigBundle to your vendor/ directory:

$ git submodule add https://github.com/opensky/OpenSkyRuntimeConfigBundle.git vendor/bundles/OpenSky/Bundle/RuntimeConfigBundle

Class Autoloading

Register the "OpenSky" namespace prefix in your project's autoload.php:

# app/autoload.php

$loader->registerNamespaces(array(
    'OpenSky' => __DIR__'/../vendor/bundles',
));

Application Kernel

Add RuntimeConfigBundle to the registerBundles() method of your application
kernel:

public function registerBundles()
{
    return array(
        new OpenSky\Bundle\RuntimeConfigBundle\OpenSkyRuntimeConfigBundle(),
    );
}

Configuration

RuntimeConfigBundle Extension

The RuntimeParameterBag may be configured with the following:

# app/config/config.yml

opensky_runtime_config:
    provider: parameter.provider.service
    cascade:  true
    logging:
        enabled: true
        level:   debug

These settings are explained below:

  • provider: A service implementing ParameterProviderInterface. If you are using Doctrine ORM as your datasource, this could be an EntityRepository.
  • cascade: If true, calls to get() will cascade to the service container when the parameter is undefined in the runtime configuration. This will not change the behavior of has() or all(), which always only consider parameters from the runtime configuration provider.
  • logging.enabled: Whether to enable logging access to undefined parameters, regardless of whether service container cascading is enabled. If you are using Monolog, logs will be sent to the "opensky.runtime_config" channel.
  • logging.level: Log level to use (should be a LoggerInterface method).

Note: when using cascade, it's a good idea to define default values for your
runtime configuration parameters in your service container. This will help
avoid an undesirable ParameterNotFoundException if you happen to fetch a
parameter that is not yet defined in your runtime configuration.

Injecting Parameters

Consider the scenario where "my.service" depends on a dynamic parameter
"my.service.enabled".

Runtime parameters may be conveniently injected by abusing the anonymous service
syntax in XML configurations:

# Resources/config/my_service.xml

<service id="my.service" class="MyService">
    <argument type="service">
        <service class="stdClass" factory-service="opensky.runtime_config" factory-method="get">
            <argument>my.service.enabled</argument>
        </service>
    </argument>
</service>

Unfortunately, the YAML format does not yet support defining anonymous services.
Parameter injection is still possible, but more verbose:

# MyBundle/Resources/config/my_service.yml

my.service:
    class: MyService
    arguments:
        - @my.service.enabled

my.service.enabled:
    public: false
    class: stdClass
    factory_service: opensky.runtime_config
    factory_method: get
    arguments:
        - my.service.enabled

Note: in both cases (anonymous and labeled services), Symfony2 requires that we
define the class on our service definition. The above examples use "stdClass" as
an arbitrary placeholder to satisfy CheckDefinitionValidityPass. In reality, our
service is simply a means to lazily load our parameter. The value returned by
get() can be anything (e.g. object, scalar, array).

Cascade Mode

If you have enabled cascade mode, get() will attempt to fetch undefined
runtime parameters from the service container before throwing an exception.

Building upon the previous XML example, this would look like:

# Resources/config/my_service.xml

<parameters>
    <parameter key="my.service.enabled">false</parameter>
</parameters>

<services>
    <service id="my.service" class="MyService">
        <argument type="service">
            <service class="stdClass" factory-service="opensky.runtime_config" factory-method="get">
                <argument>my.service.enabled</argument>
            </service>
        </argument>
    </service>
</service>

In this example, get('my.services.enabled') would return false even if the
parameter was not defined in the runtime configuration. This is a safe way to
introduce new parameters, which might not yet be available from your provider
at the time of deployment.

Note: parameters sourced from the runtime configuration provider are not
resolved for placeholder syntax (i.e. "%reference%"), unlike those defined in
the service container.

Recipe: Interpreting Parameter Values as YAML

If you're using Doctrine ORM (or any database) to hold your parameters, you will
likely implement a CRUD interface to define and edit parameters via an admin
controller in your application.

Additionally, this allows us to add custom behavior to our ParameterProvider.
For instance, we can use Symfony2's YAML component to interpret parameter values
stored in the database as strings.

Consider the following Entity:

# MyBundle/Entity/Parameter.php

namespace MyBundle\Entity\Parameter;

use Doctrine\ORM\Mapping as ORM;
use OpenSky\Bundle\RuntimeConfigBundle\Entity\Parameter as BaseParameter;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContext;
use Symfony\Component\Yaml\Inline;
use Symfony\Component\Yaml\ParserException;

/**
 * @ORM\Entity(repositoryClass="MyBundle\Entity\ParameterRepository")
 * @ORM\Table(
 *     name="parameters",
 *     uniqueConstraints={
 *         @ORM\UniqueConstraint(name="name_unique", columns={"name"})
 *     }
 * )
 * @Assert\Callback(methods={"validateValueAsYaml"})
 */
class Parameter extends BaseParameter
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    public function getId()
    {
        return $this->id;
    }

    public function validateValueAsYaml(ExecutionContext $context)
    {
        try {
            Inline::load($this->value);
        } catch (ParserException $e) {
            $context->setPropertyPath($context->getPropertyPath() . '.value');
            $context->addViolation('This value is not valid YAML syntax', array(), $this->value);
        }
    }
}

Several things are happening here:

  • We must map an ID field, as the base Parameter class only defines essential name and value fields.
  • The base class defines assertions for name and value fields (in groups, which can be easily disabled); however, the mapped superclass does not define a unique constraint on the name, so that is necessary.
  • A callback assertion is used to check that the value property is valid YAML.

The above Entity class is complemented by the following EntityRepository, which
serves as the ParameterProvider for the RuntimeParameterBag:

# MyBundle/Entity/ParameterRepository.php

namespace MyBundle\Entity\Parameter;

use OpenSky\Bundle\RuntimeConfigBundle\Entity\ParameterRepository as BaseParameterRepository;
use Symfony\Component\Yaml\Inline;

class ParameterRepository extends BaseParameterRepository
{
    public function getParametersAsKeyValueHash()
    {
        return array_map(
            function($v){ return Inline::load($v); },
            parent::getParametersAsKeyValueHash()
        );
    }
}

The base ParameterRepository already fetches name/value pairs from the database
via a DQL query. Using array_map(), we can easily interpret those values
through the same YAML component method.

Note: although we validate the Entity, it's possible that a value might have
been manually altered in the database and contain invalid YAML when parameters
are fetched for provision. If this is a concern, you may want to gracefully
handle thrown ParserExceptions within getParametersAsKeyValueHash().

Copyright (c) 2011 OpenSky Project Inc

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
opensky_runtime_config:
provider: ~ # Required
cascade: true
logging:
enabled: true
level: debug
  • More performant RuntimeParameterBag::get() method.
    By Jonathan H. Wage, 5 months ago
  • Merge pull request #8 from geoffreytran/master
    By jwage, 2 years ago
  • Removed *
    By geoffreytran, 2 years ago
  • Changed version to be compatible with Symfony 2.1+
    By geoffreytran, 2 years ago
  • Merge pull request #7 from geoffreytran/master
    By jwage, 2 years ago
  • Changed required symfony version
    By geoffreytran, 2 years ago
  • Added Composer definition
    By geoffreytran, 2 years ago
  • Merge pull request #5 from opensky/feature/capture-exceptions
    By kriswallsmith, 2 years ago
  • Catch missing parameter exceptions thrown by containers
    By steves, 2 years ago
  • Merge pull request #4 from opensky/feature/handle-exceptions-in-twig
    By kriswallsmith, 2 years ago
  • Swallow exceptions caused by a missing key in the templates
    By steves, 2 years ago
  • added license
    By kriswallsmith, 2 years ago
  • Merge pull request #2 from opensky/feature/twig-extension
    By bobthecow, 3 years ago
  • Add a Twig extension to expose the RuntimeConfig.
    By bobthecow, 3 years ago
  • Added nullable=true on Parameter::$value property as it does not have an Assert\NotBlank and we want to allow null values.
    By jwage, 3 years ago
  • Fix typo in the readme
    By jmikola, 3 years ago
  • Update DIC service definition to match new constructor args.
    By bobthecow, 3 years ago
  • Revise some documentation for the cascade option
    By jmikola, 3 years ago
  • Merge branch 'master' of github.com:opensky/OpenSkyRuntimeConfigBundle
    By jmikola, 3 years ago
  • Remove strict mode and implement the ability to cascade get() to the service container
    By jmikola, 3 years ago
  • Fix typo in readme
    By jmikola, 3 years ago
  • De-Stofed a typo in the readme file
    By jmikola, 3 years ago
  • Add documentation
    By jmikola, 3 years ago
  • Extract generic Model\Parameter class and add additional, grouped validation constraints
    By jmikola, 3 years ago
  • Allow RuntimeParameterBagLogger to be configured and enabled by bundle extension
    By jmikola, 3 years ago
  • Create RuntimeParameterBagLogger, which can log requests for nonexistent parameters in RuntimeParameterBag
    By jmikola, 3 years ago
  • Use "name" instead of "key" to play nice with reserved words in SQL
    By jmikola, 3 years ago
  • Do not add unique constraint to column in mapped superclass
    By jmikola, 3 years ago
  • Manually load extension to overcome naming convention requirement in Bundle::getContainerExtension()
    By jmikola, 3 years ago
  • Fix reference to invalid class in RuntimeConfigAwareInterface and rename interface
    By jmikola, 3 years ago