API PSR-11 compliance (fixes #6594) (#6931)

Note that our usage of `$asSingleton` in `get()` is fine. Quote from the PSR:

> Two successive calls to get with the same identifier SHOULD return the same value. However, depending on the implementor design and/or user configuration, different values might be returned, so user SHOULD NOT rely on getting the same value on 2 successive calls.
This commit is contained in:
Ingo Schommer 2017-05-19 13:45:07 +12:00 committed by Damian Mooyman
parent db3e3d51fd
commit 100048da33
7 changed files with 130 additions and 42 deletions

View File

@ -39,7 +39,8 @@
"ext-tokenizer": "*",
"ext-ctype": "*",
"ext-hash": "*",
"ext-session": "*"
"ext-session": "*",
"psr/container-implementation": "1.0.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7",
@ -48,6 +49,9 @@
"silverstripe/serve": "dev-master",
"se/selenium-server-standalone": "2.41.0"
},
"provide": {
"psr/container-implementation": "1.0.0"
},
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"

View File

@ -1292,6 +1292,9 @@ After (`mysite/_config/config.yml`):
* Removed `CreditCardField`, `CountryDropdownField`, `PhoneNumberField`, `MemberDatetimeOptionsetField`, `InlineFormAction`.
Use custom code instead
* Removed `ResetFormAction`, use `FormAction::create()->setAttribute('type', 'reset')` instead
* `Injector` now complies with [PSR-11](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md).
Accordingly, `hasService()` has been renamed to `has()`, and `get()` will throw
`SilverStripe\Core\Injector\InjectorNotFoundException` when the service can't be found.
#### <a name="overview-general-deprecated"></a>General and Core Deprecated API

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Core\Injector;
use ReflectionClass;
use ReflectionException;
/**
* A class for creating new objects by the injector.
@ -12,7 +13,11 @@ class InjectionCreator implements Factory
public function create($class, array $params = array())
{
$reflector = new ReflectionClass($class);
try {
$reflector = new ReflectionClass($class);
} catch (ReflectionException $e) {
throw new InjectorNotFoundException($e);
}
if (count($params)) {
return $reflector->newInstanceArgs($params);

View File

@ -2,11 +2,14 @@
namespace SilverStripe\Core\Injector;
use Psr\Container\NotFoundExceptionInterface;
use SilverStripe\Core\Config\Config;
use ReflectionProperty;
use ArrayObject;
use ReflectionObject;
use ReflectionMethod;
use Psr\Container\ContainerInterface;
use SilverStripe\Dev\Deprecation;
/**
* A simple injection manager that manages creating objects and injecting
@ -122,7 +125,7 @@ use ReflectionMethod;
* @author marcus@silverstripe.com.au
* @license BSD License http://silverstripe.org/bsd-license/
*/
class Injector
class Injector implements ContainerInterface
{
/**
@ -233,7 +236,7 @@ class Injector
* If a user wants to use the injector as a static reference
*
* @param array $config
* @return Injector
* @return ContainerInterface
*/
public static function inst($config = null)
{
@ -246,10 +249,10 @@ class Injector
/**
* Sets the default global injector instance.
*
* @param Injector $instance
* @param ContainerInterface $instance
* @return Injector Reference to new active Injector instance
*/
public static function set_inst(Injector $instance)
public static function set_inst(ContainerInterface $instance)
{
return self::$instance = $instance;
}
@ -693,7 +696,7 @@ class Injector
if ($propertyObject->isPublic() && !$propertyObject->getValue($object)) {
$origName = $propertyObject->getName();
$name = ucfirst($origName);
if ($this->hasService($name)) {
if ($this->has($name)) {
// Pull the name out of the registry
$value = $this->get($name);
$propertyObject->setValue($object, $value);
@ -710,7 +713,7 @@ class Injector
$methName = $methodObj->getName();
if (strpos($methName, 'set') === 0) {
$pname = substr($methName, 3);
if ($this->hasService($pname)) {
if ($this->has($pname)) {
// Pull the name out of the registry
$value = $this->get($pname);
$methodObj->invoke($object, $value);
@ -782,6 +785,35 @@ class Injector
}
}
/**
* @deprecated 4.0.0:5.0.0 Use Injector::has() instead
* @param $name
* @return string
*/
public function hasService($name)
{
Deprecation::notice('5.0', 'Use Injector::has() instead');
return $this->has($name);
}
/**
* Does the given service exist?
*
* We do a special check here for services that are using compound names. For example,
* we might want to say that a property should be injected with Log.File or Log.Memory,
* but have only registered a 'Log' service, we'll instead return that.
*
* Will recursively call itself for each depth of dotting.
*
* @param string $name
* @return boolean
*/
public function has($name)
{
return (bool)$this->getServiceName($name);
}
/**
* Does the given service exist, and if so, what's the stored name for it?
*
@ -789,15 +821,14 @@ class Injector
* we might want to say that a property should be injected with Log.File or Log.Memory,
* but have only registered a 'Log' service, we'll instead return that.
*
* Will recursively call hasService for each depth of dotting
* Will recursively call itself for each depth of dotting.
*
* @param string $name
* @return string The name of the service (as it might be different from the one passed in)
* The name of the service (as it might be different from the one passed in)
* @return string|null The name of the service (as it might be different from the one passed in)
*/
public function hasService($name)
public function getServiceName($name)
{
// common case, get it overwith first
// common case, get it over with first
if (isset($this->specs[$name])) {
return $name;
}
@ -808,7 +839,7 @@ class Injector
return null;
}
return $this->hasService(substr($name, 0, strrpos($name, '.')));
return $this->getServiceName(substr($name, 0, strrpos($name, '.')));
}
/**
@ -859,30 +890,45 @@ class Injector
* Next, will check to see if there's any registered configuration for the given type
* and will then try and load that
*
* Failing all of that, will just return a new instance of the
* specificied object.
* Failing all of that, will just return a new instance of the specified object.
*
* @param string $name
* the name of the service to retrieve. If not a registered
* service, then a class of the given name is instantiated
* @param boolean $asSingleton
* Whether to register the created object as a singleton
* if no other configuration is found
* @param array $constructorArgs
* Optional set of arguments to pass as constructor arguments
* if this object is to be created from scratch
* (ie asSingleton = false)
* @return mixed the instance of the specified object
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
*
* @param string $name The name of the service to retrieve. If not a registered
* service, then a class of the given name is instantiated
* @param boolean $asSingleton Whether to register the created object as a singleton
* if no other configuration is found
* @param array $constructorArgs Optional set of arguments to pass as constructor arguments
* if this object is to be created from scratch (with $asSingleton = false)
* @return mixed Instance of the specified object
*/
public function get($name, $asSingleton = true, $constructorArgs = null)
{
$object = $this->getNamedService($name, $asSingleton, $constructorArgs);
if (!$object) {
throw new InjectorNotFoundException("The '{$name}' service could not be found");
}
return $object;
}
/**
* Returns the service, or `null` if it doesnt' exist. See {@link get()} for main usage.
*
* @param string $name
* @param boolean $asSingleton
* @param array $constructorArgs
* @return mixed|null Instance of the specified object (if it exists)
*/
protected function getNamedService($name, $asSingleton = true, $constructorArgs = null)
{
// reassign the name as it might actually be a compound name
if ($serviceName = $this->hasService($name)) {
if ($serviceName = $this->getServiceName($name)) {
// check to see what the type of bean is. If it's a prototype,
// we don't want to return the singleton version of it.
$spec = $this->specs[$serviceName];
$type = isset($spec['type']) ? $spec['type'] : null;
// if we're explicitly a prototype OR we're not wanting a singleton
if (($type && $type == 'prototype') || !$asSingleton) {
if ($spec && $constructorArgs) {
@ -903,7 +949,6 @@ class Injector
return $this->serviceCache[$serviceName];
}
}
$config = $this->configLocator->locateConfigFor($name);
if ($config) {
$this->load(array($name => $config));
@ -916,7 +961,6 @@ class Injector
return $this->instantiate($spec, $name);
}
}
// If we've got this far, we're dealing with a case of a user wanting
// to create an object based on its name. So, we need to fake its config
// if the user wants it managed as a singleton service style object

View File

@ -0,0 +1,9 @@
<?php
namespace SilverStripe\Core\Injector;
use Psr\Container\NotFoundExceptionInterface;
class InjectorNotFoundException extends \Exception implements NotFoundExceptionInterface
{
}

View File

@ -6,6 +6,7 @@ use InvalidArgumentException;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Factory;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Injector\InjectorNotFoundException;
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
use SilverStripe\Core\Tests\Injector\AopProxyServiceTest\AnotherService;
use SilverStripe\Core\Tests\Injector\AopProxyServiceTest\SampleService;
@ -80,9 +81,15 @@ class InjectorTest extends SapphireTest
);
$injector->load($config);
$this->assertFalse($injector->has('UnknownService'));
$this->assertNull($injector->getServiceName('UnknownService'));
$this->assertTrue($injector->has('SampleService'));
$this->assertEquals(
'SampleService',
$injector->hasService('SampleService')
$injector->getServiceName('SampleService')
);
$myObject = new TestObject();
@ -110,15 +117,17 @@ class InjectorTest extends SapphireTest
);
$injector->load($services);
$this->assertTrue($injector->has('SampleService'));
$this->assertEquals(
'SampleService',
$injector->hasService('SampleService')
$injector->getServiceName('SampleService')
);
// We expect a false because the AnotherService::class is actually
// just a replacement of the SilverStripe\Core\Tests\Injector\AopProxyServiceTest\SampleService
$this->assertTrue($injector->has('SampleService'));
$this->assertEquals(
'AnotherService',
$injector->hasService('AnotherService')
$injector->getServiceName('AnotherService')
);
$item = $injector->get('AnotherService');
@ -136,8 +145,11 @@ class InjectorTest extends SapphireTest
$injector->load($services);
$this->assertTrue($injector->hasService('FirstId') == 'FirstId');
$this->assertTrue($injector->hasService('SecondId') == 'SecondId');
$this->assertTrue($injector->has('FirstId'));
$this->assertEquals($injector->getServiceName('FirstId'), 'FirstId');
$this->assertTrue($injector->has('SecondId'));
$this->assertEquals($injector->getServiceName('SecondId'), 'SecondId');
$this->assertTrue($injector->get('FirstId') instanceof AnotherService);
$this->assertTrue($injector->get('SecondId') instanceof SampleService);
@ -251,9 +263,10 @@ class InjectorTest extends SapphireTest
);
$injector->load($config);
$this->assertTrue($injector->has('SampleService'));
$this->assertEquals(
'SampleService',
$injector->hasService('SampleService')
$injector->getServiceName('SampleService')
);
// We expect a false because the AnotherService::class is actually
// just a replacement of the SilverStripe\Core\Tests\Injector\AopProxyServiceTest\SampleService
@ -419,7 +432,8 @@ class InjectorTest extends SapphireTest
);
$injector->load($config);
$this->assertEquals('SampleService', $injector->hasService('SampleService'));
$this->assertTrue($injector->has('SampleService'));
$this->assertEquals('SampleService', $injector->getServiceName('SampleService'));
$myObject = new InjectorTest\OtherTestObject();
$injector->inject($myObject);
@ -453,7 +467,8 @@ class InjectorTest extends SapphireTest
);
$injector->load($config);
$this->assertEquals('SampleService', $injector->hasService('SampleService'));
$this->assertTrue($injector->has('SampleService'));
$this->assertEquals('SampleService', $injector->getServiceName('SampleService'));
$myObject = $injector->get(OtherTestObject::class);
$this->assertInstanceOf(
@ -919,7 +934,14 @@ class InjectorTest extends SapphireTest
$item = $injector->get('TestService');
}
/**
* @expectedException \SilverStripe\Core\Injector\InjectorNotFoundException
*/
public function testGetThrowsOnNotFound()
{
$injector = new Injector();
$injector->get('UnknownService');
}
/**
* Test nesting of injector

View File

@ -3,6 +3,7 @@
namespace SilverStripe\ORM\Tests;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\InjectorNotFoundException;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\Filterable;
@ -744,7 +745,7 @@ class DataListTest extends SapphireTest
public function testSimpleFilterWithNonExistingComparisator()
{
$this->setExpectedException(
'ReflectionException',
InjectorNotFoundException::class,
'Class DataListFilter.Bogus does not exist'
);
$list = TeamComment::get();
@ -755,7 +756,7 @@ class DataListTest extends SapphireTest
{
// Invalid modifiers are treated as failed filter construction
$this->setExpectedException(
'ReflectionException',
InjectorNotFoundException::class,
'Class DataListFilter.invalidmodifier does not exist'
);
$list = TeamComment::get();