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

Quick Start

Typically you will have a factory which creates a concrete instance depending on some options (dependencies).

RequiresConfig interface

Let's say My factory requires a configuration so you will implement the RequiresConfig interface.

use Interop\Config\RequiresConfig;

class MyAwesomeFactory implements RequiresConfig
{
    public function dimensions() : iterable
    {
        return ['vendor-package'];
    }
    
    public function canRetrieveOptions($config) : bool
    {
        // custom implementation depending on specifications
    }
    
    public function options($config)
    {
        // custom implementation depending on specifications
    }
}

Need configuration per container id

If you support more than one instance with different configuration then you simply use the RequiresConfigId interface.

Don't use the dimensions() method for container id configuration

use Interop\Config\RequiresConfigId;

class MyAwesomeFactory implements RequiresConfigId
{
    public function dimensions() : iterable
    {
        return ['vendor-package'];
    } 

    public function canRetrieveOptions($config, string $configId = null) : bool
    {
        // custom implementation depending on specifications
    }
    
    public function options($config, string $configId = null)
    {
        // custom implementation depending on specifications
    }
}

Ok you have now a factory which says that the factory supports a configuration and you have a PHP file which contains the configuration as a PHP array, but how is the configuration used?

Depending on the implemented interface RequiresConfigId above our configuration PHP file looks like that:

// interop config example
return [
    // vendor/package name
    'vendor-package' => [
        // container id
        'container-id' => [
            // some options ...
        ],
    ],
];

As you can see that you have to implement the functionality of canRetrieveOptions() and options() method. Good news, this is not necessary. See ConfigurationTrait.

ConfigurationTrait

The ConfigurationTrait is a concrete implementation of the RequiresConfig interface and has full support of ProvidesDefaultOptions, RequiresMandatoryOptions and RequiresConfigIdinterfaces. It's a PHP Trait so you can extend your factory from a class.

Your factory looks now like that:

use Interop\Config\RequiresConfigId;
use Interop\Config\ConfigurationTrait;

class MyAwesomeFactory implements RequiresConfigId
{
    use ConfigurationTrait;
    
    public function dimensions() : iterable
    {
        return ['vendor-package'];
    }
}

Now you have all the ingredients to create multiple different instances depending on configuration.

Create an instance

Factories are often implemented as a callable. This means that your factory instance can be called like a function. You can also use a create method or something else.

The factory gets a ContainerInterface (Container PSR) provided to retrieve the configuration.

Note that the configuration above is injected as $config in options() and psr-11 is used to retrieve the application configuration.

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

class MyAwesomeFactory implements RequiresConfigId
{
    use ConfigurationTrait;
    
    public function dimensions() : iterable
    {
        return ['vendor-package'];
    }
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for vendor-package.container-id
        // method options() is implemented in ConfigurationTrait
        $options = $this->options($container->get('config'), 'orm_default');
        
        return new Awesome($options);
    }
}

The ConfigurationTrait does the job to check and retrieve options depending on implemented interfaces. Nice, but what is if I have mandatory options? See RequiresMandatoryOptions interface.

RequiresMandatoryOptions interface

The RequiresConfig::options() interface specification says that it MUST support mandatory options check. Let's say that we need params for a db connection. Our config should looks like that:

// interop config example
return [
    // vendor/package name
    'vendor-package' => [
        // container id
        'container-id' => [
            'params' => [
                'user'     => 'username',
                'password' => 'password',
                'dbname'   => 'database',
            ],
        ],
    ],
];

Remember our factory sentence. My factory requires a configuration and requires a container id along with mandatory options. The ConfigurationTrait ensures that these options are available, otherwise an exception is thrown. This is great, because the developer gets an exact exception message with what is wrong. This is useful for developers who use your factory the first time.

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

class MyAwesomeFactory implements RequiresConfigId, RequiresMandatoryOptions
{
    use ConfigurationTrait;
    
    public function dimensions() : iterable
    {
        return ['vendor-package'];
    }
    
    public function mandatoryOptions() : iterable
    {
        return ['params' => ['user', 'password', 'dbname']];
    }
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for vendor-package.container-id
        // method options() is implemented in ConfigurationTrait
        // an exception is raised when a mandatory option is missing
        $options = $this->options($container->get('config'), 'container-id');
        
        return new Awesome($options);
    }
}

Hey, the database port and host is missing. That's right, but the default value of the port is 3306 and the host is localhost. It makes no sense to set it in the configuration. So I make the database port/host not configurable? No, you use the ProvidesDefaultOptions interface.

ProvidesDefaultOptions interface

The ProvidesDefaultOptions interface defines default options for your instance. These options are merged with the provided options.

Remember: My factory requires configuration, requires a container id along with mandatory options and it provides default options.

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

class MyAwesomeFactory implements RequiresConfigId, RequiresMandatoryOptions, ProvidesDefaultOptions
{
    use ConfigurationTrait;
    
    public function dimensions() : iterable
    {
        return ['vendor-package'];
    }
    
    public function mandatoryOptions() : iterable
    {
        return ['params' => ['user', 'password', 'dbname']];
    }
    
    public function defaultOptions() : iterable
    {
        return [
            'params' => [
                'host' => 'localhost',
                'port' => '3306',
            ],
        ];
    }
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for vendor-package.container-id
        // method options() is implemented in ConfigurationTrait
        // an exception is raised when a mandatory option is missing
        // if host/port is missing, default options will be used
        $options = $this->options($container->get('config'), 'container-id');
        
        return new Awesome($options);
    }
}

Now you have a bullet proof factory class which throws meaningful exceptions if something goes wrong. This is cool, but I don't want to use exceptions. No problem, see next.

Avoid exceptions

The RequiresConfig interface provides a method canRetrieveOptions(). This method checks if options are available depending on implemented interfaces and checks that the retrieved options are an array or have implemented \ArrayAccess.

canRetrieveOptions() returning true does not mean that options($config) will not throw an exception. It does however mean that options() will not throw an OptionNotFoundException. Mandatory options are not checked.

You can call this function and if it returns false, you can use the default options.

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

class MyAwesomeFactory implements RequiresConfigId, RequiresMandatoryOptions, ProvidesDefaultOptions
{
    use ConfigurationTrait;
    
    // other functions see above
    
    public function __invoke(ContainerInterface $container)
    {
        $config = $container->get('config');
        
        $options = [];
        
        if ($this->canRetrieveOptions($config, 'container-id')) {
            // get options for vendor-package.container-id
            // method options() is implemented in ConfigurationTrait
            // if host/port is missing, default options will be used
            $options = $this->options($config, 'container-id');
        } elseif ($this instanceof ProvidesDefaultOptions) {
            $options = $this->defaultOptions();
        }
        
        return new Awesome($options);
    }
}

Nice, is there a one-liner? Of course. You can use the optionsWithFallback() method. This function is not a part of the specification but is implemented in ConfigurationTrait to reduce some boilerplate code.

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

class MyAwesomeFactory implements RequiresConfigId, RequiresMandatoryOptions, ProvidesDefaultOptions
{
    use ConfigurationTrait;
    
    // other functions see above
    
    public function __invoke(ContainerInterface $container)
    {
        // get options for vendor-package.container-id
        // method options() is implemented in ConfigurationTrait
        // if configuration is not available, default options will be used
        $options = $this->optionsWithFallback($container->get('config'), 'container-id');
        
        return new Awesome($options);
    }
}

Using optionsWithFallback() method and the RequiresMandatoryOptions is ambiguous or? Yes, so it's up to you to implement the interfaces in a sense order.

Take a look at the examples section for more use cases. interop-config is universally applicable.

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