eric-chau/jarvis

View on GitHub
src/Skill/Core/CallbackResolver.php

Summary

Maintainability
A
55 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Jarvis\Skill\Core;

use Jarvis\Jarvis;
use Jarvis\Skill\DependencyInjection\Reference;

/**
 * @author Eric Chau <eriic.chau@gmail.com>
 */
class CallbackResolver
{
    /**
     * @var Jarvis
     */
    private $app;

    public function __construct(Jarvis $app)
    {
        $this->app = $app;
    }

    /**
     * Resolves and replaces Reference by the parameter/service from Jarvis's
     * dependency injection container.
     *
     * @param  mixed $callback
     * @return mixed
     */
    public function resolveReference($callback)
    {
        if ($callback instanceof Reference) {
            $callback = $this->app[(string) $callback] ?? $callback;
        } elseif (is_array($callback) && $callback[0] instanceof Reference) {
            $callback[0] = $this->app[(string) $callback[0]] ?? $callback[0];
        }

        return $callback;
    }

    /**
     * Resolves and replaces placeholders references by the parameter from Jarvis's
     * dependency injection container.
     *
     * @param  mixed $callback
     * @return \Closure
     */
    public function toClosure($callback): \Closure
    {
        if ($callback instanceof \Closure) {
            return $callback;
        }

        return \Closure::fromCallable($this->resolveReference($callback));
    }

    /**
     * Resolves and returns an array of arguments according to the given closure.
     * This method can also smartly type hint and find the right object to match
     * callback requested arguments.
     *
     * @param  \Closure $callback
     * @param  array    $rawArgs
     * @return array
     */
    public function resolveArgumentsForClosure(\Closure $callback, array $rawArgs = []): array
    {
        $result = [];
        $refMethod = new \ReflectionMethod($callback, '__invoke');
        foreach ($refMethod->getParameters() as $refParam) {
            if (null !== $refClass = $refParam->getClass()) {
                if (isset($this->app[$refClass->name])) {
                    $result[$refParam->getPosition()] = $this->app[$refClass->name];

                    continue;
                }
            }

            if (in_array($refParam->name, array_keys($rawArgs))) {
                $result[$refParam->getPosition()] = $rawArgs[$refParam->name];
            }
        }

        return $result;
    }

    /**
     * Shortcut that calls successively ::toClosure(), ::resolveArgumentsForClosure(),
     * call_user_func_array() and returns the result.
     *
     * @param  mixed $callback
     * @param  array  $rawArgs
     * @return mixed
     */
    public function resolveAndCall($callback, array $rawArgs = [])
    {
        $closure = $this->toClosure($callback);

        return call_user_func_array(
            $closure,
            $this->resolveArgumentsForClosure($closure, $rawArgs)
        );
    }
}