Skip to content

Commit

Permalink
Merge pull request #15 from webfactory/sqs_driver
Browse files Browse the repository at this point in the history
SQS driver support for 1.x
  • Loading branch information
henrikbjorn committed Jun 10, 2015
2 parents 1b8bb6d + d4e4033 commit ac51c9d
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 12 deletions.
38 changes: 37 additions & 1 deletion DependencyInjection/BernardBernardExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Kernel;

class BernardBernardExtension extends \Symfony\Component\HttpKernel\DependencyInjection\Extension
{
Expand Down Expand Up @@ -41,6 +41,10 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerJmsConfiguration($container);
}

if ($config['driver'] == 'sqs') {
$this->registerSqsConfiguration($config, $container);
}

$this->registerMiddlewaresConfiguration($config['middlewares'], $container);
}

Expand All @@ -49,6 +53,30 @@ protected function registerFlatFileConfiguration($config, $container)
$container->getDefinition('bernard.driver.file')->replaceArgument(0, $config['directory']);
}

protected function registerSqsConfiguration(array $config, ContainerBuilder $container)
{
$sqsClientDefinition = new Definition();
if ($this->definitionClassDeprecatesSetFactoryClassAndSetFactoryMethod()) {
$sqsClientDefinition->setFactory('Aws\Sqs\SqsClient::factory');
} else {
$sqsClientDefinition->setFactoryClass('Aws\Sqs\SqsClient')
->setFactoryMethod('factory');
}
$sqsClientDefinition->setArguments(
array(
array(
'region' => $config['sqs']['region'],
'key' => $config['sqs']['key'],
'secret' => $config['sqs']['secret'],
)
)
);
$container->getDefinition('bernard.driver.sqs')->replaceArgument(0, $sqsClientDefinition);

$container->getDefinition('bernard.driver.sqs')->replaceArgument(1, $config['options']['queue_map']);
$container->getDefinition('bernard.driver.sqs')->replaceArgument(2, $config['options']['prefetch']);
}

protected function registerDoctrineConfiguration($config, $container)
{
$container->getDefinition('bernard.schema_listener')
Expand Down Expand Up @@ -91,4 +119,12 @@ protected function registerPhpRedisConfiguration($config, $container)
{
$container->getDefinition('bernard.driver.phpredis')->replaceArgument(0, new Reference($config['phpredis_service']));
}

/**
* @return bool
*/
private function definitionClassDeprecatesSetFactoryClassAndSetFactoryMethod()
{
return method_exists(new Definition(), 'setFactory');
}
}
54 changes: 46 additions & 8 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@
namespace Bernard\BernardBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements \Symfony\Component\Config\Definition\ConfigurationInterface
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$tree = new TreeBuilder();
$root = $tree->root('bernard_bernard');

$this->addNodes($root);
$this->addValidationRules($root);

return $tree;
}

protected function addNodes(NodeDefinition $root)
{
$root
->validate()
->ifTrue(function ($v) { return 'file' === $v['driver'] && empty($v['options']['directory']); })
->thenInvalid('The "directory" option must be defined when using the file driver.')
->end()
->children()
->enumNode('driver')
->values(array('file', 'predis', 'doctrine', 'phpredis', 'ironmq'))
->values(array('file', 'predis', 'doctrine', 'phpredis', 'ironmq', 'sqs'))
->isRequired()
->cannotBeEmpty()
->end()
->enumNode('serializer')
->defaultValue('simple')
Expand All @@ -33,6 +41,14 @@ public function getConfigTreeBuilder()
->booleanNode('failures')->defaultFalse()->end()
->end()
->end()
->arrayNode('sqs')
->addDefaultsIfNotSet()
->children()
->scalarNode('region')->defaultNull()->end()
->scalarNode('key')->defaultNull()->end()
->scalarNode('secret')->defaultNull()->end()
->end()
->end()
->arrayNode('options')
->addDefaultsIfNotSet()
->children()
Expand All @@ -41,12 +57,34 @@ public function getConfigTreeBuilder()
->scalarNode('connection')->defaultValue('default')->end()
->scalarNode('phpredis_service')->defaultValue('snc_redis.bernard')->end()
->scalarNode('ironmq_service')->defaultNull()->end()
->arrayNode('queue_map')->prototype('array')->end()
->arrayNode('queue_map')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
;
}

return $tree;
protected function addValidationRules(NodeDefinition $root)
{
$root
->validate()
->ifTrue(function ($v) { return 'file' === $v['driver'] && empty($v['options']['directory']); })
->thenInvalid('The "directory" option must be defined when using the file driver.')
->end()
->validate()
->ifTrue(function ($v) { return 'sqs' === $v['driver'] && empty($v['sqs']['region']); })
->thenInvalid('The "region" option must be defined when using the sqs driver.')
->end()
->validate()
->ifTrue(function ($v) { return 'sqs' === $v['driver'] && empty($v['sqs']['key']); })
->thenInvalid('The "key" option must be defined when using the sqs driver.')
->end()
->validate()
->ifTrue(function ($v) { return 'sqs' === $v['driver'] && empty($v['sqs']['secret']); })
->thenInvalid('The "secret" option must be defined when using the sqs driver.')
->end();
}
}
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function registerBundles()
``` yml
# .. previous content of app/config/config.yml
bernard_bernard:
driver: file # you can choose predis, phpredis, file, doctrine etc.
driver: file # you can choose predis, phpredis, file, doctrine, sqs etc.
serializer: simple # this is the default and it is optional. Other values are symfony or jms
```
Expand Down Expand Up @@ -164,3 +164,20 @@ bernard_bernard:
options:
ironmq_service: ironmq_connection
```

### Amazon SQS

To use Amazon SQS, configure your driver like this:

``` yaml
bernard_bernard:
driver: sqs
options:
queue_map: # optional for aliasing queue urls, e.g.:
send_newsletter: https://sqs.eu-west-1.amazonaws.com/...
prefetch: 1 # optional, but beware the default is >1 and you may run into invisibility timeout problems with that
sqs:
region: "your aws region" # e.g. "eu-west-1"
key: "your aws user's key"
secret: "your aws user's secret"
```
6 changes: 6 additions & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
<argument /><!-- IronMQ connection instance -->
</service>

<service id="bernard.driver.sqs" class="Bernard\Driver\SqsDriver" public="false">
<argument /><!-- SQS client-->
<argument /><!-- queue map -->
<argument /><!-- prefetch -->
</service>

<!-- Middlewares -->
<service id="bernard.middleware.error_log" class="Bernard\Middleware\ErrorLogFactory" public="false" />

Expand Down
68 changes: 67 additions & 1 deletion Tests/DependencyInjection/BernardBernardExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,21 @@

use Bernard\BernardBundle\DependencyInjection\BernardBernardExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\HttpKernel\Kernel;

class BernardBernardExtensionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var BernardBernardExtension
*/
protected $extension;

/**
* @var ContainerBuilder
*/
protected $container;

public function setUp()
{
$this->extension = new BernardBernardExtension;
Expand Down Expand Up @@ -49,7 +61,7 @@ public function testMiddlewaresHaveMiddlewareTag()
$this->assertEquals(array(array('type' => 'consumer')), $definition->getTag('bernard.middleware'));
}

public function testDoctrinEventListenerIsAdded()
public function testDoctrineEventListenerIsAdded()
{
$config = array_filter(array('driver' => 'doctrine', 'options' => array('connection' => 'bernard')));

Expand Down Expand Up @@ -97,4 +109,58 @@ public function testDriverIsAliased()
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Alias', $alias);
$this->assertEquals('bernard.driver.doctrine', (string) $alias);
}

public function testSqsDriverCanBeBuildFromConfiguration()
{
$configuredQueueMap = array('name1' => 'url1', 'name2' => 'url2');
$configuredPrefetch = 5;
$configuredRegion = 'test-region';
$configuredKey = 'test-key';
$configuredSecret = 'test-secret';

$config = array(
'driver' => 'sqs',
'options' => array(
'queue_map' => $configuredQueueMap,
'prefetch' => $configuredPrefetch,
),
'sqs' => array(
'region' => $configuredRegion,
'key' => $configuredKey,
'secret' => $configuredSecret,
),
);

$this->extension->load(array($config), $this->container);
$driverDefinition = $this->container->getDefinition('bernard.driver.sqs');

/** @var Definition $resultingSqsClientArgument */
$resultingSqsClientArgument = $driverDefinition->getArgument(0);
if ($this->definitionClassDeprecatesSetFactoryClassAndSetFactoryMethod()) {
$this->assertSame(array('Aws\Sqs\SqsClient', 'factory'), $resultingSqsClientArgument->getFactory());
} else {
$this->assertSame('Aws\Sqs\SqsClient', $resultingSqsClientArgument->getFactoryClass());
$this->assertSame('factory', $resultingSqsClientArgument->getFactoryMethod());
}

$sqsClientFactoryArguments = $resultingSqsClientArgument->getArguments();
$sqsClientFactoryConfiguration = $sqsClientFactoryArguments[0];
$this->assertSame($configuredRegion, $sqsClientFactoryConfiguration['region']);
$this->assertSame($configuredKey, $sqsClientFactoryConfiguration['key']);
$this->assertSame($configuredSecret, $sqsClientFactoryConfiguration['secret']);

$resultingQueueMapArgument = $driverDefinition->getArgument(1);
$this->assertEquals($configuredQueueMap, $resultingQueueMapArgument);

$resultingPrefetchArgument = $driverDefinition->getArgument(2);
$this->assertEquals($configuredPrefetch, $resultingPrefetchArgument);
}

/**
* @return bool
*/
private function definitionClassDeprecatesSetFactoryClassAndSetFactoryMethod()
{
return method_exists(new Definition(), 'setFactory');
}
}
56 changes: 56 additions & 0 deletions Tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public function testDefaults()

$this->assertEquals(array('error_log' => false, 'logger' => false, 'failures' => false), $config['middlewares']);
$this->assertEquals(array('prefetch' => null, 'directory' => '', 'connection' => 'default', 'phpredis_service' => 'snc_redis.bernard', 'ironmq_service' => null, 'queue_map' => array()), $config['options']);
$this->assertEquals(array('region' => null, 'key' => null, 'secret' => null), $config['sqs']);
}

public function testDriverIsRequired()
Expand Down Expand Up @@ -47,10 +48,65 @@ public function testFileDriverRequiresDirectoryOptionToBeSet()
$this->processConfig(array('driver' => 'file'));
}

public function testSqsDriverWorksWithRequiredOptionsSet()
{
$this->setExpectedException(null);

$this->processConfig($this->createValidSqsConfiguration());
}

public function testSqsDriverRequiresRegionOptionToBeSet()
{
$this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidConfigurationException');

$configurationWithoutRegion = $this->createValidSqsConfiguration();
$configurationWithoutRegion['options']['sqs']['region'] = null;
$this->processConfig($configurationWithoutRegion);
}

public function testSqsDriverRequiresKeyOptionToBeSet()
{
$this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidConfigurationException');

$configurationWithoutKey = $this->createValidSqsConfiguration();
$configurationWithoutKey['options']['sqs']['key'] = null;
$this->processConfig($configurationWithoutKey);
}

public function testSqsDriverRequiresSecretOptionToBeSet()
{
$this->setExpectedException('Symfony\Component\Config\Definition\Exception\InvalidConfigurationException');

$configurationWithoutSecret = $this->createValidSqsConfiguration();
$configurationWithoutSecret['options']['sqs']['secret'] = null;
$this->processConfig($configurationWithoutSecret);
}

protected function processConfig($config)
{
$processor = new Processor;

return $processor->processConfiguration(new Configuration, array($config));
}

/**
* @return array(string => string|array)
*/
private function createValidSqsConfiguration()
{
return array(
'driver' => 'sqs',
'options' => array(
'queue_map' => array(
'name1' => 'url1',
'name2' => 'url2',
)
),
'sqs' => array(
'region' => 'test-region',
'key' => 'test-key',
'secret' => 'test-secret',
),
);
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

"require-dev" : {
"symfony/console" : "~2.1",
"symfony/finder" : "~2.1"
"symfony/finder" : "~2.1",
"phpunit/phpunit": "~4.0"
},

"extra" : {
Expand Down

0 comments on commit ac51c9d

Please sign in to comment.