src/Resolver.php
<?php declare(strict_types=1);
namespace Acelot\Resolver;
use Acelot\Resolver\Definition\FactoryDefinition;
use Acelot\Resolver\Definition\ObjectDefinition;
use Acelot\Resolver\Exception\DefinitionException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
/**
* @example
*
* $resolver = new Resolver([
* LoggerInterface::class => ClassDefinition::define(LoggerFactory::class),
* Database::class => ClassDefinition::define(MongoDbFactory::class)
* ]);
*
*/
class Resolver implements ResolverInterface, InvokerInterface, ContainerInterface
{
private const ALIASES = [
Resolver::class,
ContainerInterface::class,
ResolverInterface::class,
InvokerInterface::class,
];
/**
* @var array<string, DefinitionInterface>
*/
protected $definitions;
/**
* @var array
*/
protected $shared = [];
/**
* @param array<string, DefinitionInterface|mixed> $definitions Definitions mapping
*
* @return Resolver
*/
public static function create(array $definitions = []): Resolver
{
return new Resolver($definitions);
}
/**
* @param array<string, DefinitionInterface|mixed> $definitions Definitions mapping
*/
public function __construct(array $definitions = [])
{
foreach ($definitions as $fqcn => $definition) {
if (!$definition instanceof DefinitionInterface) {
throw new \InvalidArgumentException(
sprintf('Definition of "%s" must implement DefinitionInterface', $fqcn)
);
}
}
$this->definitions = $definitions;
}
/**
* Returns all bound definitions.
*
* @return array<string, DefinitionInterface>
*/
public function getDefinitions(): array
{
return $this->definitions;
}
/**
* Binds the class name to definition. Immutable.
*
* @param string $fqcn Fully qualified class name
* @param DefinitionInterface $definition Definition
*
* @return Resolver
*/
public function withDefinition(string $fqcn, DefinitionInterface $definition): Resolver
{
$clone = clone $this;
$clone->definitions[$fqcn] = $definition;
return $clone;
}
/**
* Unbinds the definition by class name. Immutable.
*
* @param string $fqcn Fully qualified class name
*
* @return Resolver
*/
public function withoutDefinition(string $fqcn): Resolver
{
$clone = clone $this;
unset($clone->definitions[$fqcn]);
return $clone;
}
/**
* Resolves and returns the instance of the class.
*
* @param string $fqcn Fully qualified class name
*
* @return object
* @throws DefinitionException
*/
public function resolve(string $fqcn)
{
// Resolver aliases
if (in_array($fqcn, self::ALIASES)) {
return $this;
}
// Search class in shared
if (array_key_exists($fqcn, $this->shared)) {
return $this->shared[$fqcn];
}
// Search definition in predefined definition, otherwise use ObjectDefinition
if (array_key_exists($fqcn, $this->definitions)) {
$definition = $this->definitions[$fqcn];
} else {
$definition = ObjectDefinition::define($fqcn);
}
// Resolving
$result = $definition->resolve($this);
// Sharing result between calls
if ($definition->isShared()) {
$this->shared[$fqcn] = $result;
}
return $result;
}
/**
* Invoke a callable.
*
* @param callable $callable Callable
* @param array $args Arguments
*
* @return mixed
* @throws Exception\ResolverException
*/
public function invoke(callable $callable, array $args = [])
{
return FactoryDefinition::define($callable)
->withArguments($args)
->resolve($this);
}
/**
* @param string $id
*
* @return mixed|object
*/
public function get(string $id)
{
return $this->resolve($id);
}
/**
* @param string $id
*
* @return bool
*/
public function has(string $id)
{
return true;
}
/**
* Removes the resolved object from shared items.
* This is useful when you want the object to be re-resolved.
*
* @param string $fqcn
*/
public function unshare(string $fqcn): void
{
unset($this->shared[$fqcn]);
}
/**
* Clear all the resolved objects from shared items.
* @see `removeShared` method
*/
public function unshareAll(): void
{
$this->shared = [];
}
}