railt/container

View on GitHub
src/Container/SignatureResolver.php

Summary

Maintainability
A
30 mins
Test Coverage
<?php
/**
 * This file is part of Railt package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);

namespace Railt\Container;

use Railt\Container\Exception\ContainerInvocationException;
use Railt\Container\SignatureResolver\CallableArrayFetcher;
use Railt\Container\SignatureResolver\CallableFunctionFetcher;
use Railt\Container\SignatureResolver\CallableStaticMethodFetcher;
use Railt\Container\SignatureResolver\ClosureFetcher;
use Railt\Container\SignatureResolver\FetcherInterface;
use Railt\Container\SignatureResolver\InstanceMethodFetcher;
use Railt\Container\SignatureResolver\InvocableClassFetcher;
use Railt\Container\SignatureResolver\InvocableObjectFetcher;

/**
 * Class SignatureResolver
 */
class SignatureResolver
{
    /**
     * @var array|string[]|FetcherInterface[]
     */
    private const DEFAULT_FETCHER_CLASSES = [
        ClosureFetcher::class,
        CallableArrayFetcher::class,
        CallableFunctionFetcher::class,
        CallableStaticMethodFetcher::class,
        InstanceMethodFetcher::class,
        InvocableClassFetcher::class,
        InvocableObjectFetcher::class,
    ];

    /**
     * @var ContainerInterface
     */
    private $container;

    /**
     * @var array|FetcherInterface[]
     */
    private $fetchers = [];

    /**
     * SignatureResolver constructor.
     *
     * @param ContainerInterface|null $container
     */
    public function __construct(ContainerInterface $container = null)
    {
        $this->container = $container ?? new Container();

        $this->bootDefaultFetchers();
    }

    /**
     * @return void
     */
    private function bootDefaultFetchers(): void
    {
        foreach (self::DEFAULT_FETCHER_CLASSES as $class) {
            $this->register($class);
        }
    }

    /**
     * @param string|FetcherInterface $resolver
     */
    public function register(string $resolver): void
    {
        $this->fetchers[] = new $resolver($this->container);
    }

    /**
     * @param callable|mixed $signature
     * @return string|null
     * @throws \InvalidArgumentException
     */
    public function fetchClass($signature): ?string
    {
        if ($fetcher = $this->match($signature)) {
            return $fetcher->fetchClass($signature);
        }

        $error = 'Could not determine callable format of %s';
        throw new \InvalidArgumentException(\sprintf($error, static::signatureToString($signature)));
    }

    /**
     * @param mixed|callable $signature
     * @return FetcherInterface|null
     */
    public function match($signature): ?FetcherInterface
    {
        foreach ($this->fetchers as $fetcher) {
            if ($fetcher->match($signature)) {
                return $fetcher;
            }
        }

        return null;
    }

    /**
     * @param mixed $signature
     * @return string
     */
    public static function signatureToString($signature): string
    {
        $pattern = '%s %s';

        switch (true) {
            case $signature === null:
                return 'null value';

            case \is_object($signature):
                return \sprintf($pattern, 'object', \get_class($signature));

            case \is_string($signature):
                return \sprintf($pattern, 'string', '"' . \addcslashes($signature, '"') . '"');

            case \is_array($signature):
                $items = \array_map([static::class, 'signatureToString'], $signature);

                return '[ ' . \implode(', ', $items) . ' ]';

            default:
                return \sprintf($pattern, \strtolower(\gettype($signature)), $signature);
        }
    }

    /**
     * @param callable|mixed $signature
     * @return \Closure
     * @throws ContainerInvocationException
     */
    public function fetchAction($signature): \Closure
    {
        if ($fetcher = $this->match($signature)) {
            return $fetcher->fetchAction($signature);
        }

        $error = '%s is not allowed for invocations';
        throw new ContainerInvocationException(\sprintf($error, static::signatureToString($signature)));
    }
}