Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Interop Config Laravel Package

sandrokeil/interop-config

Framework-agnostic PHP library for configuration-driven factories. Validates config structure, merges defaults, enforces required options, reduces factory boilerplate, supports auto-discovery of factories, and can generate configuration files from factory classes.

View on GitHub
Deep Wiki
Context7

Examples

This files contains examples for each interface. The factory class uses the ConfigurationTrait to retrieve options from a configuration and optional to perform a mandatory option check or merge default options. There is also an example for a independent config structure of the Zend Expressive TwigRendererFactory. The psr-11 specification is used, so it's framework agnostic.

Use a vendor.package.id config structure

Let's assume we have the following module configuration:

// interop config example
return [
    // vendor name
    'doctrine' => [
        // package name
        'connection' => [
            // container id
            'orm_default' => [
                // mandatory params
                'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
                'params' => [
                    'host'     => 'localhost',
                    'port'     => '3306',
                    'user'     => 'username',
                    'password' => 'password',
                    'dbname'   => 'database',
                ],
            ], 
        ],
    ],
];

Note that the configuration above is injected as $config in options()

Retrieving options

Then you have easily access to the orm_default options in your method with ConfigurationTrait.

use Interop\Config\ConfigurationTrait;
use Interop\Config\RequiresConfigId;
use Psr\Container\ContainerInterface;

class MyDBALConnectionFactory implements RequiresConfigId
{
    use ConfigurationTrait;
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for doctrine.connection.orm_default
        $options = $this->options($container->get('config'), 'orm_default');
        
        // check if mandatory options are available or use \Interop\Config\RequiresMandatoryOptions, see below 
        if (empty($options['driverClass'])) {
            throw new Exception\RuntimeException(
                sprintf(
                    'Driver class was not set for configuration %s.%s.%s',
                    'doctrine', 
                    'connection', 
                    'orm_default'
                )
            );
        }

        if (empty($options['params'])) {
            throw new Exception\RuntimeException(
                sprintf(
                    'Params was not set for configuration %s.%s.%s',
                    'doctrine', 
                    'connection', 
                    'orm_default'
                )
            );
        }

        $driverClass = $options['driverClass'];
        $params = $options['params'];

        // create your instance and set options

        return $instance;
    }
    
    /**
     * Is used to retrieve options from the configuration array ['doctrine' => ['connection' => [...]]].
     *
     * [@return](https://github.com/return) iterable
     */
    public function dimensions() : iterable
    {
        return ['doctrine', 'connection'];
    }
}

Mandatory options check

You can also check for mandatory options automatically with MandatoryOptionsInterface. Now we want also check that option driverClass and params are available. So we also implement in the example above the interface RequiresMandatoryOptions. If one of these options are missing, an exception is raised.

use Interop\Config\ConfigurationTrait;
use Interop\Config\RequiresMandatoryOptions;
use Interop\Config\RequiresConfigId;
use Psr\Container\ContainerInterface;

class MyDBALConnectionFactory implements RequiresConfigId, RequiresMandatoryOptions
{
    use ConfigurationTrait;
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for doctrine.connection.orm_default
        $options = $this->options($container->get('config'), 'orm_default');

        // mandatory options check is automatically done by RequiresMandatoryOptions

        $driverClass = $options['driverClass'];
        $params = $options['params'];

        // create your instance and set options

        return $instance;
    }

    /**
     * Returns a list of mandatory options which must be available
     *
     * [@return](https://github.com/return) string[] List with mandatory options
     */
    public function mandatoryOptions() : iterable
    {
        return [
            'driverClass',
            'params',
        ];
    }
     
    /**
     * Is used to retrieve options from the configuration array ['doctrine' => ['connection' => [...]]].
     *
     * [@return](https://github.com/return) []
     */
    public function dimensions() : iterable
    {
        return ['doctrine', 'connection'];
    }
}

Use a static factory

Creation of a new instance of a specific config key is really easy by using the static variants of the factory. With this you don't have to instantiate a factory to use another config id in your config, which is really awesome. Remember, config files should only contain scalar values, so you can cache it in production. We have to add some lines of code to our factory. The magic is done via the __callStatic() method.

use Interop\Config\ConfigurationTrait;
use Interop\Config\RequiresMandatoryOptions;
use Interop\Config\RequiresConfigId;
use Psr\Container\ContainerInterface;

class MyDBALConnectionFactory implements RequiresConfigId, RequiresMandatoryOptions
{
    use ConfigurationTrait;
    
    /**
     * [@var](https://github.com/var) string
     */
    private $configId;

    /**
     * Creates a new instance from a specified config, specifically meant to be used as static factory.
     *
     * In case you want to use another config key than provided by the factories, you can add the following factory to
     * your config:
     *
     * <code>
     * <?php
     * return [
     *     'doctrine.connection.orm_second' => [MyDBALConnectionFactory::class, 'orm_second'],
     * ];
     * </code>
     *
     * [@param](https://github.com/param) string $name
     * [@param](https://github.com/param) array $arguments
     * [@return](https://github.com/return) mixed
     * [@throws](https://github.com/throws) \InvalidArgumentException
     */
    public static function __callStatic($name, array $arguments)
    {
        if (!isset($arguments[0]) || !$arguments[0] instanceof ContainerInterface) {
            throw new \InvalidArgumentException(
                sprintf('The first argument must be of type %s', ContainerInterface::class)
            );
        }
        return (new static($name))->__invoke($arguments[0]);
    }

    /**
     * [@param](https://github.com/param) string $configId
     */
    public function __construct(string $configId)
    {
        $this->configId = $configId;
    }
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for doctrine.connection.[config id]
        $options = $this->options($container->get('config'), $this->configId);

        // mandatory options check is automatically done by RequiresMandatoryOptions

        $driverClass = $options['driverClass'];
        $params = $options['params'];

        // create your instance and set options

        return $instance;
    }

    /**
     * Returns a list of mandatory options which must be available
     *
     * [@return](https://github.com/return) iterable List with mandatory options
     */
    public function mandatoryOptions() : iterable
    {
        return [
            'driverClass',
            'params',
        ];
    }
    
    /**
     * Is used to retrieve options from the configuration array ['doctrine' => ['connection' => [...]]].
     *
     * [@return](https://github.com/return) iterable
     */
    public function dimensions() : iterable
    {
        return ['doctrine', 'connection'];
    }
}

Default options

Use the ProvidesDefaultOptions interface if you have default options. These options are merged with the provided options in \Interop\Config\RequiresConfig::options(). Let's look at this example from DoctrineORMModule. All the options under the key orm_crawler are optional, but it's not visible in the factory.

return [
    'doctrine' => [
        'configuration' => [
            'orm_crawler' => [
                'metadata_cache'    => 'array',
                'query_cache'       => 'array',
                'result_cache'      => 'array',
                'hydration_cache'   => 'array',
            ],
        ],
    ],
];
use Interop\Config\ConfigurationTrait;
use Interop\Config\ProvidesDefaultOptions;
use Interop\Config\RequiresConfigId;

class ConfigurationFactory implements RequiresConfigId, ProvidesDefaultOptions
{
    use ConfigurationTrait;
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for doctrine.configuration.orm_crawler
        $options = $this->options($container->get('config'), 'orm_crawler');

        # these keys are always available now 
        $options['metadata_cache'];
        $options['query_cache'];
        $options['result_cache'];
        $options['hydration_cache'];

        // create your instance and set options

        return $instance;
    }
    
    /**
     * Returns a list of default options, which are merged in \Interop\Config\RequiresConfig::options
     *
     * [@return](https://github.com/return) iterable List with default options and values
     */
    public function defaultOptions() : iterable
    {
        return [
            'metadata_cache' => 'array',
            'query_cache' => 'array',
            'result_cache' => 'array',
            'hydration_cache' => 'array',
        ];
    }
    
    /**
     * Is used to retrieve options from the configuration array 
     * ['doctrine' => ['configuration' => []]].
     *
     * [@return](https://github.com/return) iterable
     */
    public function dimensions() : iterable
    {
        return ['doctrine', 'configuration'];
    }
}

Use arbitrary configuration structure

Whatever configuration structure you use, interop-config can handle it. You can use a three-dimensional array with vendor.package.id like the examples above or you don't care of it and organize your configuration by behavior or nature (db, cache, ... or sale, admin).

The following example demonstrates how to replace the Zend Expressive TwigRendererFactory. The factory uses optionally the following config structure:

return [
    'debug' => true,
    'templates' => [
        'cache_dir' => 'path to cached templates',
        'assets_url' => 'base URL for assets',
        'assets_version' => 'base version for assets',
        'extension' => 'file extension used by templates; defaults to html.twig',
        'paths' => [
            // namespace / path pairs
            //
            // Numeric namespaces imply the default/main namespace. Paths may be
            // strings or arrays of string paths to associate with the namespace.
        ],
    ],
    'twig' => [
        'cache_dir' => 'path to cached templates',
        'assets_url' => 'base URL for assets',
        'assets_version' => 'base version for assets',
        'extensions' => [
            // extension service names or instances
        ],
    ],
];

You can see that the factory uses different keys (debug, templates, twig) of the config array on the same level. This configuration is maybe used by other factories too like the debug setting. interop-config reduces the checks in the factory and gives the user the possibility to find out the config structure. More than that, it is possible to create the configuration file from the factory.

namespace Zend\Expressive\Twig;

use Psr\Container\ContainerInterface;
use Twig_Environment as TwigEnvironment;
use Twig_Extension_Debug as TwigExtensionDebug;
use Twig_ExtensionInterface;
use Twig_Loader_Filesystem as TwigLoader;
use Zend\Expressive\Router\RouterInterface;

// interop-config
use Interop\Config\ConfigurationTrait;
use Interop\Config\RequiresConfig;
use Interop\Config\ProvidesDefaultOptions;

class TwigRendererFactory implements RequiresConfig, ProvidesDefaultOptions
{
    use ConfigurationTrait;

    /**
     * Uses root config to retrieve several options
     *
     * [@return](https://github.com/return) iterable
     */
    public function dimensions() : iterable
    {
        return [];
    }

    /**
     * This is the whole config structure with default settings for this factory
     */
    public function defaultOptions() : iterable
    {
        return [
            'debug' => false,
            'templates' => [
                'extension' => 'html.twig',
                'paths' => [],
            ],
            'twig' => [
                'cache_dir' => false,
                'assets_url' => '',
                'assets_version' => '',
                'extensions' => [],
            ],
        ];
    }

    /**
     * [@param](https://github.com/param) ContainerInterface $container
     * [@return](https://github.com/return) TwigRenderer
     */
    public function __invoke(ContainerInterface $container)
    {
        $config = $container->has('config') ? $container->get('config') : [];

        // no OptionNotFoundException is thrown from ConfigurationTrait, because there are no config dimensions
        $config = $this->options($config);

        $debug = (bool) $config['debug'];

        // Create the engine instance
        $loader      = new TwigLoader();
        $environment = new TwigEnvironment($loader, [
            'cache'            => $debug ? false : $config['twig']['cache_dir'],
            'debug'            => $debug,
            'strict_variables' => $debug,
            'auto_reload'      => $debug
        ]);
        // Add extensions
        if ($container->has(RouterInterface::class)) {
            $environment->addExtension(new TwigExtension(
                $container->get(RouterInterface::class),
                $config['twig']['assets_url'],
                $config['twig']['assets_version']
            ));
        }
        if ($debug) {
            $environment->addExtension(new TwigExtensionDebug());
        }
        // Add user defined extensions
        $this->injectExtensions($environment, $container, $config['twig']['extensions']);
        // Inject environment
        $twig = new TwigRenderer($environment, $config['templates']['extension']);
        // Add template paths
        foreach ($config['templates']['paths'] as $namespace => $paths) {
            $namespace = is_numeric($namespace) ? null : $namespace;
            foreach ((array) $paths as $path) {
                $twig->addPath($path, $namespace);
            }
        }
        return $twig;
    }
    /**
     * Inject extensions into the TwigEnvironment instance.
     *
     * [@param](https://github.com/param) TwigEnvironment $environment
     * [@param](https://github.com/param) ContainerInterface $container
     * [@param](https://github.com/param) array $extensions
     * [@throws](https://github.com/throws) Exception\InvalidExtensionException
     */
    private function injectExtensions(TwigEnvironment $environment, ContainerInterface $container, array $extensions)
    {
        foreach ($extensions as $extension) {
            // Load the extension from the container
            if (is_string($extension) && $container->has($extension)) {
                $extension = $container->get($extension);
            }
            if (! $extension instanceof Twig_ExtensionInterface) {
                throw new Exception\InvalidExtensionException(sprintf(
                    'Twig extension must be an instance of Twig_ExtensionInterface; "%s" given,',
                    is_object($extension) ? get_class($extension) : gettype($extension)
                ));
            }
            if ($environment->hasExtension($extension->getName())) {
                continue;
            }
            $environment->addExtension($extension);
        }
    }
    // The mergeConfig function is not needed anymore
    // private function mergeConfig($config)
}
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky