src/Util/ContainerTrait.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

/**
 * Phoole (PHP7.2+)
 *
 * @category  Library
 * @package   Phoole\Di
 * @copyright Copyright (c) 2019 Hong Zhang
 */
declare(strict_types=1);

namespace Phoole\Di\Util;

use Phoole\Config\ConfigAwareTrait;
use Phoole\Di\Exception\LogicException;
use Phoole\Di\Exception\UnresolvedClassException;

/**
 * ContainerTrait
 *
 * @package Phoole\Di
 */
trait ContainerTrait
{
    use ResolveTrait;
    use FactoryTrait;
    use ConfigAwareTrait;

    /**
     * service objects stored by its service id
     *
     * @var object[]
     */
    protected $objects = [];

    /**
     * DI definition prefix in $config
     *
     * @var string
     */
    protected $prefix = 'di.';

    /**
     * Get the instance by SERVICE id, create it if not yet
     *
     * @param  string $id  defined service id
     * @return object
     * @throws UnresolvedClassException  if dependencies unresolved
     * @throws LogicException if 'di.before' or 'di.after' definitions go wrong
     */
    protected function getInstance(string $id): object
    {
        // found in cache
        if (isset($this->objects[$id])) {
            return $this->objects[$id];
        }

        // initiate object
        $object = $this->newInstance($this->getServiceDefinition($id));

        // if '@' at the end, return without store in cache
        if ('@' === substr($id, -1)) {
            return $object;
        }

        // store in object cache
        $this->objects[$id] = $object;

        // store object in classmap
        if (FALSE === strpos($id, '@')) {
            $this->storeClass($object);
        }

        return $object;
    }

    /**
     * initiate an object by its definition
     *
     * @param  array $definition
     * @return object
     * @throws UnresolvedClassException if dependencies unresolved
     * @throws LogicException if 'di.before' or 'di.after' definitions go wrong
     */
    public function newInstance(array $definition): object
    {
        // execute global beforehand 'di.before' methods
        $this->executeMethods($definition, 'before');

        // fabricate this object
        $obj = $this->fabricate($definition);

        // execute global aftermath ('di.after') methods
        $this->executeMethods($obj, 'after');

        return $obj;
    }

    /**
     * get the raw service id by adding prefix & stripping the scope '@XXX' off
     *
     * @param  string $id
     * @return string
     */
    protected function getRawId(string $id): string
    {
        // 'di.service.' prefix
        $prefix = $this->prefix . 'service.';

        // cutoff the scope suffix
        return $prefix . explode('@', $id, 2)[0];
    }

    /**
     * execute 'di.before' or 'di.after' methods for newly created object
     *
     * @param  object|array $object  newly created object or object definition
     * @param  string       $stage   'before' | 'after'
     * @return void
     * @throws LogicException if 'di.before' or 'di.after' definitions go wrong
     */
    protected function executeMethods($object, string $stage): void
    {
        // 'di.before' or 'di.after'
        $node = $this->prefix . $stage;
        if ($this->getConfig()->has($node)) {
            foreach ($this->getConfig()->get($node) as $line) {
                list($runner, $arguments) = $this->fixMethod((array) $line, $object);
                $this->executeCallable($runner, $arguments);
            }
        }
    }

    /**
     * A service defined in the definitions ?
     *
     * @param  string $id
     * @return bool
     */
    protected function hasDefinition(string $id): bool
    {
        return $this->getConfig()->has($this->getRawId($id)) || isset($this->objects[$id]);
    }

    /**
     * for reserving 'container' or 'config'
     *
     * @param  string $id
     * @param  object $object
     * @return $this
     */
    protected function reserveObject(string $id, object $object)
    {
        $this->objects[$id] = $object;
        $this->classMap[\get_class($object)] = $object;
        return $this;
    }

    /**
     * Find the service definition and fix any non-standard stuff
     *
     * @param  string $id
     * @return array
     */
    protected function getServiceDefinition(string $id): array
    {
        $def = $this->getConfig()->get($this->getRawId($id));
        return $this->fixDefinition($def);
    }
}