shrink0r/php-schema

View on GitHub
src/Builder.php

Summary

Maintainability
A
25 mins
Test Coverage
<?php

namespace Shrink0r\PhpSchema;

/**
 * Default implementation of the BuilderInterface.
 */
class Builder implements BuilderInterface, \ArrayAccess
{
    /**
     * @var mixed[] $data The builder's data
     */
    protected $data;

    /**
     * @var SchemaInterface $schema
     */
    protected $schema;

    /**
     * @param SchemaInterface $schema Schema to validate against when building
     */
    public function __construct(SchemaInterface $schema = null)
    {
        $this->data = [];
        $this->schema = $schema;
    }

    /**
     * {@inheritdoc}
     */
    public function build(array $defaults = [])
    {
        $builtConfig = $defaults;
        foreach ($this->data as $key => $value) {
            $builtConfig[$key] = $value instanceof BuilderInterface
                ? $value->build(isset($defaults[$key]) ? $defaults[$key] : [])->unwrap()
                : $value;
        }

        if (!$this->schema) {
            return Ok::unit($builtConfig);
        }

        $validationResult = $this->schema->validate($builtConfig);
        return $validationResult instanceof Error ? $validationResult : Ok::unit($builtConfig);
    }

    /**
     * {@inheritdoc}
     */
    public function valueOf($key)
    {
        return isset($this->data[$key]) ? $this->data[$key] : null;
    }

    /**
     * {@inheritdoc}
     */
    public function end()
    {
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function rewind()
    {
        return $this;
    }

    /**
     * Tells if the given key exists.
     *
     * @param string $key
     *
     * @return bool
     */
    public function offsetExists($key)
    {
        return isset($this->{$key});
    }

    /**
     * Navigate to the given key, creating it along the way if it does not yet exist.
     *
     * @param string $key
     *
     * @return BuilderInterface The nested BuilderInterface at $key
     */
    public function offsetGet($key)
    {
        return $this->{$key};
    }

    /**
     * Assign a given value to the given key.
     *
     * @param string $key
     * @param mixed $value
     */
    public function offsetSet($key, $value)
    {
        $this->{$key} = $value;
    }

    /**
     * Unset the given key.
     *
     * @param string $key
     */
    public function offsetUnset($key)
    {
        unset($this->{$key});
    }

    /**
     * Navigate to the given key, creating it along the way if it does not yet exist.
     *
     * @param string $key
     *
     * @return BuilderInterface Returns a new BuilderStack instance representing
     *                          the current access path.
     */
    public function __get($key)
    {
        if (!isset($this->data[$key])) {
            $this->data[$key] = new Builder;
        }

        if (!$this->data[$key] instanceof BuilderInterface) {
            throw new Exception("Can not access scalar value at '$key' with accessor. Use valueOf() instead");
        }

        $stackImplementor = $this->getStackImplementor();
        return new $stackImplementor([ $this, $this->data[$key] ]);
    }

    /**
     * Assign a given value to the given key. If an array is given it is
     * recursively converted to Builder objects. BuilderInterface instances
     * can be used to insert another Builder in the given location.
     * Changing the value from a child Builder to another value or other way
     * around is not possible.
     *
     * @param string $key
     * @param mixed $value
     */
    public function __set($key, $value)
    {
        if (is_array($value)) {
            $builder = new Builder;
            foreach ($value as $k => $v) {
                $builder->{$k} = $v;
            }
            $value = $builder;
        }

        if (isset($this->data[$key])) {
            $valueIsBuilder = $value instanceof BuilderInterface;
            $dataIsBuilder = $this->data[$key] instanceof BuilderInterface;
            if ($dataIsBuilder !== $valueIsBuilder) {
                throw new Exception("Trying to overwrite value at '$key' with incompatible data");
            }
        }
        $this->data[$key] = $value;
    }

    /**
     * Tells if the given key exists.
     *
     * @param string $key
     * @return bool
     */
    public function __isset($key)
    {
        return isset($this->data[$key]);
    }

    /**
     * Unset the given key.
     *
     * @param string $key
     */
    public function __unset($key)
    {
        unset($this->data[$key]);
    }

    /**
     * Assign a given value to the given key relative to the builder's current position.
     * Does not rewind the builder and returns self, so fluent assignment of values is possible.
     *
     * @param string $key
     * @param mixed $args
     *
     * @return BuilderInterface Returns self
     */
    public function __call($key, array $args = [])
    {
        if (count($args) !== 0) {
            $this->{$key} = $args[0];
        }

        return $this;
    }

    /**
     * Specify the stack implementor to use
     *
     * @return string
     */
    protected function getStackImplementor()
    {
        return BuilderStack::CLASS;
    }
}