Skip to content

Commit

Permalink
Merge 99e7963 into 1b3319d
Browse files Browse the repository at this point in the history
  • Loading branch information
feryardiant committed Jul 8, 2020
2 parents 1b3319d + 99e7963 commit 9c87a97
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 126 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

strategy:
matrix:
php-versions: ['7.1', '7.2', '7.3', '7.4']
php-versions: ['7.1', '7.2', '7.3', '7.4', '8.0']

steps:
- name: Checkout
Expand All @@ -36,7 +36,7 @@ jobs:
uses: shivammathur/setup-php@master
with:
php-version: ${{ matrix.php-versions }}
coverage: xdebug
coverage: none

- name: Get Composer cache directory
id: composer-cache
Expand All @@ -61,7 +61,7 @@ jobs:
run: echo "::set-env name=GIT_BRANCH::$GITHUB_HEAD_REF"

- name: Spec
run: composer spec
run: composer lint && phpdbg -qrr ./vendor/bin/kahlan --config=test/config.php

- name: CodeClimate
env:
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"lint": "phpcs --standard=PSR12 -n -p src"
},
"require": {
"php": "^7.1",
"php": ">=7.1",
"psr/container": "^1.0"
},
"provide": {
Expand Down
4 changes: 2 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ public function get($id)
return $this->resolved[$id] = $this->resolver->handle($instance);
}

if (is_string($instance) && $this->has($instance)) {
return $this->get($instance);
}

return $instance;
}

Expand Down
80 changes: 50 additions & 30 deletions src/Container/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Resolver implements ContainerAwareInterface
*
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
final public function __construct(ContainerInterface $container)
{
$this->setContainer($container);
}
Expand All @@ -41,13 +41,7 @@ public function handle($instance, array $args = [])
: new \ReflectionFunction($instance);

if ($isMethod) {
$obj = is_object($instance[0]) ? $instance[0] : null;

if (! $reflector->isStatic() && is_string($instance[0])) {
$obj = $this->createInstance($instance[0]);
}

$params[] = $obj;
$params[] = is_object($instance[0]) ? $instance[0] : null;
}

// If it was internal method resolve its params as a closure.
Expand All @@ -66,30 +60,31 @@ public function handle($instance, array $args = [])
*
* @param string|object|callable|\Closure $toResolve
* @return object
* @throws Exception When $toResolve is neither string of class name, instance
* of \Closure, object of class nor a callable.
* @throws UnresolvableException
*/
public function resolve($toResolve)
{
if (is_string($toResolve) && class_exists($toResolve)) {
return $this->createInstance($toResolve);
}
if (is_string($toResolve) && ! function_exists($toResolve)) {
if (false === strpos($toResolve, '::')) {
return $this->createInstance($toResolve);
}

if (
(is_string($toResolve) && $this->getContainer()->has($toResolve)) ||
($toResolve instanceof \Closure || is_callable($toResolve))
) {
return $toResolve;
$toResolve = explode('::', $toResolve);
}

if (is_object($toResolve)) {
return $this->injectContainer($toResolve);
return $toResolve instanceof \Closure ? $toResolve : $this->injectContainer($toResolve);
}

throw new Exception(sprintf(
'Couldn\'t resolve "%s" as an instance.',
! is_string($toResolve) ? gettype($toResolve) : $toResolve
));
try {
if ($this->assertCallable($toResolve)) {
return $toResolve;
}
} catch (UnresolvableException $err) {
// do nothing
}

throw new UnresolvableException($toResolve);
}

/**
Expand All @@ -105,7 +100,11 @@ protected function createInstance(string $className)
return $this->getContainer($className);
}

$reflector = new \ReflectionClass($className);
try {
$reflector = new \ReflectionClass($className);
} catch (\ReflectionException $err) {
throw new UnresolvableException($className, $err);
}

if (! $reflector->isInstantiable()) {
throw new Exception(sprintf('Target "%s" is not instantiable.', $className));
Expand All @@ -126,27 +125,42 @@ protected function createInstance(string $className)
protected function resolveArgs(\ReflectionFunctionAbstract $callable, array $args = []): array
{
foreach ($callable->getParameters() as $param) {
$position = $param->getPosition();

// Just skip if parameter already provided.
if (array_key_exists($param->getPosition(), $args)) {
if (array_key_exists($position, $args)) {
continue;
}

try {
$args[$param->getPosition()] = $this->getContainer(
($class = $param->getClass()) ? $class->getName() : $param->getName()
$args[$position] = $this->getContainer(
$this->getArgsType($param)->getName()
);
} catch (NotFoundException $e) {
if (! $param->isOptional()) {
throw $e;
}

$args[$param->getPosition()] = $param->getDefaultValue();
$args[$position] = $param->getDefaultValue();
}
}

return $args;
}

/**
* Determine parameter type.
*
* @param \ReflectionParameter $param
* @return \ReflectionParameter
*/
private function getArgsType(\ReflectionParameter $param)
{
$type = $param->getType();

return $type && ! $type->isBuiltin() ? $type : $param;
}

/**
* Assert $instance is callable.
*
Expand All @@ -163,8 +177,14 @@ private function assertCallable(&$instance): bool
$instance = [$instance, '__invoke'];
}

if (is_array($instance) && ! method_exists($instance[0], $instance[1])) {
throw new BadMethodCallException($instance);
if (is_array($instance)) {
if (is_string($instance[0])) {
$instance[0] = $this->createInstance($instance[0]);
}

if (! method_exists(...$instance)) {
throw new BadMethodCallException($instance);
}
}

return is_callable($instance);
Expand Down
24 changes: 24 additions & 0 deletions src/Container/UnresolvableException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Projek\Container;

class UnresolvableException extends Exception
{
public function __construct($toResolve, ?\Throwable $prev = null)
{
parent::__construct(sprintf('Couldn\'t resolve %s.', $this->getTypeString($toResolve)), 0, $prev);
}

private function getTypeString($toResolve)
{
if (is_string($toResolve)) {
return 'string: ' . $toResolve;
} elseif (is_array($toResolve)) {
return 'array: [' . join(', ', $toResolve) . ']';
}

return 'type: ' . gettype($toResolve);
}
}
6 changes: 3 additions & 3 deletions test/spec/ArrayContainer.spec.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

use Projek\Container;
use Projek\Container\{ ArrayContainer, Exception };
use Projek\Container\{ArrayContainer, Exception, UnresolvableException};
use Stubs\AbstractFoo;
use function Kahlan\describe;
use function Kahlan\expect;
Expand Down Expand Up @@ -29,10 +29,10 @@

expect(function () {
$this->c['foo'] = ['foo', 'bar'];
})->toThrow(new Exception('Couldn\'t resolve "array" as an instance.'));
})->toThrow(new UnresolvableException(['foo', 'bar']));

expect(function () {
$this->c['foo'] = null;
})->toThrow(new Exception('Couldn\'t resolve "NULL" as an instance.'));
})->toThrow(new UnresolvableException(null));
});
});
11 changes: 7 additions & 4 deletions test/spec/Container.spec.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

use Projek\Container;
use Projek\Container\{BadMethodCallException, ContainerInterface, Exception, InvalidArgumentException, NotFoundException, RangeException};
use Projek\Container\{BadMethodCallException, ContainerInterface, Exception, InvalidArgumentException, NotFoundException, RangeException, UnresolvableException};
use Psr\Container\ContainerInterface as PsrContainer;
use Stubs\{Dummy, AbstractFoo, ConcreteBar, ServiceProvider};
use function Kahlan\describe;
Expand Down Expand Up @@ -114,15 +114,15 @@

expect(function () {
$this->c->set('foo', 'NotExistsClass');
})->toThrow(new Exception('Couldn\'t resolve "NotExistsClass" as an instance.'));
})->toThrow(new UnresolvableException('NotExistsClass'));

expect(function () {
$this->c->set('foo', ['foo', 'bar']);
})->toThrow(new Exception('Couldn\'t resolve "array" as an instance.'));
})->toThrow(new UnresolvableException(['foo', 'bar']));

expect(function () {
$this->c->set('foo', null);
})->toThrow(new Exception('Couldn\'t resolve "NULL" as an instance.'));
})->toThrow(new UnresolvableException(null));
});

it('Should make an instance without adding to the stack', function () {
Expand Down Expand Up @@ -269,6 +269,9 @@
});

it('Should be able to invoke a method directly without condition', function () {
// required by the ConcreteBar
$this->c->set('dummy', Dummy::class);

expect(
// Resolve and handle non-static method like a static method
$this->c->make('Stubs\SomeClass::shouldCalled', ['new value'])
Expand Down
6 changes: 3 additions & 3 deletions test/spec/PropertyContainer.spec.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

use Projek\Container;
use Projek\Container\{ Exception, PropertyContainer };
use Projek\Container\{Exception, PropertyContainer, UnresolvableException};
use Stubs\AbstractFoo;
use function Kahlan\describe;
use function Kahlan\expect;
Expand Down Expand Up @@ -29,10 +29,10 @@

expect(function () {
$this->c->foo = ['foo', 'bar'];
})->toThrow(new Exception('Couldn\'t resolve "array" as an instance.'));
})->toThrow(new UnresolvableException(['foo', 'bar']));

expect(function () {
$this->c->foo = null;
})->toThrow(new Exception('Couldn\'t resolve "NULL" as an instance.'));
})->toThrow(new UnresolvableException(null));
});
});

0 comments on commit 9c87a97

Please sign in to comment.