sebastianthiel/DI

View on GitHub
src/DI/DI.php

Summary

Maintainability
A
35 mins
Test Coverage
<?php
/**
 * sebastianthiel DI
 *
 * @package    sebastianthiel/DI
 * @author     Sebastian Thiel <me@sebastian-thiel.eu>
 * @license    https://opensource.org/licenses/MIT  MIT
 * @version    0.1
 */

declare(strict_types=1);

namespace sebastianthiel\DI;

use sebastianthiel\DI\Definition\Definition;
use sebastianthiel\DI\Definition\DefinitionInterface;
use sebastianthiel\DI\Exceptions\CircularException;
use sebastianthiel\DI\Exceptions\InvalidArgumentException;
use sebastianthiel\DI\Resolver\Resolver;
use sebastianthiel\DI\Resolver\ResolverInterface;

/**
 * Class DI
 */
class DI implements DIInterface
{
    protected $Resolver;

    /** @var string[] */
    protected $processing = [];

    /** @var Definition[] */
    protected $definitions = [];

    public function __construct()
    {
        $this->Resolver = new Resolver();
        $this->Resolver->setDDI($this);

        $this->setInstance(static::class, $this);
        $this->setAlias(DIInterface::class, static::class);
    }

    /**************************/
    /*    Setter Functions   */
    /*************************/

    /**
     * add new definition to DI
     *
     * @param string $classId has to be a valid class or interface name
     * @param mixed  $value
     */
    public function set(string $classId, $value) : void
    {
        $this->addNewDefinition($classId, $value);
    }

    /**
     * add a new alias definition
     *
     * @param string $sourceClassId source class / interface name
     * @param string $targetClassId target class name
     */
    public function setAlias(string $sourceClassId, string $targetClassId) : void
    {
        $this->addNewDefinition($sourceClassId, $targetClassId, DefinitionInterface::TYPE_ALIAS);
    }

    /**
     * add a new factory definition
     *
     * @param string          $classId  class / interface name
     * @param callable|string $callable function / function name used as factory method
     */
    public function setFactory(string $classId, $callable) : void
    {
        $this->addNewDefinition($classId, $callable, DefinitionInterface::TYPE_FACTORY);
    }

    /**
     * @param string $classId
     * @param        $class
     */
    public function setInstance(string $classId, $class) : void
    {
        $this->addNewDefinition($classId, $class, DefinitionInterface::TYPE_INSTANCE);
    }

    /**
     * create a new definition object and add it to the list
     *
     * @param          $classId
     * @param          $definition
     * @param int|null $type
     */
    protected function addNewDefinition($classId, $definition, $type = null) : void
    {
        $this->remove($classId);

        $this->definitions[$classId] = new Definition($classId, $definition, $type, $this->Resolver);
    }

    /**************************/
    /*    Getter Functions   */
    /*************************/

    /**
     * get an object from DI
     *
     * @param string $classId
     * @param array  $arguments constructor arguments
     *
     * @return object
     */
    public function get($classId, array $arguments = [])
    {
        if (isset($this->processing[$classId])) {
            throw new CircularException(sprintf("Circular dependency detected. Stacktrace:\n%s", implode("\n", $this->processing)));
        }

        $result = null;
        $this->processing[$classId] = true;

        if (isset($this->definitions[$classId])) {
            $type = $this->definitions[$classId]->getType();
            switch ($type) {
                case DefinitionInterface::TYPE_ALIAS:
                    $result = $this->get($this->definitions[$classId]->getDefinition(), $arguments);
                    break;
                case DefinitionInterface::TYPE_FACTORY:
                    $result = $this->invoke($this->definitions[$classId]->getDefinition(), $arguments);
                    break;
                case DefinitionInterface::TYPE_INSTANCE:
                    $result = $this->definitions[$classId]->getDefinition();
                    break;
                default:
            }
        }
        if (!$result) {
            $result = $this->Resolver->getInstance($classId, $arguments);
        }

        unset($this->processing[$classId]);

        return $result;
    }

    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $classId Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has($classId) : bool
    {
        return class_exists($classId) || (interface_exists($classId) && isset($this->definitions[$classId]));
    }

    /**
     * Returns true if the container has a definition for the given identifier
     *
     * `hasDefinition($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $classId Identifier of the entry to look for.
     *
     * @return bool
     */
    public function hasDefinition(string $classId) : bool
    {
        return isset($this->definitions[$classId]);
    }

    /**
     * completely remove an item from the DI
     *
     * @param string $classId
     */
    public function remove(string $classId) : void
    {
        if (isset($this->definitions[$classId])) {
            unset($this->definitions[$classId]);
        }
    }

    public function invoke($callable, array $arguments = [], $object = null)
    {

        return $this->Resolver->invoke($callable, $arguments, $object);
    }

    /**
     * Whether a offset exists
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetexists.php
     *
     * @param mixed $offset <p>
     *                      An offset to check for.
     *                      </p>
     *
     * @return boolean true on success or false on failure.
     * </p>
     * <p>
     * The return value will be casted to boolean if non-boolean was returned.
     * @since 5.0.0
     */
    public function offsetExists($offset)
    {
        return $this->has($offset);
    }

    /**
     * Offset to retrieve
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetget.php
     *
     * @param mixed $offset <p>
     *                      The offset to retrieve.
     *                      </p>
     *
     * @return mixed Can return all value types.
     * @since 5.0.0
     */
    public function offsetGet($offset)
    {
        return $this->get($offset);
    }


    /**
     * Offset to set
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetset.php
     *
     * @param mixed $offset <p>
     *                      The offset to assign the value to.
     *                      </p>
     * @param mixed $value  <p>
     *                      The value to set.
     *                      </p>
     *
     * @return void
     * @since 5.0.0
     */
    public function offsetSet($offset, $value)
    {
        $this->set($offset, $value);
    }

    /**
     * Offset to unset
     *
     * @link  http://php.net/manual/en/arrayaccess.offsetunset.php
     *
     * @param mixed $offset <p>
     *                      The offset to unset.
     *                      </p>
     *
     * @return void
     * @since 5.0.0
     */
    public function offsetUnset($offset)
    {
        $this->remove($offset);
    }
}