1
0

initial commit

This commit is contained in:
frostealth
2016-04-08 15:50:37 +06:00
commit 9cfd06866e
40 changed files with 2312 additions and 0 deletions

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_new_line = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
.gitattributes export-ignore
.gitignore export-ignore
.editorconfig export-ignore
grumphp.yml export-ignore

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
.DS_Store
thumbs.db
*.class
*.pyc
*.pyo
*.swp
.project
.settings
.idea
composer.phar
composer.lock
vendor

21
LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Ivan Kudinov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

106
README.md Normal file
View File

@@ -0,0 +1,106 @@
# Yii2 AWS S3
An Amazon S3 component for Yii2.
[![License](https://poser.pugx.org/frostealth/yii2-aws-s3/license)](https://github.com/frostealth/yii2-aws-s3/blob/2.x/LICENSE.md)
[![Latest Stable Version](https://poser.pugx.org/frostealth/yii2-aws-s3/v/stable)](https://packagist.org/packages/frostealth/yii2-aws-s3)
[![Total Downloads](https://poser.pugx.org/frostealth/yii2-aws-s3/downloads)](https://packagist.org/packages/frostealth/yii2-aws-s3)
[![Latest Unstable Version](https://poser.pugx.org/frostealth/yii2-aws-s3/v/unstable)](https://packagist.org/packages/frostealth/yii2-aws-s3)
## Installation
1. Run the [Composer](http://getcomposer.org/download/) command to install the latest stable version:
```bash
composer require frostealth/yii2-aws-s3 ~2.0@stable
```
2. Add the component to `config/main.php`
```php
'components' => [
// ...
's3' => [
'class' => 'frostealth\yii2\aws\s3\Service',
'credentials' => [ // Aws\Credentials\CredentialsInterface|array|callable
'key' => 'my-key',
'secret' => 'my-secret',
],
'region' => 'my-region',
'defaultBucket' => 'my-bucket',
'defaultAcl' => 'public-read',
],
// ...
],
```
## Basic usage
### Usage the command factory and additional params
```php
/** @var \frostealth\yii2\aws\s3\Service $s3 */
$s3 = Yii::$app->get('s3');
/** @var \Aws\ResultInterface $result */
$result = $s3->commands()->get('filename.ext')->saveAs('/path/to/local/file.ext')->execute();
$result = $s3->commands()->put('filename.ext', 'body')->setContentType('text/plain')->execute();
$result = $s3->commands()->delete('filename.ext')->execute();
$result = $s3->commands()->upload('filename.ext', '/path/to/local/file.ext')->setAcl('private')->execute();
$result = $s3->commands()->restore('filename.ext', $days = 7)->execute();
$isExisting = $s3->commands()->exist('filename.ext')->execute();
$url = $s3->commands()->getUrl('filename.ext')->execute();
$signedUrl = $s3->commands()->getPresignedUrl('filename.ext', '+2 days')->execute();
```
### Short syntax
```php
/** @var \frostealth\yii2\aws\s3\Service $s3 */
$s3 = Yii::$app->get('s3');
/** @var \Aws\ResultInterface $result */
$result = $s3->get('filename.ext');
$result = $s3->put('filename.ext', 'body');
$result = $s3->delete('filename.ext');
$result = $s3->upload('filename.ext', '/path/to/local/file.ext');
$result = $s3->restore('filename.ext', $days = 7);
$isExisting = $s3->exist('filename.ext');
$url = $s3->getUrl('filename.ext');
$signedUrl = $s3->getPresignedUrl('filename.ext', '+2 days');
```
### Asynchronous execution
```php
/** @var \frostealth\yii2\aws\s3\Service $s3 */
$s3 = Yii::$app->get('s3');
/** @var \GuzzleHttp\Promise\PromiseInterface $promise */
$promise = $s3->commands()->get('filename.ext')->async()->execute();
$promise = $s3->commands()->put('filename.ext', 'body')->async()->execute();
$promise = $s3->commands()->delete('filename.ext')->async()->execute();
$promise = $s3->commands()->upload('filename.ext', 'source')->async()->execute();
```
## License
Yii2 AWS S3 is licensed under the MIT License.
See the [LICENSE.md](LICENSE.md) file for more information.

36
composer.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "frostealth/yii2-aws-s3",
"type": "yii2-extension",
"description": "An Amazon S3 component for Yii2",
"keywords": ["yii2", "aws", "s3", "aws-s3", "yii2-aws", "yii2-s3"],
"homepage": "https://github.com/frostealth/yii2-aws-s3",
"license": "MIT",
"authors": [
{
"name": "Ivan Kudinov",
"email": "i.kudinov@frostealth.ru",
"homepage": "http://frostealth.ru"
},
{
"name": "Constantine Chuprik",
"email": "constantinchuprik@gmail.com"
}
],
"support": {
"issues": "https://github.com/frostealth/yii2-aws-s3/issues?state=open"
},
"require": {
"php": ">=7.0.0",
"aws/aws-sdk-php": "~3.17",
"yiisoft/yii2": "~2.0"
},
"require-dev": {
"phpro/grumphp": "~0.8",
"squizlabs/php_codesniffer": "~2.3"
},
"autoload": {
"psr-4": {
"frostealth\\yii2\\aws\\s3\\": "src/"
}
}
}

12
grumphp.yml Normal file
View File

@@ -0,0 +1,12 @@
parameters:
git_dir: .
bin_dir: ./vendor/bin
tasks:
git_blacklist:
keywords:
- "die("
- "die;"
- "exit("
- "exit;"
- "print_r("
- "var_dump("

273
src/Service.php Normal file
View File

@@ -0,0 +1,273 @@
<?php
namespace frostealth\yii2\aws\s3;
use Aws\ResultInterface;
use frostealth\yii2\aws\s3\base\CommandFactory;
use frostealth\yii2\aws\s3\interfaces\commands\Command;
use frostealth\yii2\aws\s3\interfaces\HandlerResolver;
use frostealth\yii2\aws\s3\interfaces\Service as ServiceInterface;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* Class Service
*
* @property HandlerResolver $resolver
*
* @method ResultInterface get(string $filename)
* @method ResultInterface put(string $filename, $body)
* @method ResultInterface delete(string $filename)
* @method ResultInterface upload(string $filename, $source)
* @method ResultInterface restore(string $filename, int $days)
* @method bool exist(string $filename)
* @method string getUrl(string $filename)
* @method string getPresignedUrl(string $filename, $expires)
*
* @package frostealth\yii2\aws\s3
*/
class Service extends Component implements ServiceInterface
{
const ACL_PRIVATE = 'private';
const ACL_PUBLIC_READ = 'public-read';
const ACL_PUBLIC_READ_WRITE = 'public-read-write';
const ACL_AWS_EXEC_READ = 'aws-exec-read';
const ACL_AUTHENTICATED_READ = 'authenticated-read';
const ACL_BUCKET_OWNER_READ = 'bucket-owner-read';
const ALC_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control';
/** @var string */
public $defaultBucket;
/** @var string */
public $defaultAcl = '';
/** @var array S3Client config */
protected $clientConfig = ['version' => '2006-03-01'];
/** @var array */
private $components = [];
/** @var array */
private $definitions = [
'client' => ['class' => 'Aws\S3\S3Client'],
'resolver' => ['class' => 'frostealth\yii2\aws\s3\base\HandlerResolver'],
'bus' => ['class' => 'frostealth\yii2\aws\s3\base\Bus'],
'builder' => ['class' => 'frostealth\yii2\aws\s3\base\CommandBuilder'],
'factory' => ['class' => 'frostealth\yii2\aws\s3\base\CommandFactory'],
];
/**
* Initializes the object.
* This method is invoked at the end of the constructor after the object is initialized with the
* given configuration.
*/
public function init()
{
if (empty($this->clientConfig['credentials'])) {
throw new InvalidConfigException('Credentials is not set.');
}
if (empty($this->clientConfig['region'])) {
throw new InvalidConfigException('Region is not set.');
}
if (empty($this->defaultBucket)) {
throw new InvalidConfigException('Default bucket name is not set.');
}
foreach ($this->definitions as $name => $definition) {
$this->components[$name] = $this->components[$name] ?? $definition;
}
}
/**
* Executes a command.
*
* @param \frostealth\yii2\aws\s3\interfaces\commands\Command $command
*
* @return mixed
*/
public function execute(Command $command)
{
return $this->getComponent('bus')->execute($command);
}
/**
* Creates a command with default params.
*
* @param string $commandClass
*
* @return \frostealth\yii2\aws\s3\interfaces\commands\Command
*/
public function create(string $commandClass): Command
{
return $this->getComponent('builder')->build($commandClass);
}
/**
* Returns command factory.
*
* @return \frostealth\yii2\aws\s3\base\CommandFactory
*/
public function commands(): CommandFactory
{
return $this->getComponent('factory');
}
/**
* Returns handler resolver.
*
* @return \frostealth\yii2\aws\s3\interfaces\HandlerResolver
*/
public function getResolver(): HandlerResolver
{
return $this->getComponent('resolver');
}
/**
* @param string $name
* @param array $params
*
* @return mixed
*/
public function __call($name, $params)
{
if (method_exists($this->commands(), $name)) {
$result = call_user_func_array([$this->commands(), $name], $params);
return $result instanceof Command ? $this->execute($result) : $result;
}
return parent::__call($name, $params);
}
/**
* @param \Aws\Credentials\CredentialsInterface|array|callable $credentials
*/
public function setCredentials($credentials)
{
$this->clientConfig['credentials'] = $credentials;
}
/**
* @param string $region
*/
public function setRegion(string $region)
{
$this->clientConfig['region'] = $region;
}
/**
* @param array|bool $debug
*/
public function setDebug($debug)
{
$this->clientConfig['debug'] = $debug;
}
/**
* @param string|array|object $resolver
*/
public function setResolver($resolver)
{
$this->setComponent('resolver', $resolver);
}
/**
* @param string|array|object $bus
*/
public function setBus($bus)
{
$this->setComponent('bus', $bus);
}
/**
* @param string|array|object $builder
*/
public function setBuilder($builder)
{
$this->setComponent('builder', $builder);
}
/**
* @param string|array|object $factory
*/
public function setFactory($factory)
{
$this->setComponent('factory', $factory);
}
/**
* @param string $name
*
* @return object
*/
protected function getComponent(string $name)
{
if (!is_object($this->components[$name])) {
$this->components[$name] = $this->createComponent($name);
}
return $this->components[$name];
}
/**
* @param string $name
* @param array|object|string $definition
*/
protected function setComponent(string $name, $definition)
{
if (!is_object($definition)) {
$definition = !is_array($definition) ? ['class' => $definition] : $definition;
$definition = ArrayHelper::merge($this->definitions[$name], $definition);
}
$this->components[$name] = $definition;
}
/**
* @param string $name
*
* @return object
* @throws \yii\base\InvalidConfigException
*/
protected function createComponent(string $name)
{
$definition = $this->components[$name];
$params = $this->getComponentParams($name);
return \Yii::createObject($definition, $params);
}
/**
* @param string $name
*
* @return array
*/
protected function getComponentParams(string $name): array
{
switch ($name) {
case 'client':
$params = [$this->clientConfig];
break;
case 'resolver':
$params = [$this->getComponent('client')];
break;
case 'bus':
$params = [$this->getComponent('resolver')];
break;
case 'builder':
$params = [$this->getComponent('bus'), $this->defaultBucket, $this->defaultAcl];
break;
case 'factory':
$params = [$this->getComponent('builder')];
break;
default:
$params = [];
}
return $params;
}
}

38
src/base/Bus.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
namespace frostealth\yii2\aws\s3\base;
use frostealth\yii2\aws\s3\interfaces;
/**
* Class Bus
*
* @package frostealth\yii2\aws\s3\base
*/
class Bus implements interfaces\Bus
{
/** @var interfaces\HandlerResolver */
protected $resolver;
/**
* Bus constructor.
*
* @param \frostealth\yii2\aws\s3\interfaces\HandlerResolver $inflector
*/
public function __construct(interfaces\HandlerResolver $inflector)
{
$this->resolver = $inflector;
}
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\Command $command
*
* @return mixed
*/
public function execute(interfaces\commands\Command $command)
{
$handler = $this->resolver->resolve($command);
return call_user_func([$handler, 'handle'], $command);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace frostealth\yii2\aws\s3\base;
use frostealth\yii2\aws\s3\interfaces;
/**
* Class CommandBuilder
*
* @package frostealth\yii2\aws\s3\base
*/
class CommandBuilder implements interfaces\CommandBuilder
{
/** @var string default bucket name */
protected $bucket;
/** @var string default acl */
protected $acl;
/** @var interfaces\Bus */
protected $bus;
/**
* CommandBuilder constructor.
*
* @param \frostealth\yii2\aws\s3\interfaces\Bus $bus
* @param string $bucket
* @param string $acl
*/
public function __construct(interfaces\Bus $bus, string $bucket = '', string $acl = '')
{
$this->bus = $bus;
$this->bucket = $bucket;
$this->acl = $acl;
}
/**
* @param string $className
*
* @return \frostealth\yii2\aws\s3\interfaces\commands\Command
* @throws \yii\base\InvalidConfigException
*/
public function build(string $className): interfaces\commands\Command
{
$params = is_subclass_of($className, interfaces\commands\ExecutableCommand::class) ? [$this->bus] : [];
/** @var interfaces\commands\Command $command */
$command = \Yii::createObject($className, $params);
$this->prepareCommand($command);
return $command;
}
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\Command $command
*/
protected function prepareCommand(interfaces\commands\Command $command)
{
if ($command instanceof interfaces\commands\HasBucket) {
$command->setBucket($this->bucket);
}
if ($command instanceof interfaces\commands\HasAcl) {
$command->setAcl($this->acl);
}
}
}

160
src/base/CommandFactory.php Normal file
View File

@@ -0,0 +1,160 @@
<?php
namespace frostealth\yii2\aws\s3\base;
use frostealth\yii2\aws\s3\commands\DeleteCommand;
use frostealth\yii2\aws\s3\commands\ExistCommand;
use frostealth\yii2\aws\s3\commands\GetCommand;
use frostealth\yii2\aws\s3\commands\GetPresignedUrlCommand;
use frostealth\yii2\aws\s3\commands\GetUrlCommand;
use frostealth\yii2\aws\s3\commands\PutCommand;
use frostealth\yii2\aws\s3\commands\RestoreCommand;
use frostealth\yii2\aws\s3\commands\UploadCommand;
use frostealth\yii2\aws\s3\interfaces;
/**
* Class CommandFactory
*
* @package frostealth\yii2\aws\s3\base
*/
class CommandFactory
{
/** @var \frostealth\yii2\aws\s3\interfaces\CommandBuilder */
private $builder;
/**
* CommandFactory constructor.
*
* @param \frostealth\yii2\aws\s3\interfaces\CommandBuilder $builder
*/
public function __construct(interfaces\CommandBuilder $builder)
{
$this->builder = $builder;
}
/**
* @param string $filename
*
* @return \frostealth\yii2\aws\s3\commands\GetCommand
*/
public function get(string $filename): GetCommand
{
/** @var GetCommand $command */
$command = $this->builder->build(GetCommand::class);
$command->setFilename($filename);
return $command;
}
/**
* @param string $filename
* @param mixed $body
*
* @return \frostealth\yii2\aws\s3\commands\PutCommand
*/
public function put(string $filename, $body): PutCommand
{
/** @var PutCommand $command */
$command = $this->builder->build(PutCommand::class);
$command->setFilename($filename)->setBody($body);
return $command;
}
/**
* @param string $filename
*
* @return \frostealth\yii2\aws\s3\commands\DeleteCommand
*/
public function delete(string $filename): DeleteCommand
{
/** @var DeleteCommand $command */
$command = $this->builder->build(DeleteCommand::class);
$command->setFilename($filename);
return $command;
}
/**
* @param string $filename
* @param mixed $source
*
* @return \frostealth\yii2\aws\s3\commands\UploadCommand
*/
public function upload(string $filename, $source): UploadCommand
{
/** @var UploadCommand $command */
$command = $this->builder->build(UploadCommand::class);
$command->setFilename($filename)->setSource($source);
return $command;
}
/**
* @param string $filename
* @param int $days lifetime of the active copy in days
*
* @return \frostealth\yii2\aws\s3\commands\RestoreCommand
*/
public function restore(string $filename, int $days): RestoreCommand
{
/** @var RestoreCommand $command */
$command = $this->builder->build(RestoreCommand::class);
$command->setFilename($filename)->setDays($days);
return $command;
}
/**
* @param string $filename
*
* @return \frostealth\yii2\aws\s3\commands\ExistCommand
*/
public function exist(string $filename): ExistCommand
{
/** @var ExistCommand $command */
$command = $this->builder->build(ExistCommand::class);
$command->setFilename($filename);
return $command;
}
/**
* @param string $filename
*
* @return \frostealth\yii2\aws\s3\commands\GetUrlCommand
*/
public function getUrl(string $filename): GetUrlCommand
{
/** @var GetUrlCommand $command */
$command = $this->builder->build(GetUrlCommand::class);
$command->setFilename($filename);
return $command;
}
/**
* @param string $filename
* @param mixed $expires
*
* @return \frostealth\yii2\aws\s3\commands\GetPresignedUrlCommand
*/
public function getPresignedUrl(string $filename, $expires): GetPresignedUrlCommand
{
/** @var GetPresignedUrlCommand $command */
$command = $this->builder->build(GetPresignedUrlCommand::class);
$command->setFilename($filename)->setExpires($expires);
return $command;
}
/**
* @param string $commandClass
*
* @return \frostealth\yii2\aws\s3\interfaces\commands\Command
*/
final protected function createCommand(string $commandClass)
{
return $this->builder->build($commandClass);
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace frostealth\yii2\aws\s3\base;
use Aws\S3\S3Client;
use frostealth\yii2\aws\s3\handlers\PlainCommandHandler;
use frostealth\yii2\aws\s3\interfaces;
use yii\base\Exception;
use yii\base\Object;
/**
* Class HandlerResolver
*
* @package frostealth\yii2\aws\s3\base
*/
class HandlerResolver extends Object implements interfaces\HandlerResolver
{
/** @var array */
protected $handlers = [];
/** @var \Aws\S3\S3Client */
protected $s3Client;
/**
* HandlerResolver constructor.
*
* @param \Aws\S3\S3Client $s3Client
* @param array $config
*/
public function __construct(S3Client $s3Client, array $config = [])
{
$this->s3Client = $s3Client;
parent::__construct($config);
}
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\Command $command
*
* @return \frostealth\yii2\aws\s3\interfaces\handlers\Handler
* @throws \yii\base\Exception
*/
public function resolve(interfaces\commands\Command $command): interfaces\handlers\Handler
{
$commandClass = get_class($command);
if (isset($this->handlers[$commandClass])) {
$handler = $this->handlers[$commandClass];
return is_object($handler) ? $handler : $this->createHandler($handler);
}
if ($command instanceof interfaces\commands\PlainCommand) {
return $this->createHandler(PlainCommandHandler::class);
}
$handlerClass = $commandClass . 'Handler';
if (class_exists($handlerClass)) {
return $this->createHandler($handlerClass);
}
$handlerClass = str_replace('\\commands\\', '\\handlers\\', $handlerClass);
if (class_exists($handlerClass)) {
return $this->createHandler($handlerClass);
}
throw new Exception("Could not terminate the handler of a command of type \"{$commandClass}\"");
}
/**
* @param string $commandClass
* @param mixed $handler
*/
public function bindHandler(string $commandClass, $handler)
{
$this->handlers[$commandClass] = $handler;
}
/**
* @param array $handlers
*/
public function setHandlers(array $handlers)
{
foreach ($handlers as $commandClass => $handler) {
$this->bindHandler($commandClass, $handler);
}
}
/**
* @param string|array $type
*
* @return \frostealth\yii2\aws\s3\interfaces\handlers\Handler
* @throws \yii\base\InvalidConfigException
*/
protected function createHandler($type): interfaces\handlers\Handler
{
return \Yii::createObject($type, [$this->s3Client]);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace frostealth\yii2\aws\s3\base\commands;
use frostealth\yii2\aws\s3\interfaces\Bus;
use frostealth\yii2\aws\s3\interfaces\commands\ExecutableCommand as ExecutableCommandInterface;
/**
* Class ExecutableCommand
*
* @package frostealth\yii2\aws\s3\base\commands
*/
abstract class ExecutableCommand implements ExecutableCommandInterface
{
/** @var \frostealth\yii2\aws\s3\interfaces\Bus */
private $bus;
/**
* ExecutableCommand constructor.
*
* @param \frostealth\yii2\aws\s3\interfaces\Bus $bus
*/
public function __construct(Bus $bus)
{
$this->bus = $bus;
}
/**
* @return mixed
*/
public function execute()
{
return $this->bus->execute($this);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace frostealth\yii2\aws\s3\base\commands\traits;
/**
* Trait Async
*
* @package frostealth\yii2\aws\s3\base\commands\traits
*/
trait Async
{
/** @var bool */
private $isAsync = false;
/**
* @param bool $async
*
* @return $this
*/
final public function async(bool $async = true)
{
$this->isAsync = $async;
return $this;
}
/**
* @return bool
*/
final public function isAsync(): bool
{
return $this->isAsync;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace frostealth\yii2\aws\s3\base\commands\traits;
/**
* Trait Options
*
* @package frostealth\yii2\aws\s3\base\commands\traits
*/
trait Options
{
/** @var array */
protected $options = [];
/**
* @param array $value
*
* @return $this
*/
final public function setOptions(array $value)
{
$this->options = $value;
return $this;
}
/**
* @return array
*/
final public function getOptions(): array
{
return $this->options;
}
/**
* @param string $name
* @param mixed $value
*
* @return $this
*/
final public function setOption(string $name, $value)
{
$this->options[$name] = $value;
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace frostealth\yii2\aws\s3\base\handlers;
use Aws\S3\S3Client;
use frostealth\yii2\aws\s3\interfaces\handlers\Handler as HandlerInterface;
/**
* Class Handler
*
* @package frostealth\yii2\aws\s3\base\handlers
*/
abstract class Handler implements HandlerInterface
{
/** @var S3Client */
protected $s3Client;
/**
* Handler constructor.
*
* @param \Aws\S3\S3Client $s3Client
*/
public function __construct(S3Client $s3Client)
{
$this->s3Client = $s3Client;
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use Aws\ResultInterface;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\base\commands\traits\Async;
use frostealth\yii2\aws\s3\base\commands\traits\Options;
use frostealth\yii2\aws\s3\interfaces\commands\Asynchronous;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
use frostealth\yii2\aws\s3\interfaces\commands\PlainCommand;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Class DeleteCommand
*
* @method ResultInterface|PromiseInterface execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class DeleteCommand extends ExecutableCommand implements PlainCommand, HasBucket, Asynchronous
{
use Async;
use Options;
/** @var array */
protected $args = [];
/**
* @return string
*/
public function getBucket(): string
{
return $this->args['Bucket'] ?? '';
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->args['Bucket'] = $bucket;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return $this->args['Key'] ?? '';
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->args['Key'] = $filename;
return $this;
}
/**
* @return string
*/
public function getVersionId(): string
{
return $this->args['VersionId'] ?? '';
}
/**
* @param string $versionId
*
* @return $this
*/
public function setVersionId(string $versionId)
{
$this->args['VersionId'] = $versionId;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return 'DeleteObject';
}
/**
* @return array
*/
public function toArgs(): array
{
return array_replace($this->options, $this->args);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\base\commands\traits\Options;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
/**
* Class ExistCommand
*
* @method bool execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class ExistCommand extends ExecutableCommand implements HasBucket
{
use Options;
/** @var string */
protected $bucket;
/** @var string */
protected $filename;
/**
* @return string
*/
public function getBucket(): string
{
return $this->bucket;
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->bucket = $bucket;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return $this->filename;
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->filename = $filename;
return $this;
}
}

108
src/commands/GetCommand.php Normal file
View File

@@ -0,0 +1,108 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use Aws\ResultInterface;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\base\commands\traits\Async;
use frostealth\yii2\aws\s3\base\commands\traits\Options;
use frostealth\yii2\aws\s3\interfaces\commands\Asynchronous;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
use frostealth\yii2\aws\s3\interfaces\commands\PlainCommand;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Class GetCommand
*
* @method ResultInterface|PromiseInterface execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class GetCommand extends ExecutableCommand implements PlainCommand, HasBucket, Asynchronous
{
use Async;
use Options;
/** @var array */
protected $args = [];
/**
* @return string
*/
public function getBucket(): string
{
return $this->args['Bucket'] ?? '';
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->args['Bucket'] = $bucket;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return $this->args['Key'] ?? '';
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->args['Key'] = $filename;
return $this;
}
/**
* @param string $value
*
* @return $this
*/
public function saveAs(string $value)
{
$this->args['SaveAs'] = $value;
return $this;
}
/**
* @param string $ifMatch
*
* @return $this
*/
public function ifMatch(string $ifMatch)
{
$this->args['IfMatch'] = $ifMatch;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return 'GetObject';
}
/**
* @return array
*/
public function toArgs(): array
{
return array_replace($this->options, $this->args);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
/**
* Class GetPresignedUrlCommand
*
* @method string execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class GetPresignedUrlCommand extends ExecutableCommand implements HasBucket
{
/** @var array */
protected $args = [];
/** @var mixed */
protected $expires;
/**
* @return string
*/
public function getBucket(): string
{
return $this->args['Bucket'] ?? '';
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->args['Bucket'] = $bucket;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return $this->args['Key'] ?? '';
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->args['Key'] = $filename;
return $this;
}
/**
* @return mixed
*/
public function getExpires()
{
return $this->expires;
}
/**
* @param int|string|\DateTime $expires
*
* @return $this
*/
public function setExpires($expires)
{
$this->expires = $expires;
return $this;
}
/**
* @return array
*/
public function getArgs(): array
{
return $this->args;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
/**
* Class GetUrlCommand
*
* @method string execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class GetUrlCommand extends ExecutableCommand implements HasBucket
{
/** @var string */
protected $bucket;
/** @var string */
protected $filename;
/**
* @return string
*/
public function getBucket(): string
{
return $this->bucket;
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->bucket = $bucket;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return $this->filename;
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->filename = $filename;
return $this;
}
}

185
src/commands/PutCommand.php Normal file
View File

@@ -0,0 +1,185 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use Aws\ResultInterface;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\base\commands\traits\Async;
use frostealth\yii2\aws\s3\base\commands\traits\Options;
use frostealth\yii2\aws\s3\interfaces\commands\Asynchronous;
use frostealth\yii2\aws\s3\interfaces\commands\HasAcl;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
use frostealth\yii2\aws\s3\interfaces\commands\PlainCommand;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Class PutCommand
*
* @method ResultInterface|PromiseInterface execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class PutCommand extends ExecutableCommand implements PlainCommand, HasBucket, HasAcl, Asynchronous
{
use Async;
use Options;
/** @var array */
protected $args = [];
/**
* @return string
*/
public function getBucket(): string
{
return $this->args['Bucket'] ?? '';
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->args['Bucket'] = $bucket;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return $this->args['Key'] ?? '';
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->args['Key'] = $filename;
return $this;
}
/**
* @return string
*/
public function getAcl(): string
{
return $this->args['ACL'] ?? '';
}
/**
* @param string $acl
*
* @return $this
*/
public function setAcl(string $acl)
{
$this->args['ACL'] = $acl;
return $this;
}
/**
* @return mixed
*/
public function getBody()
{
return $this->args['Body'] ?? null;
}
/**
* @param mixed $body
*
* @return $this
*/
public function setBody($body)
{
$this->args['Body'] = $body;
return $this;
}
/**
* @return array
*/
public function getMetadata(): array
{
return $this->args['Metadata'] ?? [];
}
/**
* @param array $metadata
*
* @return $this
*/
public function setMetadata(array $metadata)
{
$this->args['Metadata'] = $metadata;
return $this;
}
/**
* @return string
*/
public function getContentType(): string
{
return $this->args['ContentType'] ?? '';
}
/**
* @param string $contentType
*
* @return $this
*/
public function setContentType(string $contentType)
{
$this->args['ContentType'] = $contentType;
return $this;
}
/**
* @return mixed
*/
public function getExpires()
{
return $this->args['Expires'] ?? null;
}
/**
* @param mixed $expires
*
* @return $this
*/
public function setExpires($expires)
{
$this->args['Expires'] = $expires;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return 'PutObject';
}
/**
* @return array
*/
public function toArgs(): array
{
return array_replace($this->options, $this->args);
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use Aws\ResultInterface;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\base\commands\traits\Async;
use frostealth\yii2\aws\s3\interfaces\commands\Asynchronous;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
use frostealth\yii2\aws\s3\interfaces\commands\PlainCommand;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Class RestoreCommand
*
* @method ResultInterface|PromiseInterface execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class RestoreCommand extends ExecutableCommand implements PlainCommand, HasBucket, Asynchronous
{
use Async;
/** @var array */
protected $args = [];
/**
* @return string
*/
public function getBucket(): string
{
return $this->args['Bucket'] ?? '';
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->args['Bucket'] = $bucket;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return $this->args['Key'] ?? '';
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->args['Key'] = $filename;
return $this;
}
/**
* @return int lifetime of the active copy in days
*/
public function getDays(): int
{
return $this->args['Days'] ?? 0;
}
/**
* @param int $days lifetime of the active copy in days
*
* @return $this
*/
public function setDays(int $days)
{
$this->args['Days'] = $days;
return $this;
}
/**
* @return string
*/
public function getVersionId(): string
{
return $this->args['VersionId'] ?? '';
}
/**
* @param string $versionId
*
* @return $this
*/
public function setVersionId(string $versionId)
{
$this->args['VersionId'] = $versionId;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return 'RestoreObject';
}
/**
* @return array
*/
public function toArgs(): array
{
return $this->args;
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace frostealth\yii2\aws\s3\commands;
use Aws\ResultInterface;
use frostealth\yii2\aws\s3\base\commands\ExecutableCommand;
use frostealth\yii2\aws\s3\base\commands\traits\Async;
use frostealth\yii2\aws\s3\interfaces\commands\Asynchronous;
use frostealth\yii2\aws\s3\interfaces\commands\HasAcl;
use frostealth\yii2\aws\s3\interfaces\commands\HasBucket;
use GuzzleHttp\Promise\PromiseInterface;
/**
* Class UploadCommand
*
* @method ResultInterface|PromiseInterface execute()
*
* @package frostealth\yii2\aws\s3\commands
*/
class UploadCommand extends ExecutableCommand implements HasBucket, HasAcl, Asynchronous
{
use Async;
/** @var string */
protected $bucket;
/** @var string */
protected $acl;
/** @var mixed */
protected $source;
/** @var string */
protected $filename;
/** @var array */
protected $options = [];
/**
* @return string
*/
public function getBucket(): string
{
return (string)$this->bucket;
}
/**
* @param string $bucket
*
* @return $this
*/
public function setBucket(string $bucket)
{
$this->bucket = $bucket;
return $this;
}
/**
* @return string
*/
public function getAcl(): string
{
return (string)$this->acl;
}
/**
* @param string $acl
*
* @return $this
*/
public function setAcl(string $acl)
{
$this->acl = $acl;
return $this;
}
/**
* @return mixed
*/
public function getSource()
{
return $this->source;
}
/**
* @param mixed $source
*
* @return $this
*/
public function setSource($source)
{
$this->source = $source;
return $this;
}
/**
* @return string
*/
public function getFilename(): string
{
return (string)$this->filename;
}
/**
* @param string $filename
*
* @return $this
*/
public function setFilename(string $filename)
{
$this->filename = $filename;
return $this;
}
/**
* @return int
*/
public function getPartSize(): int
{
return $this->options['part_size'] ?? 0;
}
/**
* @param int $partSize
*
* @return $this
*/
public function setPartSize(int $partSize)
{
$this->options['part_size'] = $partSize;
return $this;
}
/**
* @return int
*/
public function getConcurrency(): int
{
return $this->options['concurrency'] ?? 0;
}
/**
* @param int $concurrency
*
* @return $this
*/
public function setConcurrency(int $concurrency)
{
$this->options['concurrency'] = $concurrency;
return $this;
}
/**
* @return int
*/
public function getMupThreshold(): int
{
return $this->options['mup_threshold'] ?? 0;
}
/**
* @param int $mupThreshold
*
* @return $this
*/
public function setMupThreshold(int $mupThreshold)
{
$this->options['mup_threshold'] = $mupThreshold;
return $this;
}
/**
* @return array
*/
public function getOptions(): array
{
return $this->options;
}
/**
* @param callable $beforeUpload
*
* @return $this
*/
public function beforeUpload(callable $beforeUpload)
{
$this->options['before_upload'] = $beforeUpload;
return $this;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace frostealth\yii2\aws\s3\handlers;
use frostealth\yii2\aws\s3\base\handlers\Handler;
use frostealth\yii2\aws\s3\commands\ExistCommand;
/**
* Class ExistCommandHandler
*
* @package frostealth\yii2\aws\s3\handlers
*/
final class ExistCommandHandler extends Handler
{
/**
* @param \frostealth\yii2\aws\s3\commands\ExistCommand $command
*
* @return bool
*/
public function handle(ExistCommand $command): bool
{
return $this->s3Client->doesObjectExist(
$command->getBucket(),
$command->getFilename(),
$command->getOptions()
);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace frostealth\yii2\aws\s3\handlers;
use frostealth\yii2\aws\s3\base\handlers\Handler;
use frostealth\yii2\aws\s3\commands\GetPresignedUrlCommand;
/**
* Class GetPresignedUrlCommandHandler
*
* @package frostealth\yii2\aws\s3\handlers
*/
final class GetPresignedUrlCommandHandler extends Handler
{
/**
* @param \frostealth\yii2\aws\s3\commands\GetPresignedUrlCommand $command
*
* @return string
*/
public function handle(GetPresignedUrlCommand $command): string
{
$awsCommand = $this->s3Client->getCommand('GetObject', $command->getArgs());
$request = $this->s3Client->createPresignedRequest($awsCommand, $command->getExpires());
return (string)$request->getUri();
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace frostealth\yii2\aws\s3\handlers;
use frostealth\yii2\aws\s3\base\handlers\Handler;
use frostealth\yii2\aws\s3\commands\GetUrlCommand;
/**
* Class GetUrlCommandHandler
*
* @package frostealth\yii2\aws\s3\handlers
*/
final class GetUrlCommandHandler extends Handler
{
/**
* @param \frostealth\yii2\aws\s3\commands\GetUrlCommand $command
*
* @return string
*/
public function handle(GetUrlCommand $command): string
{
return $this->s3Client->getObjectUrl($command->getBucket(), $command->getFilename());
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace frostealth\yii2\aws\s3\handlers;
use Aws\CommandInterface as AwsCommand;
use frostealth\yii2\aws\s3\base\handlers\Handler;
use frostealth\yii2\aws\s3\interfaces\commands\Asynchronous;
use frostealth\yii2\aws\s3\interfaces\commands\PlainCommand;
/**
* Class PlainCommandHandler
*
* @package frostealth\yii2\aws\s3\handlers
*/
final class PlainCommandHandler extends Handler
{
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\PlainCommand $command
*
* @return \Aws\ResultInterface|\GuzzleHttp\Promise\PromiseInterface
*/
public function handle(PlainCommand $command)
{
$awsCommand = $this->toAwsCommand($command);
/** @var \GuzzleHttp\Promise\PromiseInterface $promise */
$promise = $this->s3Client->executeAsync($awsCommand);
return $this->commandIsAsync($command) ? $promise : $promise->wait();
}
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\PlainCommand $command
*
* @return bool
*/
protected function commandIsAsync(PlainCommand $command): bool
{
return $command instanceof Asynchronous && $command->isAsync();
}
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\PlainCommand $command
*
* @return \Aws\CommandInterface
*/
protected function toAwsCommand(PlainCommand $command): AwsCommand
{
$args = array_filter($command->toArgs());
return $this->s3Client->getCommand($command->getName(), $args);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace frostealth\yii2\aws\s3\handlers;
use frostealth\yii2\aws\s3\commands\UploadCommand;
use frostealth\yii2\aws\s3\base\handlers\Handler;
use GuzzleHttp\Psr7;
use Psr\Http\Message\StreamInterface;
/**
* Class UploadCommandHandler
*
* @package frostealth\yii2\aws\s3\handlers
*/
final class UploadCommandHandler extends Handler
{
/**
* @param \frostealth\yii2\aws\s3\commands\UploadCommand $command
*
* @return \Aws\ResultInterface|\GuzzleHttp\Promise\PromiseInterface
*/
public function handle(UploadCommand $command)
{
$source = $this->toStream($command->getSource());
$options = array_filter($command->getOptions());
$promise = $this->s3Client->uploadAsync(
$command->getBucket(),
$command->getFilename(),
$source,
$command->getAcl(),
$options
);
return $command->isAsync() ? $promise : $promise->wait();
}
/**
* Create a new stream based on the input type.
*
* @param resource|string|StreamInterface $source path to a local file, resource or stream
*
* @return StreamInterface
*/
protected function toStream($source): StreamInterface
{
if (is_string($source)) {
$source = Psr7\try_fopen($source, 'r+');
}
return Psr7\stream_for($source);
}
}

20
src/interfaces/Bus.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces;
use frostealth\yii2\aws\s3\interfaces\commands\Command;
/**
* Interface Bus
*
* @package frostealth\yii2\aws\s3\interfaces
*/
interface Bus
{
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\Command $command
*
* @return mixed
*/
public function execute(Command $command);
}

View File

@@ -0,0 +1,20 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces;
use frostealth\yii2\aws\s3\interfaces\commands\Command;
/**
* Interface CommandBuilder
*
* @package frostealth\yii2\aws\s3\interfaces
*/
interface CommandBuilder
{
/**
* @param string $commandClass
*
* @return \frostealth\yii2\aws\s3\interfaces\commands\Command
*/
public function build(string $commandClass): Command;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces;
use frostealth\yii2\aws\s3\interfaces\commands\Command;
use frostealth\yii2\aws\s3\interfaces\handlers\Handler;
/**
* Interface HandlerResolver
*
* @package frostealth\yii2\aws\s3\interfaces
*/
interface HandlerResolver
{
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\Command $command
*
* @return \frostealth\yii2\aws\s3\interfaces\handlers\Handler
*/
public function resolve(Command $command): Handler;
/**
* @param string $commandClass
* @param mixed $handler
*/
public function bindHandler(string $commandClass, $handler);
}

View File

@@ -0,0 +1,27 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces;
use frostealth\yii2\aws\s3\interfaces\commands\Command;
/**
* Interface Service
*
* @package frostealth\yii2\aws\s3\interfaces
*/
interface Service
{
/**
* @param \frostealth\yii2\aws\s3\interfaces\commands\Command $command
*
* @return mixed
*/
public function execute(Command $command);
/**
* @param string $commandClass
*
* @return \frostealth\yii2\aws\s3\interfaces\commands\Command
*/
public function create(string $commandClass): Command;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces\commands;
/**
* Interface Asynchronous
*
* @package frostealth\yii2\aws\s3\interfaces\commands
*/
interface Asynchronous
{
/**
* @param bool $async
*/
public function async(bool $async = true);
/**
* @return bool
*/
public function isAsync(): bool;
}

View File

@@ -0,0 +1,12 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces\commands;
/**
* Interface Command
*
* @package frostealth\yii2\aws\s3\interfaces\commands
*/
interface Command
{
}

View File

@@ -0,0 +1,16 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces\commands;
/**
* Interface ExecutableCommand
*
* @package frostealth\yii2\aws\s3\interfaces\commands
*/
interface ExecutableCommand extends Command
{
/**
* @return mixed
*/
public function execute();
}

View File

@@ -0,0 +1,16 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces\commands;
/**
* Interface HasAcl
*
* @package frostealth\yii2\aws\s3\interfaces\commands
*/
interface HasAcl
{
/**
* @param string $acl
*/
public function setAcl(string $acl);
}

View File

@@ -0,0 +1,16 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces\commands;
/**
* Interface HasBucket
*
* @package frostealth\yii2\aws\s3\interfaces\commands
*/
interface HasBucket
{
/**
* @param string $bucket
*/
public function setBucket(string $bucket);
}

View File

@@ -0,0 +1,21 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces\commands;
/**
* Interface PlainCommand
*
* @package frostealth\yii2\aws\s3\interfaces\commands
*/
interface PlainCommand extends Command
{
/**
* @return string
*/
public function getName(): string;
/**
* @return array
*/
public function toArgs(): array;
}

View File

@@ -0,0 +1,12 @@
<?php
namespace frostealth\yii2\aws\s3\interfaces\handlers;
/**
* Interface Handler
*
* @package frostealth\yii2\aws\s3\interfaces\handlers
*/
interface Handler
{
}