PHP - Create your own PHP Options Resolver ( Like Symfony )
Tags: php, laravel, codequality, tutorial
Published: 2021-06-13 11:54:33

## Options Resolver: A Simple Tool for Processing and Validating Option Arrays

**Required PHP Version: 7.3 and above**

Now, let's create an `OptionsResolver.php` file to house the resolver logic.
```php
<?php

declare(strict_types=1);

namespace DevCoder\Resolver;

final class OptionsResolver
{
    /**
     * @var \ArrayObject<Option>
     */
    private $options;

    public function __construct(array $options)
    {
        $this->options = new \ArrayObject();
        foreach ($options as $option) {
            $this->add($option);
        }
    }

    public function resolve(array $options): array
    {
        $this->checkDiff($options);

        /**
         * @var Option $option
         */
        $optionsResolved = [];
        foreach ($this->options as $option) {
            $optionName = $option->getName();
            if (\array_key_exists($optionName, $options)) {
                $value = $options[$optionName];
                if ($option->isValid($value) === false) {
                    throw new \InvalidArgumentException(\sprintf('The option "%s" with value %s is invalid.', $optionName, self::formatValue($value)));
                }
                $optionsResolved[$optionName] = $value;
                continue;
            }

            if ($option->hasDefaultValue()) {
                $optionsResolved[$optionName] = $option->getDefaultValue();
                continue;
            }

            throw new \InvalidArgumentException(\sprintf(
                'The required option "%s" is missing.', $optionName)
            );
        }
        return $optionsResolved;
    }

    private function add(Option $option): self
    {
        $this->options->offsetSet($option->getName(), $option);
        return $this;
    }

    private function checkDiff(array $options): void
    {
        $defined = $this->options->getArrayCopy();
        $diff = \array_diff_key($options, $defined);
        if (\count($diff) > 0) {
            throw new \InvalidArgumentException(\sprintf(
                    'The option(s) "%s" do(es) not exist. Defined options are: "%s".',
                    \implode(', ', \array_keys($diff)),
                    \implode('", "', \array_keys($defined)))
            );
        }
    }

    private static function formatValue($value): string
    {
        if (\is_object($value)) {
            return \get_class($value);
        }

        if (\is_string($value)) {
            return '"' . $value . '"';
        }

        if (false === $value) {
            return 'false';
        }

        if (true === $value) {
            return 'true';
        }

        return \gettype($value);
    }
}
```
### Option Definition: Creating the Option.php File for Defining an Option

The Option.php file below allows you to define an option with properties such as the name, default value, presence of a default value, a validator, and more.

```php
<?php

declare(strict_types=1);

namespace DevCoder\Resolver;

final class Option
{
    /**
     * @var string
     */
    private $name;

    /**
     * @var mixed
     */
    private $defaultValue;

    /**
     * @var bool
     */
    private $hasDefaultValue = false;

    /**
     * @var \Closure|null
     */
    private $validator;

    /**
     * Option constructor.
     * @param string $name
     */
    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getDefaultValue()
    {
        return $this->defaultValue;
    }

    /**
     * @param mixed $defaultValue
     * @return Option
     */
    public function setDefaultValue($defaultValue): self
    {
        $this->hasDefaultValue = true;
        $this->defaultValue = $defaultValue;
        return $this;
    }

    public function hasDefaultValue(): bool
    {
        return $this->hasDefaultValue;
    }

    public function validator(\Closure $closure): self
    {
        $this->validator = $closure;
        return $this;
    }

    public function isValid($value): bool
    {
        if ($this->validator instanceof \Closure) {
            $validator = $this->validator;
            return $validator($value);
        }
        return true;
    }
}
```
### How to Use?

To use this Options Resolver, follow these steps:

**1. Define Required Options:**
```php
<?php

use DevCoder\Resolver\Option;
use DevCoder\Resolver\OptionsResolver;

class Database
{

    public function __construct(array $options = [])
    {
        $resolver = new OptionsResolver([
            new Option('host'),
            new Option('username'),
            new Option('password'),
            new Option('dbname'),
        ]);

        $this->options = $resolver->resolve($options);
    }
}

$database = new Database([
    'host' => 'localhost',
    'dbname' => 'app',
]);
// Uncaught InvalidArgumentException: The required option "username" is missing.

$database = new Database([
    'host' => 'localhost',
    'dbname' => 'app',
    'username' => 'root',
    'password' => 'root',
]);
// OK
```

**2. Define default options**
```php
<?php

use DevCoder\Resolver\Option;
use DevCoder\Resolver\OptionsResolver;

class Database
{
    public function __construct(array $options = [])
    {
        $resolver = new OptionsResolver([
            (new Option('host'))->setDefaultValue('localhost'),
            (new Option('username'))->setDefaultValue('root'),
            (new Option('password'))->setDefaultValue('root'),
            (new Option('dbname'))->setDefaultValue('app'),
        ]);

        /**
         * array(4) {
         * ["host"]=>
         * string(9) "localhost"
         * ["username"]=>
         * string(4) "root"
         * ["password"]=>
         * string(4) "root"
         * ["dbname"]=>
         * string(3) "app"
         * }
         */
        $this->options = $resolver->resolve($options);
    }
}

$database = new Database([]);
// OK
```
```php
<?php

use DevCoder\Resolver\Option;
use DevCoder\Resolver\OptionsResolver;

class Database
{
    public function __construct(array $options = [])
    {
        $resolver = new OptionsResolver([
            (new Option('host'))->setDefaultValue('localhost'),
            (new Option('username'))->setDefaultValue('root'),
            (new Option('password'))->setDefaultValue('root'),
            (new Option('dbname'))->setDefaultValue('app'),
        ]);

        /**
         * array(4) {
         * ["host"]=>
         * string(9) "localhost"
         * ["username"]=>
         * string(4) "root"
         * ["password"]=>
         * string(4) "root"
         * ["dbname"]=>
         * string(3) "app-2"
         * }
         */
        $this->options = $resolver->resolve($options);
    }
}

$database = new Database([
    'dbname' => 'app-2'
]);
// OK
```

**3. Non-existent options**
```php
<?php

use DevCoder\Resolver\Option;
use DevCoder\Resolver\OptionsResolver;

class Database
{
    public function __construct(array $options = [])
    {
        $resolver = new OptionsResolver([
            (new Option('host'))->setDefaultValue('localhost'),
            (new Option('username'))->setDefaultValue('root'),
            (new Option('password'))->setDefaultValue('root'),
            (new Option('dbname'))->setDefaultValue('app'),
        ]);

        $this->options = $resolver->resolve($options);
    }
}

$database = new Database([
    'url' => 'mysql://root:root@localhost/app',
]);
// Uncaught InvalidArgumentException: The option(s) "url" do(es) not exist. Defined options are: "host", "username", "password", "dbname"
```
**4. Validate options values**
```php
<?php

use DevCoder\Resolver\Option;
use DevCoder\Resolver\OptionsResolver;

class Database
{
    public function __construct(array $options = [])
    {
        $resolver = new OptionsResolver([
            (new Option('host'))
                ->validator(static function($value) {
                    return is_string($value);
                })
                ->setDefaultValue('localhost'),
            (new Option('username'))
                ->validator(static function($value) {
                    return is_string($value);
                })
                ->setDefaultValue('root')
            ,
            (new Option('password'))
                ->validator(static function($value) {
                    return is_string($value);
                })
                ->setDefaultValue('root'),
            (new Option('dbname'))
                ->validator(static function($value) {
                    return is_string($value);
                })
                ->setDefaultValue('app'),
            (new Option('driver'))
                ->validator(static function($value) {
                    return in_array($value, ['pdo_mysql', 'pdo_pgsql']);
                })
                ->setDefaultValue('pdo_mysql'),
        ]);

        $this->options = $resolver->resolve($options);
    }
}

$database = new Database([
    'host' => '192.168.1.200',
    'username' => 'root',
    'password' => 'root',
    'dbname' => 'my-app',
    'driver' => 'pdo_sqlite'
]);
// Uncaught InvalidArgumentException: The option "driver" with value "pdo_sqlite" is invalid.
```

Ideal for small project
Simple and easy!
[https://github.com/devcoder-xyz/php-options-resolver](https://github.com/devcoder-xyz/php-options-resolver)