Skip to content

Commit

Permalink
Merge pull request #535 from ruslan-polutsygan/s3-resolver-put-options
Browse files Browse the repository at this point in the history
S3 resolver put options
  • Loading branch information
makasim committed Dec 10, 2014
2 parents 2d728fe + dcd2dab commit 5be10ce
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 18 deletions.
12 changes: 11 additions & 1 deletion DependencyInjection/Factory/Resolver/AwsS3ResolverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public function create(ContainerBuilder $container, $resolverName, array $config
$resolverDefinition->replaceArgument(0, new Reference($awsS3ClientId));
$resolverDefinition->replaceArgument(1, $config['bucket']);
$resolverDefinition->replaceArgument(2, $config['acl']);
$resolverDefinition->replaceArgument(3, $config['url_options']);
$resolverDefinition->replaceArgument(3, array_replace($config['url_options'], $config['get_options']));
$resolverDefinition->replaceArgument(4, $config['put_options']);
$resolverId = 'liip_imagine.cache.resolver.'.$resolverName;
$container->setDefinition($resolverId, $resolverDefinition);

Expand Down Expand Up @@ -89,10 +90,19 @@ public function addConfiguration(ArrayNodeDefinition $builder)
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
/** @deprecated Use `get_options` instead */
->arrayNode('url_options')
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
->arrayNode('get_options')
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
->arrayNode('put_options')
->useAttributeAsKey('key')
->prototype('scalar')->end()
->end()
->arrayNode('proxies')
->defaultValue(array())
->prototype('scalar')->end()
Expand Down
78 changes: 65 additions & 13 deletions Imagine/Cache/Resolver/AwsS3Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ class AwsS3Resolver implements ResolverInterface
/**
* @var array
*/
protected $objUrlOptions;
protected $getOptions;

/**
* Object options added to PUT requests
*
* @var array
*/
protected $putOptions;

/**
* @var LoggerInterface
Expand All @@ -46,14 +53,16 @@ class AwsS3Resolver implements ResolverInterface
* @param S3Client $storage The Amazon S3 storage API. It's required to know authentication information.
* @param string $bucket The bucket name to operate on.
* @param string $acl The ACL to use when storing new objects. Default: owner read/write, public read
* @param array $objUrlOptions A list of options to be passed when retrieving the object url from Amazon S3.
* @param array $getOptions A list of options to be passed when retrieving the object url from Amazon S3.
* @param array $putOptions A list of options to be passed when saving the object to Amazon S3.
*/
public function __construct(S3Client $storage, $bucket, $acl = CannedAcl::PUBLIC_READ, array $objUrlOptions = array())
public function __construct(S3Client $storage, $bucket, $acl = CannedAcl::PUBLIC_READ, array $getOptions = array(), $putOptions = array())
{
$this->storage = $storage;
$this->bucket = $bucket;
$this->acl = $acl;
$this->objUrlOptions = $objUrlOptions;
$this->getOptions = $getOptions;
$this->putOptions = $putOptions;
}

/**
Expand Down Expand Up @@ -96,13 +105,18 @@ public function store(BinaryInterface $binary, $path, $filter)
$objectPath = $this->getObjectPath($path, $filter);

try {
$this->storage->putObject(array(
'ACL' => $this->acl,
'Bucket' => $this->bucket,
'Key' => $objectPath,
'Body' => $binary->getContent(),
'ContentType' => $binary->getMimeType()
));
$this->storage->putObject(
array_merge(
$this->putOptions,
array(
'ACL' => $this->acl,
'Bucket' => $this->bucket,
'Key' => $objectPath,
'Body' => $binary->getContent(),
'ContentType' => $binary->getMimeType(),
)
)
);
} catch (\Exception $e) {
$this->logError('The object could not be created on Amazon S3.', array(
'objectPath' => $objectPath,
Expand Down Expand Up @@ -176,10 +190,48 @@ public function remove(array $paths, array $filters)
* @param mixed $value The value to be set.
*
* @return AmazonS3Resolver $this
*
* @deprecated Use `setGetOption` instead
*/
public function setObjectUrlOption($key, $value)
{
$this->objUrlOptions[$key] = $value;
return $this->setGetOption($key, $value);
}

/**
* Sets a single option to be passed when retrieving an objects URL.
*
* If the option is already set, it will be overwritten.
*
* @see Aws\S3\S3Client::getObjectUrl() for available options.
*
* @param string $key The name of the option.
* @param mixed $value The value to be set.
*
* @return AmazonS3Resolver $this
*/
public function setGetOption($key, $value)
{
$this->getOptions[$key] = $value;

return $this;
}

/**
* Sets a single option to be passed when saving an object.
*
* If the option is already set, it will be overwritten.
*
* @see Aws\S3\S3Client::putObject() for available options.
*
* @param string $key The name of the option.
* @param mixed $value The value to be set.
*
* @return AmazonS3Resolver $this
*/
public function setPutOption($key, $value)
{
$this->putOptions[$key] = $value;

return $this;
}
Expand Down Expand Up @@ -210,7 +262,7 @@ protected function getObjectPath($path, $filter)
*/
protected function getObjectUrl($path)
{
return $this->storage->getObjectUrl($this->bucket, $path, 0, $this->objUrlOptions);
return $this->storage->getObjectUrl($this->bucket, $path, 0, $this->getOptions);
}

/**
Expand Down
57 changes: 53 additions & 4 deletions Resources/doc/cache-resolver/aws_s3.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ liip_imagine:
secret: %amazon.s3.secret%
region: %amazon.s3.region%
bucket: %amazon.s3.cache_bucket%
get_options:
Scheme: 'https'
put_options:
CacheControl: 'max-age=86400'
```
## Create resolver as a service
Expand Down Expand Up @@ -85,9 +89,9 @@ liip_imagine:

If enabled both first one will be [Cache](./cache.md), then [Proxy](./proxy.md) and after all process delegates to AwsS3 resolver.

## Object URL Options
## Object GET Options

In order to make use of the object URL options, you can simply add a call to the service, to alter those options you need.
In order to make use of the object GET options, you can simply add a call to the service, to alter those options you need.

``` yaml
services:
Expand All @@ -97,8 +101,8 @@ services:
- "@acme.amazon_s3"
- "%amazon_s3.bucket%"
calls:
# This calls $service->setObjectUrlOption('Scheme', 'https');
- [ setObjectUrlOption, [ 'Scheme', 'https' ] ]
# This calls $service->setGetOption('Scheme', 'https');
- [ setGetOption, [ 'Scheme', 'https' ] ]
tags:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```
Expand All @@ -118,5 +122,50 @@ services:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```

## Object PUT Options

Similar to Object GET Options you can configure additional options to be passed to S3 when storing objects.
This is useful, for example, to configure Cache-control headers returned when serving object from S3.
See [S3 SDK documentation](http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.S3.S3Client.html#_putObject) for the list of available options.

Note, that the following options are configured automatically and will be ignored, even if you configure it via ObjectOptions:
* `ACL`
* `Bucket`
* `Key`
* `Body`
* `ContentType`

In order to make use of the object PUT options, you can simply add a call to the service, to alter those options you need.

``` yaml
services:
acme.imagine.cache.resolver.amazon_s3:
class: Liip\ImagineBundle\Imagine\Cache\Resolver\AwsS3Resolver
arguments:
- "@acme.amazon_s3"
- "%amazon_s3.bucket%"
calls:
# This calls $service->setPutOption('CacheControl', 'max-age=86400');
- [ setPutOption, [ 'CacheControl', 'max-age=86400' ] ]
tags:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```

You can also use the constructor of the resolver to directly inject multiple options.

``` yaml
services:
acme.imagine.cache.resolver.amazon_s3:
class: Liip\ImagineBundle\Imagine\Cache\Resolver\AwsS3Resolver
arguments:
- "@acme.amazon_s3"
- "%amazon_s3.bucket%"
- "public-read" # Aws\S3\Enum\CannedAcl::PUBLIC_READ (default)
- { Scheme: https }
- { CacheControl: 'max-age=86400' }
tags:
- { name: 'liip_imagine.cache.resolver', resolver: 'amazon_s3' }
```

- [Back to cache resolvers](../cache-resolvers.md)
- [Back to the index](../index.md)
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public function testCreateResolverDefinitionOnCreate()
'bucket' => 'theBucket',
'acl' => 'theAcl',
'url_options' => array('fooKey' => 'fooVal'),
'get_options' => array(),
'put_options' => array('barKey' => 'barVal'),
'cache' => false,
'proxies' => array()
));
Expand All @@ -57,6 +59,30 @@ public function testCreateResolverDefinitionOnCreate()
$this->assertEquals('theBucket', $resolverDefinition->getArgument(1));
$this->assertEquals('theAcl', $resolverDefinition->getArgument(2));
$this->assertEquals(array('fooKey' => 'fooVal'), $resolverDefinition->getArgument(3));
$this->assertEquals(array('barKey' => 'barVal'), $resolverDefinition->getArgument(4));
}

public function testOverrideDeprecatedUrlOptionsWithNewGetOptions()
{
$container = new ContainerBuilder;

$resolver = new AwsS3ResolverFactory;

$resolver->create($container, 'theResolverName', array(
'client_config' => array(),
'bucket' => 'theBucket',
'acl' => 'theAcl',
'url_options' => array('fooKey' => 'fooVal', 'barKey' => 'barVal'),
'get_options' => array('fooKey' => 'fooVal_overridden'),
'put_options' => array(),
'cache' => false,
'proxies' => array()
));

$this->assertTrue($container->hasDefinition('liip_imagine.cache.resolver.theresolvername'));

$resolverDefinition = $container->getDefinition('liip_imagine.cache.resolver.theresolvername');
$this->assertEquals(array('fooKey' => 'fooVal_overridden', 'barKey' => 'barVal'), $resolverDefinition->getArgument(3));
}

public function testCreateS3ClientDefinitionOnCreate()
Expand All @@ -70,6 +96,8 @@ public function testCreateS3ClientDefinitionOnCreate()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => false,
'proxies' => array()
));
Expand All @@ -94,6 +122,8 @@ public function testWrapResolverWithProxyOnCreateWithoutCache()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => false,
'proxies' => array('foo')
));
Expand Down Expand Up @@ -127,6 +157,8 @@ public function testWrapResolverWithCacheOnCreateWithoutProxy()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => 'theCacheServiceId',
'proxies' => array()
));
Expand Down Expand Up @@ -161,6 +193,8 @@ public function testWrapResolverWithProxyAndCacheOnCreate()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache' => 'theCacheServiceId',
'proxies' => array('foo')
));
Expand Down Expand Up @@ -203,6 +237,8 @@ public function testSetCachePrefixIfDefined()
'bucket' => 'aBucket',
'acl' => 'aAcl',
'url_options' => array(),
'get_options' => array(),
'put_options' => array(),
'cache_prefix' => 'theCachePrefix',
'cache' => null,
'proxies' => array()
Expand Down Expand Up @@ -282,6 +318,14 @@ public function testProcessCorrectlyOptionsOnAddConfiguration()
'theKey' => 'theUrlOptionsVal',
'theOtherKey' => 'theOtherUrlOptionsValue'
);
$expectedGetOptions = array(
'theKey' => 'theGetOptionsVal',
'theOtherKey' => 'theOtherGetOptionsValue'
);
$expectedObjectOptions = array(
'theKey' => 'theObjectOptionsVal',
'theOtherKey' => 'theOtherObjectOptionsValue'
);
$expectedBucket = 'theBucket';
$expectedAcl = 'theAcl';
$expectedCachePrefix = 'theCachePrefix';
Expand All @@ -298,6 +342,8 @@ public function testProcessCorrectlyOptionsOnAddConfiguration()
'acl' => $expectedAcl,
'client_config' => $expectedClientConfig,
'url_options' => $expectedUrlOptions,
'get_options' => $expectedGetOptions,
'put_options' => $expectedObjectOptions,
'cache_prefix' => $expectedCachePrefix,
)
));
Expand All @@ -314,6 +360,12 @@ public function testProcessCorrectlyOptionsOnAddConfiguration()
$this->assertArrayHasKey('url_options', $config);
$this->assertEquals($expectedUrlOptions, $config['url_options']);

$this->assertArrayHasKey('get_options', $config);
$this->assertEquals($expectedGetOptions, $config['get_options']);

$this->assertArrayHasKey('put_options', $config);
$this->assertEquals($expectedObjectOptions, $config['put_options']);

$this->assertArrayHasKey('cache_prefix', $config);
$this->assertEquals($expectedCachePrefix, $config['cache_prefix']);
}
Expand Down Expand Up @@ -341,6 +393,9 @@ public function testAddDefaultOptionsIfNotSetOnAddConfiguration()
$this->assertArrayHasKey('url_options', $config);
$this->assertEquals(array(), $config['url_options']);

$this->assertArrayHasKey('get_options', $config);
$this->assertEquals(array(), $config['get_options']);

$this->assertArrayHasKey('cache_prefix', $config);
$this->assertNull($config['cache_prefix']);
}
Expand Down
23 changes: 23 additions & 0 deletions Tests/Imagine/Cache/Resolver/AwsS3ResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ public function testCreateObjectOnAmazon()
$this->assertNull($resolver->store($binary, 'thumb/foobar.jpg', 'thumb'));
}

public function testObjectOptionsPassedToS3ClintOnCreate()
{
$binary = new Binary('aContent', 'image/jpeg', 'jpeg');

$s3 = $this->getS3ClientMock();
$s3
->expects($this->once())
->method('putObject')
->with(array(
'CacheControl' => 'max-age=86400',
'ACL' => 'public-read',
'Bucket' => 'images.example.com',
'Key' => 'filter/images/foobar.jpg',
'Body' => 'aContent',
'ContentType' => 'image/jpeg',
))
;

$resolver = new AwsS3Resolver($s3, 'images.example.com');
$resolver->setPutOption('CacheControl', 'max-age=86400');
$resolver->store($binary, 'images/foobar.jpg', 'filter');
}

public function testIsStoredChecksObjectExistence()
{
$s3 = $this->getS3ClientMock();
Expand Down

0 comments on commit 5be10ce

Please sign in to comment.