Merge pull request #213 from meadsteve/plugin-builder

Plugin construction change - plugin factory
This commit is contained in:
Steve B 2013-11-25 05:43:43 -08:00
commit 477fd58641
10 changed files with 568 additions and 67 deletions

View file

@ -9,6 +9,7 @@
namespace PHPCI;
use PHPCI\Helper\MailerFactory;
use PHPCI\Model\Build;
use b8\Store;
use b8\Config;
@ -90,6 +91,11 @@ class Builder implements LoggerAwareInterface
*/
public $quiet = false;
/**
* @var \PHPCI\Plugin\Util\Factory
*/
protected $pluginFactory;
/**
* Set up the builder.
* @param \PHPCI\Model\Build $build
@ -102,6 +108,7 @@ class Builder implements LoggerAwareInterface
}
$this->build = $build;
$this->store = Store\Factory::getStore('Build');
$this->setupPluginFactory($build);
}
/**
@ -425,7 +432,7 @@ class Builder implements LoggerAwareInterface
// Try running it:
try {
$obj = new $class($this, $this->build, $options);
$obj = $this->pluginFactory->buildPlugin($class, $options);
if (!$obj->execute()) {
$rtn = false;
@ -491,4 +498,35 @@ class Builder implements LoggerAwareInterface
{
return $this->logger;
}
private function setupPluginFactory(Build $build)
{
$this->pluginFactory = new Plugin\Util\Factory();
$self = $this;
$this->pluginFactory->registerResource(
function () use($self) {
return $self;
},
null,
'PHPCI\Builder'
);
$this->pluginFactory->registerResource(
function () use($build) {
return $build;
},
null,
'PHPCI\Model\Build'
);
$this->pluginFactory->registerResource(
function () use ($self) {
$factory = new MailerFactory($self->getSystemConfig('phpci'));
return $factory->getSwiftMailerFromConfig();
},
null,
'Swift_Mailer'
);
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace PHPCI\Helper;
class MailerFactory {
/**
* @var array
*/
protected $emailConfig;
public function __construct($phpCiConfig = null)
{
$this->emailConfig = isset($phpCiSettings['email_settings']) ?: array();
}
/**
* Returns an instance of Swift_Mailer based on the config.s
* @return \Swift_Mailer
*/
public function getSwiftMailerFromConfig()
{
/** @var \Swift_SmtpTransport $transport */
$transport = \Swift_SmtpTransport::newInstance(
$this->getMailConfig('smtp_address'),
$this->getMailConfig('smtp_port'),
$this->getMailConfig('smtp_encryption')
);
$transport->setUsername($this->getMailConfig('smtp_username'));
$transport->setPassword($this->getMailConfig('smtp_password'));
return \Swift_Mailer::newInstance($transport);
}
protected function getMailConfig($configName)
{
if (isset($this->emailConfig[$configName]) && $this->emailConfig[$configName] != "") {
return $this->emailConfig[$configName];
} else {
// Check defaults
switch($configName) {
case 'smtp_address':
return "localhost";
case 'default_mailto_address':
return null;
case 'smtp_port':
return '25';
case 'smtp_encryption':
return null;
default:
return "";
}
}
}
}

View file

@ -18,6 +18,5 @@ use PHPCI\Model\Build;
*/
interface Plugin
{
public function __construct(Builder $phpci, Build $build, array $options = array());
public function execute();
}

View file

@ -30,25 +30,34 @@ class Email implements \PHPCI\Plugin
*/
protected $options;
/**
* @var array
*/
protected $emailConfig;
/**
* @var \Swift_Mailer
*/
protected $mailer;
public function __construct(Builder $phpci, Build $build, array $options = array())
/**
* @var string
*/
protected $fromAddress;
public function __construct(Builder $phpci,
Build $build,
\Swift_Mailer $mailer,
array $options = array()
)
{
$phpCiSettings = $phpci->getSystemConfig('phpci');
$this->phpci = $phpci;
$this->build = $build;
$this->options = $options;
$this->emailConfig = isset($phpCiSettings['email_settings']) ? $phpCiSettings['email_settings'] : array();
$this->loadSwiftMailerFromConfig();
$phpCiSettings = $phpci->getSystemConfig('phpci');
$this->fromAddress = isset($phpCiSettings['email_settings']['from_address'])
? $phpCiSettings['email_settings']['from_address']
: "notifications-ci@phptesting.org";
$this->mailer = $mailer;
}
/**
@ -98,7 +107,7 @@ class Email implements \PHPCI\Plugin
public function sendEmail($toAddresses, $subject, $body)
{
$message = \Swift_Message::newInstance($subject)
->setFrom($this->getMailConfig('from_address'))
->setFrom($this->fromAddress)
->setTo($toAddresses)
->setBody($body)
->setContentType("text/html");
@ -120,44 +129,6 @@ class Email implements \PHPCI\Plugin
return $failures;
}
protected function loadSwiftMailerFromConfig()
{
/** @var \Swift_SmtpTransport $transport */
$transport = \Swift_SmtpTransport::newInstance(
$this->getMailConfig('smtp_address'),
$this->getMailConfig('smtp_port'),
$this->getMailConfig('smtp_encryption')
);
$transport->setUsername($this->getMailConfig('smtp_username'));
$transport->setPassword($this->getMailConfig('smtp_password'));
$this->mailer = \Swift_Mailer::newInstance($transport);
}
protected function getMailConfig($configName)
{
if (isset($this->emailConfig[$configName]) && $this->emailConfig[$configName] != "") {
return $this->emailConfig[$configName];
} else {
// Check defaults
switch($configName) {
case 'smtp_address':
return "localhost";
case 'default_mailto_address':
return null;
case 'smtp_port':
return '25';
case 'smtp_encryption':
return null;
case 'from_address':
return "notifications-ci@phptesting.org";
default:
return "";
}
}
}
protected function getEmailAddresses()
{
$addresses = array();
@ -179,4 +150,4 @@ class Email implements \PHPCI\Plugin
}
return $addresses;
}
}
}

View file

@ -0,0 +1,153 @@
<?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;
}
/**
* Builds an instance of plugin of class $className. $options will
* be passed along with any resources registered with the factory.
*
* @param $className
* @param array $options
* @return \PHPCI\Plugin
*/
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

@ -10,13 +10,12 @@
namespace PHPCI\Plugin\Tests;
use PHPCI\Plugin\Email as EmailPlugin;
define('PHPCI_BIN_DIR', "FAKEPHPCIBIN");
/**
* Unit test for the PHPUnit plugin.
* @author meadsteve
*/
class EmailTest extends \PHPUnit_Framework_TestCase
class EmailTest extends \PHPUnit_Framework_TestCase
{
/**
@ -43,7 +42,7 @@ class EmailTest extends \PHPUnit_Framework_TestCase
{
$this->mockBuild = $this->getMock(
'\PHPCI\Model\Build',
array('getLog'),
array('getLog', 'getStatus'),
array(),
"mockBuild",
false
@ -53,6 +52,10 @@ class EmailTest extends \PHPUnit_Framework_TestCase
->method('getLog')
->will($this->returnValue("Build Log"));
$this->mockBuild->expects($this->any())
->method('getStatus')
->will($this->returnValue(\PHPCI\Model\Build::STATUS_SUCCESS));
$this->mockCiBuilder = $this->getMock(
'\PHPCI\Builder',
array('getSystemConfig',
@ -96,8 +99,9 @@ class EmailTest extends \PHPUnit_Framework_TestCase
{
$this->testedEmailPlugin = new EmailPlugin(
$this->mockCiBuilder,
$arrOptions,
$this->mockMailer
$this->mockBuild,
$this->mockMailer,
$arrOptions
);
}

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": "1.1.*"
},
"suggest": {

68
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": "2f0615871ce4ee1eb8e4642bf0c731da",
"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",
@ -242,17 +288,17 @@
},
{
"name": "symfony/console",
"version": "v2.3.6",
"version": "v2.3.7",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "f880062d56edefb25b36f2defa65aafe65959dc7"
"reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/f880062d56edefb25b36f2defa65aafe65959dc7",
"reference": "f880062d56edefb25b36f2defa65aafe65959dc7",
"url": "https://api.github.com/repos/symfony/Console/zipball/00848d3e13cf512e77c7498c2b3b0192f61f4b18",
"reference": "00848d3e13cf512e77c7498c2b3b0192f61f4b18",
"shasum": ""
},
"require": {
@ -291,21 +337,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2013-09-25 06:04:15"
"time": "2013-11-13 21:27:40"
},
{
"name": "symfony/yaml",
"version": "v2.3.6",
"version": "v2.3.7",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3"
"reference": "c1bda5b459d792cb253de12c65beba3040163b2b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/6bb881b948368482e1abf1a75c08bcf88a1c5fc3",
"reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/c1bda5b459d792cb253de12c65beba3040163b2b",
"reference": "c1bda5b459d792cb253de12c65beba3040163b2b",
"shasum": ""
},
"require": {
@ -338,7 +384,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2013-09-22 18:04:39"
"time": "2013-10-17 11:48:01"
}
],
"packages-dev": [