Skip to content

Commit

Permalink
PSR-11 support
Browse files Browse the repository at this point in the history
  • Loading branch information
ppetermann committed Aug 25, 2018
1 parent 95c6505 commit c6f8d0b
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 47 deletions.
2 changes: 1 addition & 1 deletion LICENSE.md
@@ -1,5 +1,5 @@
MIT License
Copyright (c) 2015 Peter Petermann
Copyright (c) 2015 - 2018 Peter Petermann

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
Expand Down
27 changes: 19 additions & 8 deletions README.md
Expand Up @@ -5,7 +5,7 @@
[![Code Coverage](https://scrutinizer-ci.com/g/ppetermann/king23-di/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/ppetermann/king23-di/?branch=master)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/f6c34f05-4105-43f1-b84e-b27376f58106/mini.png)](https://insight.sensiolabs.com/projects/f6c34f05-4105-43f1-b84e-b27376f58106)
# King23/DI, a dependency injection lib for King23
Copyright (C) 2015 by Peter Petermann
Copyright (C) 2015 - 2018 by Peter Petermann
All rights reserved.

## LICENSE
Expand All @@ -18,6 +18,11 @@ for further information

## USAGE

### READTHIS
This DI container implements Psr-11, and can be used in the way Psr-11 describes it, however there are two things you should be aware of:
* Psr-11 states that callers shouldn't assume that the structure of the string carries semantic meaning. However it is **recommended** to use the interface that describes a service to register and retrieve services when using King23\DI, this is generally a good practise as it avoids naming confusions, and specially a good idea with this container, as that allows its auto-wiring through type-hinting to take care of injecting the right services, so you don't have to create descriptions for every single class you want to use this with.
* Psr-11's has method is supposed to return if an id is registered with the DI Container, for the way the auto-wiring works, as well as the using the interfaces (see above) the behavior is slightly different here - King23\DI will also check if the id handed over is an existing class, which will be loaded then, so it will return true if the identifier exists, or if it doesn't it will check if itcan auto-instantiate the handed class/interface name, assuming thats what the identifier is.

### Installation
install using composer:

Expand All @@ -26,18 +31,23 @@ install using composer:

**Hint**: King23/DI follows [Semantic Versioning v2.0.0](http://semver.org/spec/v2.0.0.html), so you can set your version in composer to something like "~1.0"

## Usage
Basically the container offers 3 Methods:
### Usage
Basically the container offers 4 Methods:

**Hint**: King23\DI uses $classname, as the parameter name, however this is equivalent to Psr-11's $id

- void register(string $interface, callable $implementation)
- void registerFactory(string $interface, callable $implementation)
- mixed getInstanceOf(string $classname)
- mixed get(string $classname)
- bool has(string $classname)

**register($interface, $implementation)** is used to register dependencies for injection, while $interface can be any string in theory, it is meant to hold the name of an interface. The callable is supposed to be a method which should return a new instance of the interface (there is really nothing build in to stop you from using arbitrary strings, but the automated injection in the constructors when using getInstanceOf is using the type hints of the parameters. If you use any other string you can't have it automatic injected - which kind of voids the point. That said, it still can be of use when integrating King23/DI with frameworks that require specific keys in the DI). The object returned here will only be instanced once, further calls will return the same instance.

**registerFactory($interface, $implementation)** works the same way register() does, except that each time an instance of $interface is requested a new instance will be created.

**getInstanceOf($classname)**, will return an object of type $classname, which gets the dependencies injected that are defined by the interfaces used in the type hints of its constructor. If called with a class/interfacename that has been used as a key in register/registerFactory, it will actually return an instance of the registered service.
**get($classname)**, will return an object of type $classname, which gets the dependencies injected that are defined by the interfaces used in the type hints of its constructor. If called with a class/interfacename that has been used as a key in register/registerFactory, it will actually return an instance of the registered service. (psr-11)

**has($classname)**, will return a boolean checking if the class/id is actually available (psr-11)

### Examples
```php
Expand All @@ -52,7 +62,7 @@ Basically the container offers 3 Methods:
});

// now that there is a service registered for \ExampleInterface, we can actually use it
var_dump($container->getInstance(\ExampleInterface::class)); // will dump an instance of \ExampleImplementation
var_dump($container->get(\ExampleInterface::class)); // will dump an instance of \ExampleImplementation

/**
* A simple class with a constructor that allows to inject an instance of \ExampleInterface
Expand All @@ -72,7 +82,7 @@ Basically the container offers 3 Methods:
}
}

$object = $container->getInstance(Foobar::class); // this line would cause $object to be an instance of Foobar
$object = $container->get(Foobar::class); // this line would cause $object to be an instance of Foobar
// having a protected member $example, that holds a reference
// to the \ExampleImplementation

Expand All @@ -85,7 +95,8 @@ Basically the container offers 3 Methods:
- [Homepage](http://king23.net)
- [Github](http://github.com/ppetermann/king23-di)
- [Twitter](http://twitter.com/ppetermann)
- [IRC](irc://irc.coldfront.net:6667/King23) bot will join and put in commit messages on commits there
- [IRC](irc://irc.theairlock.net:6667/King23) bot will join and put in commit messages on commits there
- [Psr-11](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md)

## CONTACT
- Peter Petermann <ppetermann80@googlemail.com>
6 changes: 5 additions & 1 deletion composer.json
Expand Up @@ -11,7 +11,8 @@
}
],
"require": {
"php": ">=5.5.0"
"php": ">=5.5.0",
"psr/container": "~1.0"
},

"require-dev": {
Expand All @@ -22,5 +23,8 @@
"psr-4": {
"King23\\DI\\": "src/"
}
},
"provide": {
"psr/container-implementation": "1.0.0"
}
}
12 changes: 1 addition & 11 deletions src/ContainerInterface.php
Expand Up @@ -2,9 +2,8 @@
namespace King23\DI;

use King23\DI\Exception\AlreadyRegisteredException;
use King23\DI\Exception\NotFoundException;

interface ContainerInterface
interface ContainerInterface extends \Psr\Container\ContainerInterface
{
/**
* register an service implementation as a singleton (shared instance)
Expand All @@ -24,13 +23,4 @@ public function register($interface, callable $implementation);
* @throws AlreadyRegisteredException
*/
public function registerFactory($interface, callable $implementation);

/**
* should create an instance of the given $classname, injecting interfaces to the constructor.
*
* @param string $classname fully qualified classname
* @return object
* @throws NotFoundException
*/
public function getInstanceOf($classname);
}
46 changes: 41 additions & 5 deletions src/DependencyContainer.php
Expand Up @@ -20,6 +20,7 @@ public function __construct()
{
$that = $this;
// we register ourselves, so this container can be injected too
/** @noinspection PhpUnhandledExceptionInspection */
$this->register(
ContainerInterface::class,
function () use ($that) {
Expand Down Expand Up @@ -94,8 +95,9 @@ public function registerFactory($interface, callable $implementation)
* @param string $classname fully qualified classname
* @return object
* @throws NotFoundException
* @throws \ReflectionException
*/
public function getInstanceOf($classname)
public function get($classname)
{
// first we check if this is maybe a known service
if ($this->hasServiceFor($classname)) {
Expand All @@ -104,7 +106,14 @@ public function getInstanceOf($classname)

// alright, this was not one of our known services, so we assume
// we are supposed to play factory for $classname

// lets see if the class actually exists first
if (!class_exists($classname, true) && !interface_exists($classname, true)) {
throw new NotFoundException("Class/Interface not found: '$classname'");
}

$reflector = new \ReflectionClass($classname);

$args = [];

$constructor = $reflector->getConstructor();
Expand All @@ -113,22 +122,49 @@ public function getInstanceOf($classname)
if (!is_null($constructor)) {
/** @var \ReflectionParameter $parameter */
foreach ($constructor->getParameters() as $parameter) {
if (is_null($parameter->getClass())) {
throw new NotFoundException("parameters for constructor contains field without typehint");
try {
if (is_null($parameter->getClass())) {
throw new NotFoundException("parameters for constructor contains field without typehint");
}
} catch (\ReflectionException $reflectionException) {
throw new NotFoundException("can't reflect parameter '{$parameter->name}' of '$classname'", 0, $reflectionException);
}
$paramClass = $parameter->getClass()->getName();
if ($this->hasServiceFor($paramClass)) {
$args[] = $this->getServiceInstanceFor($paramClass);
} else {
$args[] = $this->getInstanceOf($paramClass);
$args[] = $this->get($paramClass);
}
}
}

if ($reflector->isInterface()) {
throw new NotFoundException("no Injector registered for interface: $classname");
throw new NotFoundException("no Injector registered for interface: '$classname'");
}

return $reflector->newInstanceArgs($args);
}

/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has($id)
{
// if we have a service registered, we can assume true
if($this->hasServiceFor($id)) {
return true;
}

// if we don't have as service registered, we might still pull one from the hat
// so lets at least check if the class would be available
return class_exists($id, true);
}
}
4 changes: 3 additions & 1 deletion src/Exception/NotFoundException.php
@@ -1,12 +1,14 @@
<?php
namespace King23\DI\Exception;

use Psr\Container\NotFoundExceptionInterface;

/**
* Class NotFoundException, thrown when a dependency is not found
*
* @package King23\DI\Exception
*/
class NotFoundException extends DIException
class NotFoundException extends DIException implements NotFoundExceptionInterface
{

}
@@ -1,9 +1,10 @@
<?php

namespace King23\DI {

use Inject\MockImplemented;

class DependencyContainerTest extends \PHPUnit_Framework_TestCase
class DependencyContainerPsr11Test extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \King23\DI\Exception\AlreadyRegisteredException
Expand Down Expand Up @@ -57,24 +58,49 @@ function () {
}
);

$this->assertTrue($instance->has(\Inject\Mock::class));

$this->assertInstanceOf(
\Inject\MockImplemented::class,
$instance->getInstanceOf(\Inject\Mock::class)
$instance->get(\Inject\Mock::class)
);

$result = $instance->getInstanceOf(\Test\InjectHere::class);
$result = $instance->get(\Test\InjectHere::class);
$this->assertInstanceOf('\Inject\MockImplemented', $result->mockInjected);
$this->assertTrue($result->test());
}

/**
* @expectedException \King23\DI\Exception\NotFoundException
* @expectedExceptionMessage no Injector registered for interface: Inject\Something
* @expectedExceptionMessage Class/Interface not found: '\Test\InjectFail'
*/
public function testClassNotFound()
{
$instance = new DependencyContainer();
$this->assertFalse($instance->has('\Test\InjectFail'));
$instance->get('\Test\InjectFail');
}

/**
* @expectedException \King23\DI\Exception\NotFoundException
* @expectedExceptionMessage can't reflect parameter 'doesntExist' of '\Test\HintNotFound'
*/
public function testNotFound()
public function testHintNotFound()
{
$instance = new DependencyContainer();
$instance->getInstanceOf(\Test\InjectFail::class);
$this->assertTrue($instance->has('\Test\HintNotFound'));
$instance->get('\Test\HintNotFound');
}

/**
* @expectedException \King23\DI\Exception\NotFoundException
* @expectedExceptionMessage no Injector registered for interface: 'Inject\Something'
*/
public function testInjectorNotFound()
{
$instance = new DependencyContainer();
$this->assertTrue($instance->has('\Test\InjectorNotFound'));
$instance->get('\Test\InjectorNotFound');
}

public function testSingleton()
Expand All @@ -87,8 +113,11 @@ function () {
}
);

$result1 = $instance->getInstanceOf(\Test\InjectHere::class);
$result2 = $instance->getInstanceOf(\Test\InjectHere::class);
$this->assertTrue($instance->has(\Inject\Mock::class));
$this->assertTrue($instance->has(\Test\InjectHere::class));

$result1 = $instance->get(\Test\InjectHere::class);
$result2 = $instance->get(\Test\InjectHere::class);
$this->assertInstanceOf('\Inject\MockImplemented', $result1->mockInjected);
$this->assertInstanceOf('\Inject\MockImplemented', $result2->mockInjected);
$this->assertTrue($result1->mockInjected === $result2->mockInjected);
Expand All @@ -104,8 +133,11 @@ function () {
}
);

$result1 = $instance->getInstanceOf(\Test\InjectHere::class);
$result2 = $instance->getInstanceOf(\Test\InjectHere::class);
$this->assertTrue($instance->has(\Inject\Mock::class));
$this->assertTrue($instance->has(\Test\InjectHere::class));

$result1 = $instance->get(\Test\InjectHere::class);
$result2 = $instance->get(\Test\InjectHere::class);
$this->assertInstanceOf('\Inject\MockImplemented', $result1->mockInjected);
$this->assertInstanceOf('\Inject\MockImplemented', $result2->mockInjected);
$this->assertTrue($result1->mockInjected !== $result2->mockInjected);
Expand All @@ -118,14 +150,14 @@ function () {
public function testNoHint()
{
$instance = new DependencyContainer();
$instance->getInstanceOf(\Test\InjectNoHint::class);
$instance->get(\Test\InjectNoHint::class);
}

public function testSelfInject()
{
$instance = new DependencyContainer();
$this->assertTrue(
$instance->getInstanceOf(\Test\SelfInject::class)->container === $instance
$instance->get(\Test\SelfInject::class)->container === $instance
);
}
}
Expand Down Expand Up @@ -172,14 +204,6 @@ public function test()
}
}

class InjectFail
{
public function __construct(\Inject\Something $foobar)
{

}
}

class InjectNoHint
{
public function __construct($foo)
Expand All @@ -197,4 +221,18 @@ public function __construct(ContainerInterface $container)
$this->container = $container;
}
}

class HintNotFound
{
public function __construct(\Inject\DoesntExist $doesntExist)
{
}
}

class InjectorNotFound
{
public function __construct(\Inject\Something $something)
{
}
}
}

0 comments on commit c6f8d0b

Please sign in to comment.