You've already forked symfony-storage
version 1.0
This commit is contained in:
175
StorageBundle/Adaptors/S3Storage.php
Normal file
175
StorageBundle/Adaptors/S3Storage.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Adaptors;
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Bluesquare\StorageBundle\Exceptions\InvalidStorageConfiguration;
|
||||
use Symfony\Component\Config\Definition\Exception\Exception;
|
||||
|
||||
/**
|
||||
* Interface de manipulation du stockage S3
|
||||
* Usage: $storage = new S3Storage('my_storage_name', $config);
|
||||
* @author Maxime Renou
|
||||
*/
|
||||
class S3Storage implements StorageAdaptor
|
||||
{
|
||||
const MODE_PRIVATE = 'private';
|
||||
const MODE_PUBLIC = 'public-read';
|
||||
|
||||
protected $client;
|
||||
protected $bucket;
|
||||
protected $region;
|
||||
|
||||
public function __construct ($storage_name, $config)
|
||||
{
|
||||
if (!($this->configIsNormed($config)))
|
||||
throw new InvalidStorageConfiguration("Invalid $storage_name storage configuration");
|
||||
|
||||
$this->config = $config;
|
||||
$this->bucket = $config['bucket'];
|
||||
$this->bucket_url = $config['bucket_url'];
|
||||
|
||||
$this->client = new S3Client([
|
||||
'version' => isset($config['version']) ? $config['version'] : 'latest',
|
||||
'region' => $config['region'],
|
||||
'endpoint' => $config['endpoint'],
|
||||
'credentials' => [
|
||||
'key' => $config['credentials']['key'],
|
||||
'secret' => $config['credentials']['secret']
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function getClient()
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
public function mode($name)
|
||||
{
|
||||
if ($name == 'public') return self::MODE_PUBLIC;
|
||||
return self::MODE_PRIVATE;
|
||||
}
|
||||
|
||||
private function configIsNormed($config)
|
||||
{
|
||||
return (
|
||||
isset($config['type']) &&
|
||||
isset($config['bucket']) &&
|
||||
isset($config['bucket_url']) &&
|
||||
isset($config['region']) &&
|
||||
isset($config['endpoint']) &&
|
||||
isset($config['credentials']) &&
|
||||
isset($config['credentials']['key']) &&
|
||||
isset($config['credentials']['secret'])
|
||||
);
|
||||
}
|
||||
|
||||
protected function getPrefix($prefix = null)
|
||||
{
|
||||
$ret = '';
|
||||
if (isset($this->config['path']) && !empty($this->config['path']))
|
||||
{
|
||||
$ret = trim($this->config['path'], '/').'/';
|
||||
if (trim($ret) == '/') $ret = '';
|
||||
}
|
||||
if (!is_null($prefix)) {
|
||||
$ret = ltrim($prefix, '/');
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function index($prefix = null)
|
||||
{
|
||||
return ($this->client->listObjectsV2([
|
||||
'Bucket' => $this->bucket,
|
||||
'Prefix' => $this->getPrefix($prefix)
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet d'obtenir l'URL vers une ressource S3
|
||||
* Cette ressource n'est accessible que si le fichier a été stocké en mode public
|
||||
* @param $target_path
|
||||
* @return string
|
||||
*/
|
||||
public function url ($target_path)
|
||||
{
|
||||
return rtrim($this->bucket_url, '/').'/'.$this->getPrefix().ltrim($target_path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de stocker un fichier dans S3
|
||||
* @param $source_path
|
||||
* @param $target_path
|
||||
* @param string $permissions
|
||||
* @return \Aws\Result
|
||||
*/
|
||||
public function store ($source_path, $target_path, $permissions = self::MODE_PRIVATE)
|
||||
{
|
||||
return $this->client->putObject([
|
||||
'Bucket' => $this->bucket,
|
||||
'Path' => $this->getPrefix().$target_path,
|
||||
'Key' => $this->getPrefix().$target_path,
|
||||
'SourceFile' => $source_path, // Fix memory allocation
|
||||
//'Body' => file_get_contents($source_path),
|
||||
'ACL' => $permissions
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de récupérer un fichier dans S3 pour le stocker en local
|
||||
* @param $distant_path
|
||||
* @param $local_path
|
||||
*/
|
||||
public function retrieve ($distant_path, $local_path)
|
||||
{
|
||||
$file_stream = fopen($local_path, 'w');
|
||||
|
||||
$aws_stream = $this->client->getObject([
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $this->getPrefix().$distant_path,
|
||||
'Path' => $this->getPrefix().$distant_path,
|
||||
])->get('Body')->detach();
|
||||
|
||||
stream_copy_to_stream($aws_stream, $file_stream);
|
||||
fclose($file_stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ouvre le stream d'un fichier stocké dans S3
|
||||
* @param $distant_path
|
||||
* @param $target_stream
|
||||
*/
|
||||
public function getStream ($distant_path)
|
||||
{
|
||||
return $this->client->getObject([
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $this->getPrefix().$distant_path,
|
||||
'Path' => $this->getPrefix().$distant_path,
|
||||
])->get('Body')->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de stream un fichier stocké dans S3
|
||||
* @param $distant_path
|
||||
* @param $target_stream
|
||||
*/
|
||||
public function stream ($distant_path, $target_stream)
|
||||
{
|
||||
stream_copy_to_stream($this->getStream($distant_path), $target_stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de supprimer un fichier stocké dans S3
|
||||
* @param $distant_path
|
||||
*/
|
||||
public function delete ($distant_path)
|
||||
{
|
||||
$this->client->deleteObject([
|
||||
'Bucket' => $this->bucket,
|
||||
'Path' => $this->getPrefix().$distant_path,
|
||||
'Key' => $this->getPrefix().$distant_path,
|
||||
]);
|
||||
}
|
||||
}
|
||||
13
StorageBundle/Adaptors/StorageAdaptor.php
Normal file
13
StorageBundle/Adaptors/StorageAdaptor.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Adaptors;
|
||||
|
||||
interface StorageAdaptor
|
||||
{
|
||||
public function index();
|
||||
public function mode($name);
|
||||
public function store($source_path, $target_path);
|
||||
public function retrieve($distant_path, $local_path);
|
||||
public function stream($distant_path, $target_stream);
|
||||
public function delete($distant_path);
|
||||
}
|
||||
31
StorageBundle/Annotations/Storage.php
Normal file
31
StorageBundle/Annotations/Storage.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Annotations;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
class Storage implements ORM\Annotation
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $prefix = null;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $mode = null;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $mime = null;
|
||||
}
|
||||
41
StorageBundle/DependencyInjection/Configuration.php
Normal file
41
StorageBundle/DependencyInjection/Configuration.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Generates the configuration tree builder.
|
||||
*
|
||||
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
|
||||
*/
|
||||
public function getConfigTreeBuilder()
|
||||
{
|
||||
$treeBuilder = new TreeBuilder('storage');
|
||||
$root = $treeBuilder->root('storage');
|
||||
$root->useAttributeAsKey('storage_name')
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('type')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('bucket')->end()
|
||||
->scalarNode('bucket_url')->end()
|
||||
->scalarNode('region')->end()
|
||||
->scalarNode('endpoint')->end()
|
||||
->arrayNode('credentials')
|
||||
->children()
|
||||
->scalarNode('key')->end()
|
||||
->scalarNode('secret')->end()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('version')->end()
|
||||
->scalarNode('path')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
return ($treeBuilder);
|
||||
}
|
||||
}
|
||||
42
StorageBundle/DependencyInjection/StorageExtension.php
Normal file
42
StorageBundle/DependencyInjection/StorageExtension.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
class StorageExtension extends Extension
|
||||
{
|
||||
|
||||
/**
|
||||
* Loads a specific configuration.
|
||||
*
|
||||
* @throws \InvalidArgumentException When provided tag is not defined in this extension
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Ressources/config'));
|
||||
$loader->load('services.yaml');
|
||||
|
||||
$configuration = $this->getConfiguration($configs, $container);
|
||||
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
$definition = $container->getDefinition('bluesquare.storage');
|
||||
$definition->setArgument(0, $config);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
||||
public function getAlias()
|
||||
{
|
||||
// return ('bluesquare\\storage');
|
||||
return parent::getAlias(); // TODO: Change the autogenerated stub
|
||||
}
|
||||
}
|
||||
8
StorageBundle/Exceptions/InvalidFileException.php
Normal file
8
StorageBundle/Exceptions/InvalidFileException.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Exceptions;
|
||||
|
||||
class InvalidFileException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
8
StorageBundle/Exceptions/InvalidStorageConfiguration.php
Normal file
8
StorageBundle/Exceptions/InvalidStorageConfiguration.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Exceptions;
|
||||
|
||||
class InvalidStorageConfiguration extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
8
StorageBundle/Exceptions/MimeTypeException.php
Normal file
8
StorageBundle/Exceptions/MimeTypeException.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Exceptions;
|
||||
|
||||
class MimeTypeException extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
8
StorageBundle/Exceptions/MissingStorageAnnotation.php
Normal file
8
StorageBundle/Exceptions/MissingStorageAnnotation.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Exceptions;
|
||||
|
||||
class MissingStorageAnnotation extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
8
StorageBundle/Exceptions/UnknownStorage.php
Normal file
8
StorageBundle/Exceptions/UnknownStorage.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle\Exceptions;
|
||||
|
||||
class UnknownStorage extends \Exception
|
||||
{
|
||||
|
||||
}
|
||||
2
StorageBundle/Ressources/config/import.yaml
Normal file
2
StorageBundle/Ressources/config/import.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
imports:
|
||||
- { ressource: '%kernel.root_dir%/config/packages/bluesquare/storage.yaml' }
|
||||
7
StorageBundle/Ressources/config/services.yaml
Normal file
7
StorageBundle/Ressources/config/services.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
bluesquare.storage:
|
||||
class: Bluesquare\StorageBundle\Storage
|
||||
autowire: true
|
||||
public: true
|
||||
arguments: ['user_storage']
|
||||
Bluesquare\StorageBundle\Storage: '@bluesquare.storage'
|
||||
166
StorageBundle/Storage.php
Normal file
166
StorageBundle/Storage.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle;
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Bluesquare\StorageBundle\Adaptors\S3Storage;
|
||||
use Bluesquare\StorageBundle\Exceptions\InvalidFileException;
|
||||
use Bluesquare\StorageBundle\Exceptions\MimeTypeException;
|
||||
use Bluesquare\StorageBundle\Exceptions\MissingStorageAnnotation;
|
||||
use Bluesquare\StorageBundle\Exceptions\UnknownStorage;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Bluesquare\StorageBundle\Annotations\Storage as StorageAnnotation;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
|
||||
/**
|
||||
* Interface de manipulation des stockages préconfigurés
|
||||
* Usage par injection
|
||||
*/
|
||||
class Storage
|
||||
{
|
||||
private $config_storage = [];
|
||||
|
||||
public function __construct(array $user_config = [])
|
||||
{
|
||||
// dump($user_config); die;
|
||||
$this->config_storage = $user_config;
|
||||
}
|
||||
|
||||
public function get($storage_name)
|
||||
{
|
||||
if (array_key_exists($storage_name, $this->config_storage))
|
||||
{
|
||||
$config = $this->config_storage[$storage_name];
|
||||
|
||||
switch ($config['type'])
|
||||
{
|
||||
case 's3': return (new S3Storage($storage_name, $config));
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
protected function getStorageAnnotation($entity, $attribute)
|
||||
{
|
||||
$reflection = new \ReflectionProperty($entity, $attribute);
|
||||
|
||||
$reader = new AnnotationReader();
|
||||
$annotations = $reader->getPropertyAnnotations($reflection);
|
||||
|
||||
$storage_annotation = null;
|
||||
|
||||
foreach ($annotations as $annotation)
|
||||
{
|
||||
if ($annotation instanceof StorageAnnotation) {
|
||||
$storage_annotation = $annotation;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($storage_annotation))
|
||||
{
|
||||
throw new MissingStorageAnnotation("Missing Storage annotation for $attribute in ".get_class($entity));
|
||||
}
|
||||
|
||||
return $storage_annotation;
|
||||
}
|
||||
|
||||
public function getStorageFor($entity, $attribute)
|
||||
{
|
||||
$annotation = $this->getStorageAnnotation($entity, $attribute);
|
||||
return $annotation->name;
|
||||
}
|
||||
|
||||
public function url($entity, $attribute, $class = null)
|
||||
{
|
||||
$annotation = $this->getStorageAnnotation(!is_null($class) ? $class : $entity, $attribute);
|
||||
$storage = $this->get($annotation->name);
|
||||
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
|
||||
$camel = ucfirst(Container::camelize($attribute));
|
||||
return $storage->url("$prefix{$entity->{"get".$camel}()}");
|
||||
}
|
||||
|
||||
public function delete($entity, $attribute)
|
||||
{
|
||||
$annotation = $this->getStorageAnnotation($entity, $attribute);
|
||||
$storage = $this->get($annotation->name);
|
||||
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
|
||||
$camel = ucfirst(Container::camelize($attribute));
|
||||
return $storage->delete("$prefix{$entity->{"get".$camel}()}");
|
||||
}
|
||||
|
||||
public function retrieve($entity, $attribute, $local_path)
|
||||
{
|
||||
$annotation = $this->getStorageAnnotation($entity, $attribute);
|
||||
$storage = $this->get($annotation->name);
|
||||
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
|
||||
$camel = ucfirst(Container::camelize($attribute));
|
||||
return $storage->retrieve("$prefix{$entity->{"get".$camel}()}", $local_path);
|
||||
}
|
||||
|
||||
public function stream($entity, $attribute, $target_stream = null)
|
||||
{
|
||||
$annotation = $this->getStorageAnnotation($entity, $attribute);
|
||||
$storage = $this->get($annotation->name);
|
||||
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
|
||||
$camel = ucfirst(Container::camelize($attribute));
|
||||
|
||||
if (is_null($target_stream)) {
|
||||
return $storage->getStream("$prefix{$entity->{"get".$camel}()}");
|
||||
}
|
||||
|
||||
return $storage->stream("$prefix{$entity->{"get".$camel}()}", $target_stream);
|
||||
}
|
||||
|
||||
public function store($entity, $attribute, $file)
|
||||
{
|
||||
$storage_annotation = $this->getStorageAnnotation($entity, $attribute);
|
||||
$file_hash = hash('sha256', time().$attribute.uniqid());
|
||||
$storage = $this->get($storage_annotation->name);
|
||||
|
||||
if (is_null($storage))
|
||||
{
|
||||
throw new UnknownStorage("Unknown storage {$storage_annotation->name} for $attribute in ".get_class($entity));
|
||||
}
|
||||
|
||||
$prefix = is_null($storage_annotation->prefix) || empty($storage_annotation->prefix) ? '' : trim($storage_annotation->prefix, '/').'/';
|
||||
$mode = $storage->mode($storage_annotation->mode);
|
||||
$camel = ucfirst(Container::camelize($attribute));
|
||||
|
||||
if ($file instanceof UploadedFile) {
|
||||
$file_hash .= strlen($file->getClientOriginalExtension()) > 0 ? '.'.$file->getClientOriginalExtension() : '';
|
||||
|
||||
if (!is_null($storage_annotation->mime))
|
||||
{
|
||||
$valid = true;
|
||||
if (count(explode('/', $storage_annotation->mime)) > 1) {
|
||||
$valid = strtolower($storage_annotation->mime) == $file->getMimeType();
|
||||
}
|
||||
else {
|
||||
$valid = strtolower($storage_annotation->mime) == explode('/', $file->getMimeType())[0];
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
throw new MimeTypeException("Invalid mime type");
|
||||
}
|
||||
}
|
||||
|
||||
$storage->store($file->getRealPath(), "$prefix$file_hash", $mode);
|
||||
$previous_file_hash = $entity->{"get$camel"}();
|
||||
if (!is_null($previous_file_hash) && !empty($previous_file_hash)) {
|
||||
$storage->delete("$prefix$previous_file_hash");
|
||||
}
|
||||
}
|
||||
elseif (is_string($file) && file_exists($file)) {
|
||||
$storage->store($file, "$prefix$file_hash", $mode);
|
||||
}
|
||||
else {
|
||||
throw new InvalidFileException("Invalid file argument");
|
||||
}
|
||||
|
||||
$entity->{"set$camel"}($file_hash);
|
||||
|
||||
return $file_hash;
|
||||
}
|
||||
}
|
||||
22
StorageBundle/StorageBundle.php
Normal file
22
StorageBundle/StorageBundle.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Bluesquare\StorageBundle;
|
||||
|
||||
use Bluesquare\StorageBundle\DependencyInjection\StorageExtension;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class StorageBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
parent::build($container);
|
||||
}
|
||||
|
||||
public function getContainerExtension()
|
||||
{
|
||||
if (null === $this->extension)
|
||||
$this->extension = new StorageExtension();
|
||||
return $this->extension;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user