Create factory for plugins that resources can be registered with.

This commit is contained in:
meadsteve 2013-11-17 17:23:35 +00:00
parent f88df400f0
commit e1d8239e8a
5 changed files with 481 additions and 2 deletions

View file

@ -0,0 +1,145 @@
<?php
namespace PHPCI\Plugin\Util;
class Factory {
const TYPE_Array = "array";
const TYPE_Callable = "callable";
private $currentPluginOptions;
/**
* @var \Pimple
*/
private $container;
function __construct(\Pimple $container = null)
{
if ($container) {
$this->container = $container;
}
else {
$this->container = new \Pimple();
}
$self = $this;
$this->registerResource(
function() use ($self) {
return $self->getLastOptions();
},
'options',
'array'
);
}
public function getLastOptions() {
return $this->currentPluginOptions;
}
public function buildPlugin($className, array $options = array())
{
$this->currentPluginOptions = $options;
$reflectedPlugin = new \ReflectionClass($className);
$constructor = $reflectedPlugin->getConstructor();
if ($constructor) {
$argsToUse = array();
foreach($constructor->getParameters() as $param) {
$argsToUse = $this->addArgFromParam($argsToUse, $param);
}
$plugin = $reflectedPlugin->newInstanceArgs($argsToUse);
} else {
$plugin = $reflectedPlugin->newInstance();
}
return $plugin;
}
/**
* @param callable $loader
* @param string|null $name
* @param string|null $type
* @throws \InvalidArgumentException
* @internal param mixed $resource
*/
public function registerResource(callable $loader,
$name = null,
$type = null
)
{
if ($name === null && $type === null) {
throw new \InvalidArgumentException(
"Type or Name must be specified"
);
}
$resourceID = $this->getInternalID($type, $name);
$this->container[$resourceID] = $loader;
}
private function getInternalID($type = null, $name = null)
{
$type = $type ? : "";
$name = $name ? : "";
return $type . "-" . $name;
}
private function getResourceFor($type = null, $name = null)
{
$fullId = $this->getInternalID($type, $name);
if (isset($this->container[$fullId])) {
return $this->container[$fullId];
}
$typeOnlyID = $this->getInternalID($type, null);
if (isset($this->container[$typeOnlyID])) {
return $this->container[$typeOnlyID];
}
$nameOnlyID = $this->getInternalID(null, $name);
if (isset($this->container[$nameOnlyID])) {
return $this->container[$nameOnlyID];
}
return null;
}
private function getParamType(\ReflectionParameter $param)
{
$class = $param->getClass();
if ($class) {
return $class->getName();
} elseif($param->isArray()) {
return self::TYPE_Array;
} elseif($param->isCallable()) {
return self::TYPE_Callable;
} else {
return null;
}
}
private function addArgFromParam($existingArgs, \ReflectionParameter $param)
{
$name = $param->getName();
$type = $this->getParamType($param);
$arg = $this->getResourceFor($type, $name);
if ($arg !== null) {
$existingArgs[] = $arg;
} elseif ($arg === null && $param->isOptional()) {
$existingArgs[] = $param->getDefaultValue();
} else {
throw new \DomainException(
"Unsatisfied dependency: " . $param->getName()
);
}
return $existingArgs;
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace PHPCI\Plugin\Tests\Util;
use PHPCI\Builder;
use PHPCI\Model\Build;
use PHPCI\Plugin;
class ExamplePluginWithNoConstructorArgs {
}
class ExamplePluginWithSingleOptionalArg {
function __construct($optional = null)
{
}
}
class ExamplePluginWithSingleRequiredArg {
public $RequiredArgument;
function __construct($requiredArgument)
{
$this->RequiredArgument = $requiredArgument;
}
}
class ExamplePluginWithSingleTypedRequiredArg {
public $RequiredArgument;
function __construct(\stdClass $requiredArgument)
{
$this->RequiredArgument = $requiredArgument;
}
}
class ExamplePluginFull implements Plugin {
public $Options;
public function __construct(
Builder $phpci,
Build $build,
array $options = array()
)
{
$this->Options = $options;
}
public function execute()
{
}
}

View file

@ -0,0 +1,174 @@
<?php
namespace PHPCI\Plugin\Tests\Util;
require_once __DIR__ . "/ExamplePlugins.php";
use PHPCI\Plugin\Util\Factory;
class FactoryTest extends \PHPUnit_Framework_TestCase {
/**
* @var \PHPCI\Plugin\Util\Factory
*/
protected $testedFactory;
protected $expectedResource;
protected $resourceLoader;
protected function setUp()
{
$this->testedFactory = new Factory();
// Setup a resource that can be returned and asserted against
$this->expectedResource = new \stdClass();
$resourceLink = $this->expectedResource;
$this->resourceLoader = function() use (&$resourceLink) {
return $resourceLink;
};
}
protected function tearDown()
{
// Nothing to do.
}
public function testRegisterResourceThrowsExceptionWithoutTypeAndName()
{
$this->setExpectedException("InvalidArgumentException");
$this->testedFactory->registerResource($this->resourceLoader, null, null);
}
public function testBuildPluginWorksWithConstructorlessPlugins()
{
$namespace = '\\PHPCI\\Plugin\\Tests\\Util\\';
$expectedPluginClass = $namespace .'ExamplePluginWithNoConstructorArgs';
$plugin = $this->testedFactory->buildPlugin($expectedPluginClass);
$this->assertInstanceOf($expectedPluginClass, $plugin);
}
public function testBuildPluginWorksWithSingleOptionalArgConstructor()
{
$namespace = '\\PHPCI\\Plugin\\Tests\\Util\\';
$expectedPluginClass = $namespace . 'ExamplePluginWithSingleOptionalArg';
$plugin = $this->testedFactory->buildPlugin($expectedPluginClass);
$this->assertInstanceOf($expectedPluginClass, $plugin);
}
public function testBuildPluginThrowsExceptionIfMissingResourcesForRequiredArg()
{
$this->setExpectedException(
'DomainException',
'Unsatisfied dependency: requiredArgument'
);
$namespace = '\\PHPCI\\Plugin\\Tests\\Util\\';
$expectedPluginClass = $namespace . 'ExamplePluginWithSingleRequiredArg';
$plugin = $this->testedFactory->buildPlugin($expectedPluginClass);
}
public function testBuildPluginLoadsArgumentsBasedOnName()
{
$namespace = '\\PHPCI\\Plugin\\Tests\\Util\\';
$expectedPluginClass = $namespace . 'ExamplePluginWithSingleRequiredArg';
$this->testedFactory->registerResource(
$this->resourceLoader,
"requiredArgument"
);
/** @var ExamplePluginWithSingleRequiredArg $plugin */
$plugin = $this->testedFactory->buildPlugin($expectedPluginClass);
$this->assertEquals($this->expectedResource, $plugin->RequiredArgument);
}
public function testBuildPluginLoadsArgumentsBasedOnType()
{
$namespace = '\\PHPCI\\Plugin\\Tests\\Util\\';
$expectedPluginClass = $namespace . 'ExamplePluginWithSingleTypedRequiredArg';
$this->testedFactory->registerResource(
$this->resourceLoader,
null,
"stdClass"
);
/** @var ExamplePluginWithSingleTypedRequiredArg $plugin */
$plugin = $this->testedFactory->buildPlugin($expectedPluginClass);
$this->assertEquals($this->expectedResource, $plugin->RequiredArgument);
}
public function testBuildPluginLoadsFullExample()
{
$namespace = '\\PHPCI\\Plugin\\Tests\\Util\\';
$expectedPluginClass = $namespace . 'ExamplePluginFull';
$this->registerBuildAndBuilder();
/** @var ExamplePluginFull $plugin */
$plugin = $this->testedFactory->buildPlugin($expectedPluginClass);
$this->assertInstanceOf($expectedPluginClass, $plugin);
}
public function testBuildPluginLoadsFullExampleWithOptions()
{
$namespace = '\\PHPCI\\Plugin\\Tests\\Util\\';
$expectedPluginClass = $namespace . 'ExamplePluginFull';
$expectedArgs = array(
'thing' => "stuff"
);
$this->registerBuildAndBuilder();
/** @var ExamplePluginFull $plugin */
$plugin = $this->testedFactory->buildPlugin(
$expectedPluginClass,
$expectedArgs
);
$this->assertInternalType('array', $plugin->Options);
$this->assertArrayHasKey('thing', $plugin->Options);
}
/**
* Registers mocked Builder and Build classes so that realistic plugins
* can be tested.
*/
private function registerBuildAndBuilder()
{
$this->testedFactory->registerResource(
function () {
return $this->getMock(
'PHPCI\Builder',
array(),
array(),
'',
false
);
},
null,
'PHPCI\\Builder'
);
$this->testedFactory->registerResource(
function () {
return $this->getMock(
'PHPCI\Model\Build',
array(),
array(),
'',
false
);
},
null,
'PHPCI\\Model\Build'
);
}
}

View file

@ -29,7 +29,8 @@
"symfony/yaml" : "2.*",
"symfony/console" : "2.*",
"psr/log": "1.0.0",
"monolog/monolog": "1.6.0"
"monolog/monolog": "1.6.0",
"pimple/pimple": "v1.1.*"
},
"suggest": {

104
composer.lock generated
View file

@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "534baabecc11275d5cc7f375eecf738d",
"hash": "8ad9f4b137f30db71c8dcf45d8347655",
"packages": [
{
"name": "block8/b8framework",
@ -153,6 +153,52 @@
],
"time": "2013-07-28 22:38:30"
},
{
"name": "pimple/pimple",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/fabpot/Pimple.git",
"reference": "471c7d7c52ad6594e17b8ec33efdd1be592b5d83"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fabpot/Pimple/zipball/471c7d7c52ad6594e17b8ec33efdd1be592b5d83",
"reference": "471c7d7c52ad6594e17b8ec33efdd1be592b5d83",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-0": {
"Pimple": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
"homepage": "http://pimple.sensiolabs.org",
"keywords": [
"container",
"dependency injection"
],
"time": "2013-09-19 04:53:08"
},
{
"name": "psr/log",
"version": "1.0.0",
@ -293,6 +339,62 @@
"homepage": "http://symfony.com",
"time": "2013-09-25 06:04:15"
},
{
"name": "symfony/dependency-injection",
"version": "v2.3.7",
"target-dir": "Symfony/Component/DependencyInjection",
"source": {
"type": "git",
"url": "https://github.com/symfony/DependencyInjection.git",
"reference": "3ead0b87b455289864d648152e0930629df687d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/3ead0b87b455289864d648152e0930629df687d2",
"reference": "3ead0b87b455289864d648152e0930629df687d2",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/config": "~2.2",
"symfony/yaml": "~2.0"
},
"suggest": {
"symfony/config": "",
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
"symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\DependencyInjection\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony DependencyInjection Component",
"homepage": "http://symfony.com",
"time": "2013-11-09 15:43:20"
},
{
"name": "symfony/yaml",
"version": "v2.3.6",