diff --git a/Readme.md b/Readme.md
index e3143be..dd69237 100644
--- a/Readme.md
+++ b/Readme.md
@@ -31,9 +31,8 @@ $publicMountPoint = new \Cmp\Storage\MountPoint('/var/www/app/public', $fallBack
$vfs->registerMountPoint($localMountPoint);
$vfs->registerMountPoint($publicMountPoint);
-/*
+
//move file from /tmp (FS) to /var/www/app/public (S3) and if fails try to move from /tmp (FS) to /var/www/app/public (FS)
-*/
$vfs->move('/tmp/testfile.jpg','/var/www/app/public/avatar.jpg' );
```
@@ -139,7 +138,7 @@ __Fluid calls:__
* `setStrategy(AbstractStorageCallStrategy $strategy)` : Set a custom strategy
* `setLogger(LoggerInterface $logger)` : Set custom logger
* `addAdapter($adapter)` : Add a new adapter
-* `build(AbstractStorageCallStrategy $callStrategy = null, LoggerInterface $logger = null)` : Build the virtual storage
+* `build(AbstractStorageCallStrategy $callStrategy = null)` : Build the virtual storage
__Non fluid calls:__
diff --git a/src/Cmp/Storage/Adapter/FileSystemAdapter.php b/src/Cmp/Storage/Adapter/FileSystemAdapter.php
index 62fbfd4..1b1b92c 100644
--- a/src/Cmp/Storage/Adapter/FileSystemAdapter.php
+++ b/src/Cmp/Storage/Adapter/FileSystemAdapter.php
@@ -2,21 +2,79 @@
namespace Cmp\Storage\Adapter;
+use Cmp\Storage\AdapterInterface;
use Cmp\Storage\Exception\FileExistsException;
use Cmp\Storage\Exception\FileNotFoundException;
use Cmp\Storage\Exception\InvalidPathException;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\LogLevel;
+use Psr\Log\NullLogger;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
/**
* Class FileSystemAdapter.
*/
-class FileSystemAdapter implements \Cmp\Storage\AdapterInterface
+class FileSystemAdapter implements AdapterInterface, LoggerAwareInterface
{
+ use LoggerAwareTrait;
+ use LogicalChecksTrait;
+
/**
* Adapter Name.
*/
const NAME = 'FileSystem';
const MAX_PATH_SIZE = 255; //The major part of fs has this limit
+ public function __construct()
+ {
+ $this->logger = new NullLogger();
+ }
+
+ /**
+ * Read a file.
+ *
+ * @param string $path The path to the file
+ *
+ * @throws FileNotFoundException
+ *
+ * @return string The file contents or false on failure
+ */
+ public function get($path)
+ {
+ $path = $this->normalizePath($path);
+ $this->assertNotFileExists($path);
+
+ return file_get_contents($path);
+ }
+
+ private function normalizePath($path)
+ {
+ $this->assertFileMaxLength($path);
+
+ return realpath($path);
+ }
+
+ /**
+ * @param $path
+ *
+ * @throws InvalidPathException
+ */
+ private function assertFileMaxLength($path)
+ {
+ if (strlen(basename($path)) > self::MAX_PATH_SIZE) {
+ $e = new InvalidPathException($path);
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Invalid path {path}.',
+ ['exception' => $e, 'path' => $path]
+ );
+
+ throw $e;
+ }
+ }
+
/**
* Get Adapter name.
*
@@ -28,34 +86,36 @@ public function getName()
}
/**
- * Check whether a file exists.
- *
- * @param string $path
+ * @param $path
*
- * @return bool
+ * @throws FileNotFoundException
*/
- public function exists($path)
+ private function assertNotFileExists($path)
{
- $path = $this->normalizePath($path);
+ if (!$this->exists($path) || !is_file($path)) {
+ $e = new FileNotFoundException($path);
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. File {path} not exists.',
+ ['exception' => $e, 'path' => $path]
+ );
- return file_exists($path);
+ throw $e;
+ }
}
/**
- * Read a file.
- *
- * @param string $path The path to the file
+ * Check whether a file exists.
*
- * @throws \Cmp\Storage\FileNotFoundException
+ * @param string $path
*
- * @return string The file contents or false on failure
+ * @return bool
*/
- public function get($path)
+ public function exists($path)
{
$path = $this->normalizePath($path);
- $this->assertNotFileExists($path);
- return file_get_contents($path);
+ return file_exists($path);
}
/**
@@ -63,7 +123,7 @@ public function get($path)
*
* @param string $path The path to the file
*
- * @throws \Cmp\Storage\FileNotFoundException
+ * @throws FileNotFoundException
*
* @return resource The path resource or false on failure
*/
@@ -78,8 +138,8 @@ public function getStream($path)
/**
* Rename a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The new path of the file
+ * @param string $path Path to the existing file
+ * @param string $newpath The new path of the file
* @param bool $overwrite
*
* @return bool Thrown if $newpath exists
@@ -89,9 +149,7 @@ public function getStream($path)
public function rename($path, $newpath, $overwrite = false)
{
$path = $this->normalizePath($path);
- if (!$overwrite && $this->exists($newpath)) {
- throw new FileExistsException($newpath);
- }
+ $this->ensureWeCanWriteDestFile($newpath, $overwrite);
$this->assertNotFileExists($path);
return rename($path, $newpath);
@@ -100,8 +158,8 @@ public function rename($path, $newpath, $overwrite = false)
/**
* Copy a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The destination path of the copy
+ * @param string $path Path to the existing file
+ * @param string $newpath The destination path of the copy
*
* @return bool
*/
@@ -134,6 +192,28 @@ public function delete($path)
}
}
+ /**
+ * Removes directory recursively.
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ private function removeDirectory($path)
+ {
+ $files = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
+ RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ foreach ($files as $fileinfo) {
+ $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
+ $todo($fileinfo->getRealPath());
+ }
+
+ return rmdir($path);
+ }
+
/**
* Create a file or update if exists. It will create the missing folders.
*
@@ -147,12 +227,8 @@ public function delete($path)
public function put($path, $contents)
{
$this->assertFileMaxLength($path);
- if (is_dir($path)) {
- throw new InvalidPathException($path);
- }
- if (!$this->createParentFolder($path)) {
- throw new InvalidPathException($path);
- }
+ $this->assertIsDir($path);
+ $this->ensureParentPathExists($path);
if (($size = file_put_contents($path, $contents)) === false) {
return false;
}
@@ -161,52 +237,45 @@ public function put($path, $contents)
}
/**
- * Create a file or update if exists. It will create the missing folders.
- *
- * @param string $path The path to the file
- * @param resource $resource The file handle
- *
- * @return bool
+ * @param $path
*
* @throws InvalidPathException
*/
- public function putStream($path, $resource)
+ private function assertIsDir($path)
{
- $this->assertFileMaxLength($path);
if (is_dir($path)) {
- throw new InvalidPathException($path);
- }
- if (!$this->createParentFolder($path)) {
- throw new InvalidPathException($path);
- }
- $stream = fopen($path, 'w+');
+ $e = new InvalidPathException($path);
- if (!$stream) {
- return false;
- }
-
- stream_copy_to_stream($resource, $stream);
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Path {path} is a directory.',
+ ['exception' => $e, 'path' => $path]
+ );
- return fclose($stream);
+ throw $e;
+ }
}
/**
* @param $path
*
- * @throws FileNotFoundException
+ * @throws InvalidPathException
*/
- private function assertNotFileExists($path)
+ private function ensureParentPathExists($path)
{
- if (!$this->exists($path) || !is_file($path)) {
- throw new FileNotFoundException($path);
- }
- }
+ if (!$this->createParentFolder($path)) {
+ $e = new InvalidPathException($path);
- private function normalizePath($path)
- {
- $this->assertFileMaxLength($path);
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.
+ $this->getName().
+ '" fails. Parent path {path} is not ready and it\'s impossible to create it.',
+ ['exception' => $e, 'path' => $path]
+ );
- return realpath($path);
+ throw $e;
+ }
}
/**
@@ -225,43 +294,29 @@ private function createParentFolder($path)
}
/**
- * @param $path
- *
- * @throws InvalidPathException
- */
- private function assertFileMaxLength($path)
- {
- if (strlen(basename($path)) > self::MAX_PATH_SIZE) {
- throw new InvalidPathException($path);
- }
- }
-
- /**
- * Removes directory recursively.
+ * Create a file or update if exists. It will create the missing folders.
*
- * @param string $path
+ * @param string $path The path to the file
+ * @param resource $resource The file handle
*
* @return bool
+ *
+ * @throws InvalidPathException
*/
- private function removeDirectory($path)
+ public function putStream($path, $resource)
{
- if (is_dir($path)) {
- $objects = scandir($path);
- foreach ($objects as $object) {
- if ($object != '.' && $object != '..') {
- if (is_dir($path.'/'.$object)) {
- if (!$this->removeDirectory($path.'/'.$object)) {
- return false;
- }
- } else {
- if (!unlink($path.'/'.$object)) {
- return false;
- }
- }
- }
- }
-
- return rmdir($path);
+ $this->assertFileMaxLength($path);
+ $this->assertIsDir($path);
+ $this->ensureParentPathExists($path);
+
+ $stream = fopen($path, 'w+');
+
+ if (!$stream) {
+ return false;
}
+
+ stream_copy_to_stream($resource, $stream);
+
+ return fclose($stream);
}
}
diff --git a/src/Cmp/Storage/Adapter/LogicalChecksTrait.php b/src/Cmp/Storage/Adapter/LogicalChecksTrait.php
new file mode 100644
index 0000000..9a2e7f8
--- /dev/null
+++ b/src/Cmp/Storage/Adapter/LogicalChecksTrait.php
@@ -0,0 +1,31 @@
+exists($newpath)) {
+ $e = new FileExistsException($newpath);
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Destination file {path} already exists.',
+ ['exception' => $e, 'path' => $newpath]
+ );
+
+ throw $e;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Cmp/Storage/Adapter/S3AWSAdapter.php b/src/Cmp/Storage/Adapter/S3AWSAdapter.php
index 2926f17..e46ca9d 100644
--- a/src/Cmp/Storage/Adapter/S3AWSAdapter.php
+++ b/src/Cmp/Storage/Adapter/S3AWSAdapter.php
@@ -2,24 +2,44 @@
namespace Cmp\Storage\Adapter;
+use Aws\Result;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use Cmp\Storage\AdapterInterface;
use Cmp\Storage\Exception\AdapterException;
use Cmp\Storage\Exception\FileExistsException;
+use Cmp\Storage\Exception\FileNotFoundException;
+use Cmp\Storage\Exception\InvalidPathException;
use Cmp\Storage\Exception\InvalidStorageAdapterException;
+use InvalidArgumentException;
+use Mimey\MimeTypes;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\LogLevel;
+use Psr\Log\NullLogger;
/**
* Class S3AWSAdapter.
*/
-class S3AWSAdapter implements AdapterInterface
+class S3AWSAdapter implements AdapterInterface, LoggerAwareInterface
{
+ use LoggerAwareTrait;
+ use LogicalChecksTrait;
+
const ACL_PUBLIC_READ = 'public-read';
/**
* Adapter Name.
*/
const NAME = 'S3AWS';
-
+ /**
+ * @var array
+ */
+ private static $mandatoryEnvVars = [
+ 'AWS_REGION',
+ 'AWS_ACCESS_KEY_ID',
+ 'AWS_SECRET_ACCESS_KEY',
+ 'AWS_BUCKET',
+ ];
/**
* @var S3Client
*/
@@ -28,25 +48,23 @@ class S3AWSAdapter implements AdapterInterface
* @var string
*/
private $bucket;
-
/**
* @var $pathPrefix
*/
private $pathPrefix;
-
/**
- * @var \Mimey\MimeTypes
+ * @var MimeTypes
*/
private $mimes;
-
/**
* @var array
*/
- private static $mandatoryEnvVars = [
- 'AWS_REGION',
- 'AWS_ACCESS_KEY_ID',
- 'AWS_SECRET_ACCESS_KEY',
- 'AWS_BUCKET',
+ private $config;
+ /**
+ * @var array
+ */
+ private $options = [
+ 'ACL' => self::ACL_PUBLIC_READ,
];
/**
@@ -55,47 +73,57 @@ class S3AWSAdapter implements AdapterInterface
* @param array $config
* @param string $bucket
* @param string $pathPrefix
+ * @param array $options
*
* @throws InvalidStorageAdapterException
*/
- public function __construct(array $config = [], $bucket = '', $pathPrefix = '')
+ public function __construct(array $config = [], $bucket = '', $pathPrefix = '', array $options = [])
{
$this->bucket = $bucket;
+ $this->logger = new NullLogger();
if (empty($config) || empty($bucket)) {
$this->assertMandatoryConfigEnv();
- $config = $this->getConfigFromEnv();
+ $config = $this->getConfigFromEnv();
$this->bucket = getenv('AWS_BUCKET');
}
- $this->client = new S3Client($config);
+ $this->client = new S3Client($config);
$this->pathPrefix = $pathPrefix;
- $this->mimes = new \Mimey\MimeTypes();
+ $this->mimes = new MimeTypes();
+ $this->config = $config;
+ $this->options = array_merge($this->options, $options);
}
/**
- * Get Adapter name.
- *
- * @return string
+ * @throws InvalidStorageAdapterException
*/
- public function getName()
+ private function assertMandatoryConfigEnv()
{
- return self::NAME;
+ foreach (self::$mandatoryEnvVars as $env) {
+ if (empty(getenv($env))) {
+ throw new InvalidStorageAdapterException(
+ 'The env "'.
+ $env.
+ '" is missing. Set it to run this adapter as builtin or use the regular constructor.'
+ );
+ }
+ }
}
/**
- * Check whether a file exists.
- *
- * @param string $path
- *
- * @return bool
+ * @return array
*/
- public function exists($path)
+ private function getConfigFromEnv()
{
- $path = $this->trimPrefix($path);
- if ($this->client->doesObjectExist($this->bucket, $path)) {
- return true;
- }
+ $config = [
+ 'version' => 'latest',
+ 'region' => getenv('AWS_REGION'),
+ 'credentials' => [
+ 'key' => getenv('AWS_ACCESS_KEY_ID'),
+ 'secret' => getenv('AWS_SECRET_ACCESS_KEY'),
+ ],
+ ];
- return $this->doesDirectoryExist($path);
+ return $config;
}
/**
@@ -118,24 +146,6 @@ public function get($path)
return $response;
}
- /**
- * Retrieves a read-stream for a path.
- *
- * @param string $path The path to the file
- *
- * @return resource The path resource or false on failure
- */
- public function getStream($path)
- {
- $response = $this->readObject($path);
-
- if ($response !== false) {
- $response = $response['Body']->detach();
- }
-
- return $response;
- }
-
/**
* @param $path
*
@@ -145,13 +155,13 @@ public function getStream($path)
*/
protected function readObject($path)
{
- $path = $this->trimPrefix($path);
+ $path = $this->trimPrefix($path);
$command = $this->client->getCommand(
'getObject',
[
'Bucket' => $this->bucket,
- 'Key' => $path,
- '@http' => [
+ 'Key' => $path,
+ '@http' => [
'stream' => true,
],
]
@@ -161,120 +171,95 @@ protected function readObject($path)
/** @var Result $response */
$response = $this->client->execute($command);
} catch (S3Exception $e) {
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Impossible read {path}.',
+ ['exception' => $e, 'path' => $path]
+ );
+
return false;
}
return $response;
}
- /**
- * Rename a file.
- *
- * @param string $path Path to the existing file
- * @param string $newpath The new path of the file
- * @param bool $overwrite
- *
- * @return bool Thrown if $newpath exists
- *
- * @throws FileExistsException
- */
- public function rename($path, $newpath, $overwrite = false)
+ private function trimPrefix($prefix)
{
- if (!$overwrite && $this->exists($newpath)) {
- throw new FileExistsException($newpath);
- }
-
- if (!$this->copy($path, $newpath)) {
- return false;
- }
+ $from = '/'.preg_quote($this->pathPrefix, '/').'/';
+ $prefix = preg_replace($from, '', $prefix, 1);
- return $this->delete($path);
+ return ltrim($prefix, '/');
}
/**
- * Delete a file or directory.
- *
- * @param string $path
+ * Get Adapter name.
*
- * @return bool True on success, false on failure
+ * @return string
*/
- public function delete($path)
+ public function getName()
{
- $path = $this->trimPrefix($path);
- try {
- if ($this->doesDirectoryExist($path)) {
- $this->client->deleteMatchingObjects($this->bucket, $path.'/');
-
- return true;
- } elseif ($this->client->doesObjectExist($this->bucket, $path)) {
- $this->client->deleteMatchingObjects($this->bucket, $path);
-
- return true;
- }
- } catch (\Exception $e) {
- return false;
- }
-
- return false;
+ return self::NAME;
}
/**
- * Create a file or update if exists. It will create the missing folders.
- *
- * @param string $path The path to the file
- * @param string|resource $contents The file contents
+ * Retrieves a read-stream for a path.
*
- * @return bool True on success, false on failure
+ * @param string $path The path to the file
*
- * @throws \Cmp\Storage\InvalidPathException
+ * @return resource The path resource or false on failure
*/
- public function put($path, $contents)
+ public function getStream($path)
{
- $options = ['ContentType' => $this->getMimeType($path)];
- $path = $this->trimPrefix($path);
- try {
- $this->client->upload($this->bucket, $path, $contents, self::ACL_PUBLIC_READ, ['params' => $options]);
- } catch (S3Exception $e) {
- return false;
+ $response = $this->readObject($path);
+
+ if ($response !== false) {
+ $response = $response['Body']->detach();
}
- return true;
+ return $response;
}
/**
- * Create a file or update if exists. It will create the missing folders.
+ * Rename a file.
*
- * @param string $path The path to the file
- * @param resource $resource The file handle
+ * @param string $path Path to the existing file
+ * @param string $newpath The new path of the file
+ * @param bool $overwrite
*
- * @throws \Cmp\Storage\InvalidArgumentException Thrown if $resource is not a resource
+ * @return bool Thrown if $newpath exists
*
- * @return bool True on success, false on failure
+ * @throws FileExistsException
*/
- public function putStream($path, $resource)
+ public function rename($path, $newpath, $overwrite = false)
{
- return $this->put($path, $resource);
+ $this->ensureWeCanWriteDestFile($newpath, $overwrite);
+
+ if (!$this->copy($path, $newpath)) {
+ return false;
+ }
+
+ return $this->delete($path);
}
/**
* Copy a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The destination path of the copy
+ * @param string $path Path to the existing file
+ * @param string $newpath The destination path of the copy
*
* @return bool
*/
public function copy($path, $newpath)
{
- $path = $this->trimPrefix($path);
+ $path = $this->trimPrefix($path);
$newpath = $this->trimPrefix($newpath);
$command = $this->client->getCommand(
'copyObject',
[
- 'Bucket' => $this->bucket,
- 'Key' => $newpath,
- 'CopySource' => urlencode($this->bucket.'/'.$path),
- 'ACL' => self::ACL_PUBLIC_READ,
+ 'Bucket' => $this->bucket,
+ 'Key' => $newpath,
+ 'CopySource' => urlencode($this->bucket.'/'.$path),
+ 'ACL' => $this->options['ACL'],
'ContentType' => $this->getMimeType($path),
]
);
@@ -282,12 +267,61 @@ public function copy($path, $newpath)
try {
$this->client->execute($command);
} catch (S3Exception $e) {
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Impossible copy file from {from}} to {to}.',
+ ['exception' => $e, 'from' => $path, 'to' => $newpath]
+ );
+
return false;
}
return true;
}
+ private function getMimeType($path)
+ {
+ $ext = pathinfo($path, PATHINFO_EXTENSION);
+ if (empty($ext)) {
+ return 'text/plain';
+ }
+
+ return $this->mimes->getMimeType($ext);
+ }
+
+ /**
+ * Delete a file or directory.
+ *
+ * @param string $path
+ *
+ * @return bool True on success, false on failure
+ */
+ public function delete($path)
+ {
+ $path = $this->trimPrefix($path);
+ try {
+ if ($this->doesDirectoryExist($path)) {
+ $this->client->deleteMatchingObjects($this->bucket, $path.'/');
+
+ return true;
+ } elseif ($this->client->doesObjectExist($this->bucket, $path)) {
+ $this->client->deleteMatchingObjects($this->bucket, $path);
+
+ return true;
+ }
+ } catch (\Exception $e) {
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Impossible delete file {path}.',
+ ['exception' => $e, 'path' => $path]
+ );
+
+ return false;
+ }
+
+ return false;
+ }
+
/**
* @param $path
*
@@ -300,8 +334,8 @@ private function doesDirectoryExist($path)
$command = $this->client->getCommand(
'listObjects',
[
- 'Bucket' => $this->bucket,
- 'Prefix' => $this->trimPrefix($path).'/',
+ 'Bucket' => $this->bucket,
+ 'Prefix' => $this->trimPrefix($path).'/',
'MaxKeys' => 1,
]
);
@@ -311,56 +345,74 @@ private function doesDirectoryExist($path)
return $result['Contents'] || $result['CommonPrefixes'];
} catch (S3Exception $e) {
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Impossible get information about directory {path}.',
+ ['exception' => $e, 'from' => $path]
+ );
+
return false;
}
}
/**
- * @throws InvalidStorageAdapterException
+ * Check whether a file exists.
+ *
+ * @param string $path
+ *
+ * @return bool
*/
- private function assertMandatoryConfigEnv()
+ public function exists($path)
{
- foreach (self::$mandatoryEnvVars as $env) {
- if (empty(getenv($env))) {
- throw new InvalidStorageAdapterException(
- 'The env "'.
- $env.
- '" is missing. Set it to run this adapter as builtin or use the regular constructor.'
- );
- }
+ $path = $this->trimPrefix($path);
+ if ($this->client->doesObjectExist($this->bucket, $path)) {
+ return true;
}
+
+ return $this->doesDirectoryExist($path);
}
/**
- * @return array
+ * Create a file or update if exists. It will create the missing folders.
+ *
+ * @param string $path The path to the file
+ * @param resource $resource The file handle
+ *
+ * @throws InvalidArgumentException Thrown if $resource is not a resource
+ *
+ * @return bool True on success, false on failure
*/
- private function getConfigFromEnv()
+ public function putStream($path, $resource)
{
- $config = [
- 'version' => 'latest',
- 'region' => getenv('AWS_REGION'),
- 'credentials' => [
- 'key' => getenv('AWS_ACCESS_KEY_ID'),
- 'secret' => getenv('AWS_SECRET_ACCESS_KEY'),
- ],
- ];
-
- return $config;
+ return $this->put($path, $resource);
}
- private function trimPrefix($prefix)
+ /**
+ * Create a file or update if exists. It will create the missing folders.
+ *
+ * @param string $path The path to the file
+ * @param string|resource $contents The file contents
+ *
+ * @return bool True on success, false on failure
+ *
+ * @throws InvalidPathException
+ */
+ public function put($path, $contents)
{
- $from = '/'.preg_quote($this->pathPrefix, '/').'/';
- $prefix = preg_replace($from, '', $prefix, 1);
- return ltrim($prefix, '/');
- }
+ $options = ['ContentType' => $this->getMimeType($path)];
+ $path = $this->trimPrefix($path);
+ try {
+ $this->client->upload($this->bucket, $path, $contents, $this->options['ACL'], ['params' => $options]);
+ } catch (S3Exception $e) {
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$this->getName().'" fails. Impossible upload a file {path}.',
+ ['exception' => $e, 'path' => $path]
+ );
- private function getMimeType($path)
- {
- $ext = pathinfo($path, PATHINFO_EXTENSION);
- if (empty($ext)) {
- return 'text/plain';
+ return false;
}
- return $this->mimes->getMimeType($ext);
+
+ return true;
}
}
diff --git a/src/Cmp/Storage/Exception/InvalidStorageAdapterException.php b/src/Cmp/Storage/Exception/InvalidStorageAdapterException.php
index 957cace..054b212 100644
--- a/src/Cmp/Storage/Exception/InvalidStorageAdapterException.php
+++ b/src/Cmp/Storage/Exception/InvalidStorageAdapterException.php
@@ -10,6 +10,7 @@
class InvalidStorageAdapterException extends StorageException
{
const CODE = 1004;
+
/**
* InvalidStorageAdapterException constructor.
*
diff --git a/src/Cmp/Storage/Exception/StorageAdapterNotFoundException.php b/src/Cmp/Storage/Exception/StorageAdapterNotFoundException.php
deleted file mode 100644
index d5d0dd2..0000000
--- a/src/Cmp/Storage/Exception/StorageAdapterNotFoundException.php
+++ /dev/null
@@ -1,24 +0,0 @@
-virtualPath = new VirtualPath($path);
+ $this->virtualPath = new VirtualPath($path);
$this->virtualStorage = $virtualStorage;
}
diff --git a/src/Cmp/Storage/MountPointSortedSet.php b/src/Cmp/Storage/MountPointSortedSet.php
index e4912bb..ffa99cb 100644
--- a/src/Cmp/Storage/MountPointSortedSet.php
+++ b/src/Cmp/Storage/MountPointSortedSet.php
@@ -34,13 +34,11 @@ public function set(MountPoint $value)
}
/**
- * @param $path
- *
* @return bool
*/
- public function contains($path)
+ private function sort()
{
- return isset($this->mountPoints[$path]);
+ return uksort($this->mountPoints, [$this, 'compare']);
}
/**
@@ -57,6 +55,16 @@ public function get($path)
return $this->mountPoints[$path];
}
+ /**
+ * @param $path
+ *
+ * @return bool
+ */
+ public function contains($path)
+ {
+ return isset($this->mountPoints[$path]);
+ }
+
/**
* @param $path
*
@@ -73,6 +81,21 @@ public function remove($path)
return true;
}
+ /**
+ * Retrieve an external iterator.
+ *
+ * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
+ *
+ * @return Traversable An instance of an object implementing Iterator or
+ * Traversable
+ *
+ * @since 5.0.0
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->mountPoints);
+ }
+
/**
* @param string $value1
* @param string $value2
@@ -90,27 +113,4 @@ private function compare($value1, $value2)
return $s2 - $s1;
}
-
- /**
- * @return bool
- */
- private function sort()
- {
- return uksort($this->mountPoints, [$this, 'compare']);
- }
-
- /**
- * Retrieve an external iterator.
- *
- * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
- *
- * @return Traversable An instance of an object implementing Iterator or
- * Traversable
- *
- * @since 5.0.0
- */
- public function getIterator()
- {
- return new ArrayIterator($this->mountPoints);
- }
}
diff --git a/src/Cmp/Storage/MountableVirtualStorage.php b/src/Cmp/Storage/MountableVirtualStorage.php
index b827e78..f7428ad 100644
--- a/src/Cmp/Storage/MountableVirtualStorage.php
+++ b/src/Cmp/Storage/MountableVirtualStorage.php
@@ -23,14 +23,21 @@ class MountableVirtualStorage implements VirtualStorageInterface
*/
public function __construct($defaultVirtualStorage)
{
- $this->mountPoints = new MountPointSortedSet();
+ $this->mountPoints = new MountPointSortedSet();
$this->defaultMountPoint = $this->getDefaultMountPoint($defaultVirtualStorage);
$this->registerMountPoint($this->defaultMountPoint);
}
- public function getMountPoints()
+ /**
+ * @param $defaultVirtualStorage
+ *
+ * @return MountPoint
+ */
+ private function getDefaultMountPoint(VirtualStorageInterface $defaultVirtualStorage)
{
- return $this->mountPoints->getIterator();
+ $defaultMountPoint = new MountPoint(self::ROOT_PATH, $defaultVirtualStorage);
+
+ return $defaultMountPoint;
}
/**
@@ -41,6 +48,30 @@ public function registerMountPoint(MountPoint $mountPoint)
$this->mountPoints->set($mountPoint);
}
+ public function getMountPoints()
+ {
+ return $this->mountPoints->getIterator();
+ }
+
+ public function exists($path)
+ {
+ $vp = new VirtualPath($path);
+
+ return $this->getStorageForPath($vp)->exists($vp->getPath());
+ }
+
+ /**
+ * @param VirtualPath $vp
+ *
+ * @return VirtualStorageInterface
+ */
+ private function getStorageForPath(VirtualPath $vp)
+ {
+ $mountPoint = $this->getMountPointForPath($vp);
+
+ return $mountPoint->getStorage();
+ }
+
/**
* @param VirtualPath $vp
*
@@ -60,44 +91,38 @@ public function getMountPointForPath(VirtualPath $vp)
return $this->defaultMountPoint;
}
- public function exists($path)
- {
- $vp = new VirtualPath($path);
- return $this->getStorageForPath($vp)->exists($vp->getPath());
- }
-
public function get($path)
{
$vp = new VirtualPath($path);
+
return $this->getStorageForPath($vp)->get($vp->getPath());
}
public function getStream($path)
{
$vp = new VirtualPath($path);
+
return $this->getStorageForPath($vp)->getStream($vp->getPath());
}
public function rename($path, $newpath, $overwrite = false)
{
- $svp = new VirtualPath($path);
- $dvp = new VirtualPath($newpath);
+ $svp = new VirtualPath($path);
+ $dvp = new VirtualPath($newpath);
$storageSrc = $this->getStorageForPath($svp);
$storageDst = $this->getStorageForPath($dvp);
if (!$overwrite && $storageDst->exists($dvp->getPath())) {
throw new FileExistsException($dvp->getPath());
-
}
return $this->copy($svp->getPath(), $dvp->getPath()) && $storageSrc->delete($svp->getPath());
}
-
public function copy($path, $newpath)
{
- $svp = new VirtualPath($path);
- $dvp = new VirtualPath($newpath);
+ $svp = new VirtualPath($path);
+ $dvp = new VirtualPath($newpath);
$storageSrc = $this->getStorageForPath($svp);
$storageDst = $this->getStorageForPath($dvp);
@@ -114,47 +139,24 @@ public function copy($path, $newpath)
return $storageDst->exists($dvp->getPath());
}
-
public function delete($path)
{
$vp = new VirtualPath($path);
+
return $this->getStorageForPath($vp)->delete($vp->getPath());
}
public function put($path, $contents)
{
$vp = new VirtualPath($path);
+
return $this->getStorageForPath($vp)->put($vp->getPath(), $contents);
}
public function putStream($path, $resource)
{
$vp = new VirtualPath($path);
- return $this->getStorageForPath($vp)->putStream($vp->getPath(), $resource);
- }
- /**
- * @param $defaultVirtualStorage
- *
- * @return MountPoint
- */
- private function getDefaultMountPoint(VirtualStorageInterface $defaultVirtualStorage)
- {
- $defaultMountPoint = new MountPoint(self::ROOT_PATH, $defaultVirtualStorage);
-
- return $defaultMountPoint;
- }
-
-
- /**
- * @param VirtualPath $vp
- *
- * @return VirtualStorageInterface
- */
- private function getStorageForPath(VirtualPath $vp)
- {
- $mountPoint = $this->getMountPointForPath($vp);
-
- return $mountPoint->getStorage();
+ return $this->getStorageForPath($vp)->putStream($vp->getPath(), $resource);
}
}
diff --git a/src/Cmp/Storage/StorageBuilder.php b/src/Cmp/Storage/StorageBuilder.php
index e60f819..61335c3 100644
--- a/src/Cmp/Storage/StorageBuilder.php
+++ b/src/Cmp/Storage/StorageBuilder.php
@@ -4,12 +4,12 @@
use Cmp\Storage\Adapter\FileSystemAdapter;
use Cmp\Storage\Exception\InvalidStorageAdapterException;
-use Cmp\Storage\Exception\StorageAdapterNotFoundException;
use Cmp\Storage\Strategy\AbstractStorageCallStrategy;
use Cmp\Storage\Strategy\DefaultStrategyFactory;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
+use Psr\Log\NullLogger;
/**
* Class StorageBuilder.
@@ -28,14 +28,6 @@ class StorageBuilder implements LoggerAwareInterface
* @var array
*/
private $adapters;
- /**
- * @var array
- */
- private static $builtinAdapters = [];
- /**
- * @var bool
- */
- private static $builtInAdaptersLoaded = false;
/**
* StorageBuilder constructor.
@@ -43,35 +35,41 @@ class StorageBuilder implements LoggerAwareInterface
public function __construct()
{
$this->adapters = [];
+ $this->logger = new NullLogger();
}
/**
- * Set a custom strategy.
+ * Build the virtual storage.
*
- * @param AbstractStorageCallStrategy $strategy
+ * @param $callStrategy
*
- * @return $this
+ * @return VirtualStorageInterface
+ *
+ * @throws InvalidStorageAdapterException
*/
- public function setStrategy(AbstractStorageCallStrategy $strategy)
+ public function build(AbstractStorageCallStrategy $callStrategy = null)
{
- $this->log(LogLevel::INFO, 'Set the strategy {{strategy}}', ['strategy' => $strategy->getStrategyName()]);
- $this->strategy = $strategy;
+ if (!$this->hasLoadedAdapters()) {
+ $this->addAdapter($this->getDefaultBuiltinAdapter());
+ }
- return $this;
+ if ($callStrategy != null) {
+ $this->setStrategy($callStrategy);
+ }
+
+ $this->bindAdaptersLogger();
+
+ return $this->createStrategy();
}
/**
- * Set custom logger.
- *
- * @param LoggerInterface $logger
+ * Check if one or more adapters has been loaded.
*
- * @return $this
+ * @return bool
*/
- public function setLogger(LoggerInterface $logger)
+ public function hasLoadedAdapters()
{
- $this->logger = $logger;
-
- return $this;
+ return !empty($this->adapters);
}
/**
@@ -82,51 +80,56 @@ public function setLogger(LoggerInterface $logger)
* @return $this
*
* @throws InvalidStorageAdapterException
- * @throws StorageAdapterNotFoundException
*/
public function addAdapter($adapter)
{
- if (is_string($adapter)) {
- $this->addBuiltinAdapters();
- $this->assertBuiltInAdapterExists($adapter);
- $this->registerAdapter(self::$builtinAdapters[$adapter]);
-
- return $this;
- }
-
if ($adapter instanceof AdapterInterface) {
$this->registerAdapter($adapter);
return $this;
}
- throw new InvalidStorageAdapterException('Invalid storage adapter: '.get_class($adapter));
+ throw new InvalidStorageAdapterException('Invalid storage adapter.');
}
/**
- * Build the virtual storage.
- *
- * @param $callStrategy
- * @param LoggerInterface $logger
- *
- * @return VirtualStorageInterface
- *
- * @throws InvalidStorageAdapterException
+ * @param AdapterInterface $adapter
*/
- public function build(AbstractStorageCallStrategy $callStrategy = null, LoggerInterface $logger = null)
+ private function registerAdapter(AdapterInterface $adapter)
{
- if (!$this->hasLoadedAdapters()) {
- $this->addAdapter($this->getDefaultBuiltinAdapter());
- }
+ $this->adapters[] = $adapter;
+ $this->logger->log(LogLevel::INFO, 'Added new adapter {adapter}', ['adapter' => $adapter->getName()]);
+ }
- if ($callStrategy != null) {
- $this->setStrategy($callStrategy);
- }
- if ($logger != null) {
- $this->setLogger($logger);
+ private function getDefaultBuiltinAdapter()
+ {
+ return new FileSystemAdapter();
+ }
+
+ private function bindAdaptersLogger()
+ {
+ foreach ($this->adapters as $adapter) {
+ if ($adapter instanceof LoggerAwareInterface) {
+ $adapter->setLogger($this->logger);
+ }
}
+ }
- return $this->createStrategy();
+ /**
+ * @return AbstractStorageCallStrategy
+ */
+ private function createStrategy()
+ {
+ $strategy = $this->getStrategy();
+ $strategy->setLogger($this->logger);
+ $strategy->setAdapters($this->adapters);
+ $this->logger->log(
+ LogLevel::INFO,
+ 'Creating strategy {strategy}',
+ ['strategy' => $strategy->getStrategyName()]
+ );
+
+ return $strategy;
}
/**
@@ -144,54 +147,20 @@ public function getStrategy()
}
/**
- * Get the current Logger.
+ * Set a custom strategy.
*
- * @return LoggerInterface
- */
- public function getLogger()
- {
- return $this->logger;
- }
-
- /**
- * Check if one or more adapters has been loaded.
+ * @param AbstractStorageCallStrategy $strategy
*
- * @return bool
- */
- public function hasLoadedAdapters()
- {
- return !empty($this->adapters);
- }
-
- /**
- * @param AdapterInterface $adapter
- */
- private function registerAdapter(AdapterInterface $adapter)
- {
- if ($this->logger && $adapter instanceof LoggerAwareInterface) {
- $adapter->setLogger($this->logger);
- }
- $this->adapters[] = $adapter;
- $this->log(LogLevel::INFO, 'Added new adapter {{adapter}}', ['adapter' => $adapter->getName()]);
- }
-
- /**
* @return $this
*/
- private function addBuiltinAdapters()
+ public function setStrategy(AbstractStorageCallStrategy $strategy)
{
- if (!self::$builtInAdaptersLoaded) {
- self::$builtInAdaptersLoaded = true;
- foreach (glob(__DIR__.DIRECTORY_SEPARATOR.'Adapter'.DIRECTORY_SEPARATOR.'*.php') as $adapterFileName) {
- $className = __NAMESPACE__.'\\'.'Adapter'.'\\'.basename($adapterFileName, '.php');
- try {
- $class = new $className();
- self::$builtinAdapters[$class->getName()] = $class;
- } catch (\Exception $e) {
- $this->log(LogLevel::INFO, 'Impossible start {{className}} client', ['className' => $className]);
- }
- }
- }
+ $this->logger->log(
+ LogLevel::INFO,
+ 'Set the strategy {strategy}',
+ ['strategy' => $strategy->getStrategyName()]
+ );
+ $this->strategy = $strategy;
return $this;
}
@@ -204,43 +173,27 @@ private function getDefaultCallStrategy()
return DefaultStrategyFactory::create();
}
- private function getDefaultBuiltinAdapter()
- {
- return FileSystemAdapter::NAME;
- }
-
/**
- * @param $adapter
+ * Get the current Logger.
*
- * @throws StorageAdapterNotFoundException
+ * @return LoggerInterface
*/
- private function assertBuiltInAdapterExists($adapter)
- {
- if (!array_key_exists($adapter, self::$builtinAdapters)) {
- throw new StorageAdapterNotFoundException("Builtin storage \"$adapter\" not found");
- }
- }
-
- private function log($level, $msg, $context)
+ public function getLogger()
{
- if (!$this->getLogger()) {
- return;
- }
- $this->getLogger()->log($level, $msg, $context);
+ return $this->logger;
}
/**
- * @return AbstractStorageCallStrategy
+ * Set custom logger.
+ *
+ * @param LoggerInterface $logger
+ *
+ * @return $this
*/
- private function createStrategy()
+ public function setLogger(LoggerInterface $logger)
{
- $strategy = $this->getStrategy();
- $strategy->setAdapters($this->adapters);
- if ($this->getLogger()) {
- $strategy->setLogger($this->getLogger());
- }
- $this->log(LogLevel::INFO, 'Creating strategy {{strategy}}', ['strategy' => $strategy->getStrategyName()]);
+ $this->logger = $logger;
- return $strategy;
+ return $this;
}
}
diff --git a/src/Cmp/Storage/Strategy/AbstractStorageCallStrategy.php b/src/Cmp/Storage/Strategy/AbstractStorageCallStrategy.php
index 54d0cfc..da5da0b 100644
--- a/src/Cmp/Storage/Strategy/AbstractStorageCallStrategy.php
+++ b/src/Cmp/Storage/Strategy/AbstractStorageCallStrategy.php
@@ -5,41 +5,23 @@
use Cmp\Storage\AdapterInterface;
use Cmp\Storage\VirtualStorageInterface;
use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerAwareTrait;
use Psr\Log\LogLevel;
+use Psr\Log\NullLogger;
/**
* Class AbstractStorageCallStrategy.
*/
abstract class AbstractStorageCallStrategy implements VirtualStorageInterface, LoggerAwareInterface
{
- /**
- * @var LoggerInterface
- */
- private $logger;
+ use LoggerAwareTrait;
+
private $adapters;
public function __construct()
{
$this->adapters = [];
- }
-
- public function addAdapter(AdapterInterface $adapter)
- {
- $this->log(
- LogLevel::INFO,
- 'Add adapter "{{adapter}}" to strategy "{{strategy}}"',
- ['adapter' => $adapter->getName(), 'strategy' => $this->getStrategyName()]
- );
- $this->adapters[] = $adapter;
- }
-
- public function setAdapters(array $adapters)
- {
- $this->adapters = [];
- foreach ($adapters as $adapter) {
- $this->addAdapter($adapter);
- }
+ $this->logger = new NullLogger();
}
/**
@@ -50,27 +32,22 @@ public function getAdapters()
return $this->adapters;
}
- /**
- * @param LoggerInterface $logger
- */
- public function setLogger(LoggerInterface $logger)
+ public function setAdapters(array $adapters)
{
- $this->logger = $logger;
+ $this->adapters = [];
+ foreach ($adapters as $adapter) {
+ $this->addAdapter($adapter);
+ }
}
- /**
- * Logs with an arbitrary level.
- *
- * @param mixed $level
- * @param string $message
- * @param array $context
- */
- public function log($level, $message, array $context = array())
+ public function addAdapter(AdapterInterface $adapter)
{
- if (!$this->logger) {
- return;
- }
- $this->logger->log($level, $message, $context);
+ $this->logger->log(
+ LogLevel::INFO,
+ 'Add adapter "{adapter}" to strategy "{strategy}".',
+ ['adapter' => $adapter->getName(), 'strategy' => $this->getStrategyName()]
+ );
+ $this->adapters[] = $adapter;
}
abstract public function getStrategyName();
diff --git a/src/Cmp/Storage/Strategy/CallAllStrategy.php b/src/Cmp/Storage/Strategy/CallAllStrategy.php
index e6673db..4766f06 100644
--- a/src/Cmp/Storage/Strategy/CallAllStrategy.php
+++ b/src/Cmp/Storage/Strategy/CallAllStrategy.php
@@ -2,16 +2,15 @@
namespace Cmp\Storage\Strategy;
-use Cmp\Storage\Exception\AdapterException;
-use Cmp\Storage\Exception\FileNotFoundException;
-use Cmp\Storage\Exception\StorageException;
-use Psr\Log\LogLevel;
+use Cmp\Storage\AdapterInterface;
/**
* Class CallAllStrategy.
*/
class CallAllStrategy extends AbstractStorageCallStrategy
{
+ use RunAndLogTrait;
+
public function getStrategyName()
{
return 'CallAllStrategy';
@@ -26,11 +25,11 @@ public function getStrategyName()
*/
public function exists($path)
{
- $fn = function ($adapter) use ($path) {
+ $fn = function (AdapterInterface $adapter) use ($path) {
return $adapter->exists($path);
};
- return $this->runAllAndLog($fn);
+ return $this->runAll($fn);
}
/**
@@ -44,11 +43,11 @@ public function exists($path)
*/
public function get($path)
{
- $fn = function ($adapter) use ($path) {
+ $fn = function (AdapterInterface $adapter) use ($path) {
return $adapter->get($path);
};
- return $this->runOneAndLog($fn, $path);
+ return $this->logOnFalse($this->runOne($fn, $path), "Impossible get file: {file}.", ['file' => $path]);
}
/**
@@ -62,11 +61,15 @@ public function get($path)
*/
public function getStream($path)
{
- $fn = function ($adapter) use ($path) {
+ $fn = function (AdapterInterface $adapter) use ($path) {
return $adapter->getStream($path);
};
- return $this->runOneAndLog($fn, $path);
+ return $this->logOnFalse(
+ $this->runOne($fn, $path),
+ "Impossible get stream form file: {file}.",
+ ['file' => $path]
+ );
}
/**
@@ -80,28 +83,36 @@ public function getStream($path)
*/
public function rename($path, $newpath, $overwrite = false)
{
- $fn = function ($adapter) use ($path, $newpath, $overwrite) {
+ $fn = function (AdapterInterface $adapter) use ($path, $newpath, $overwrite) {
return $adapter->rename($path, $newpath, $overwrite);
};
- return $this->runAllAndLog($fn);
+ return $this->logOnFalse(
+ $this->runAll($fn),
+ "Impossible rename file from {from} to {to}.",
+ ['from' => $path, 'to' => $newpath]
+ );
}
/**
* Copy a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The new path of the file
+ * @param string $path Path to the existing file
+ * @param string $newpath The new path of the file
*
* @return bool
*/
public function copy($path, $newpath)
{
- $fn = function ($adapter) use ($path, $newpath) {
+ $fn = function (AdapterInterface $adapter) use ($path, $newpath) {
return $adapter->copy($path, $newpath);
};
- return $this->runAllAndLog($fn);
+ return $this->logOnFalse(
+ $this->runAll($fn),
+ "Impossible copy file from {from} to {to}.",
+ ['from' => $path, 'to' => $newpath]
+ );
}
/**
@@ -115,11 +126,11 @@ public function copy($path, $newpath)
*/
public function delete($path)
{
- $fn = function ($adapter) use ($path) {
+ $fn = function (AdapterInterface $adapter) use ($path) {
return $adapter->delete($path);
};
- return $this->runAllAndLog($fn);
+ return $this->logOnFalse($this->runAll($fn), "Impossible delete file {file}.", ['file' => $path]);
}
/**
@@ -134,11 +145,11 @@ public function delete($path)
*/
public function put($path, $contents)
{
- $fn = function ($adapter) use ($path, $contents) {
+ $fn = function (AdapterInterface $adapter) use ($path, $contents) {
return $adapter->put($path, $contents);
};
- return $this->runAllAndLog($fn);
+ return $this->logOnFalse($this->runAll($fn), "Impossible put file {file}.", ['file' => $path]);
}
/**
@@ -147,77 +158,16 @@ public function put($path, $contents)
* @param string $path The path to the file
* @param resource $resource The file handle
*
- * @throws \Cmp\Storage\Exception\InvalidArgumentException Thrown if $resource is not a resource
+ * @throws \InvalidArgumentException Thrown if $resource is not a resource
*
* @return bool True on success, false on failure
*/
public function putStream($path, $resource)
{
- $fn = function ($adapter) use ($path, $resource) {
+ $fn = function (AdapterInterface $adapter) use ($path, $resource) {
return $adapter->putStream($path, $resource);
};
- return $this->runAllAndLog($fn);
- }
-
- /**
- * @param callable $fn
- *
- * @return bool
- */
- private function runAllAndLog(callable $fn)
- {
- $result = false;
-
- foreach ($this->getAdapters() as $adapter) {
- try {
- $result = $fn($adapter) || $result;
- } catch (\Exception $e) {
- $this->logAdapterException($adapter, $e);
- }
- }
-
- return $result;
- }
-
- /**
- * Gets one file from all the adapters.
- *
- * @param callable $fn
- * @param string $path
- *
- * @return mixed
- *
- * @throws StorageException
- */
- private function runOneAndLog(callable $fn, $path)
- {
- $defaultException = new FileNotFoundException($path);
- foreach ($this->getAdapters() as $adapter) {
- try {
- $file = $fn($adapter);
- if ($file !== false) {
- return $file;
- }
- } catch (\Exception $exception) {
- $defaultException = new AdapterException($path, $exception);
- $this->logAdapterException($adapter, $exception);
- }
- }
-
- throw $defaultException;
- }
-
- /**
- * @param \Cmp\Storage\AdapterInterface $adapter
- * @param \Exception $e
- */
- private function logAdapterException($adapter, $e)
- {
- $this->log(
- LogLevel::ERROR,
- 'Adapter "'.$adapter->getName().'" fails.',
- ['exception' => $e]
- );
+ return $this->logOnFalse($this->runAll($fn), "Impossible put file stream {file}.", ['file' => $path]);
}
}
diff --git a/src/Cmp/Storage/Strategy/FallBackChainStrategy.php b/src/Cmp/Storage/Strategy/FallBackChainStrategy.php
index 02a7bca..13d0738 100644
--- a/src/Cmp/Storage/Strategy/FallBackChainStrategy.php
+++ b/src/Cmp/Storage/Strategy/FallBackChainStrategy.php
@@ -5,13 +5,14 @@
use Cmp\Storage\AdapterInterface;
use Cmp\Storage\Exception\FileExistsException;
use Cmp\Storage\Exception\FileNotFoundException;
-use Psr\Log\LogLevel;
/**
* Class FallBackChainStrategy.
*/
class FallBackChainStrategy extends AbstractStorageCallStrategy
{
+ use RunAndLogTrait;
+
public function getStrategyName()
{
return 'FallBackChainStrategy';
@@ -28,7 +29,7 @@ public function exists($path)
return $adapter->exists($path);
};
- return $this->runChainAndLog($fn);
+ return $this->runChain($fn);
}
/**
@@ -46,7 +47,7 @@ public function get($path)
return $adapter->get($path);
};
- return $this->runChainAndLog($fn);
+ return $this->logOnFalse($this->runChain($fn), "Impossible get file: {file}.", ['file' => $path]);
}
/**
@@ -64,14 +65,18 @@ public function getStream($path)
return $adapter->getStream($path);
};
- return $this->runChainAndLog($fn);
+ return $this->logOnFalse(
+ $this->runChain($fn),
+ "Impossible get stream form file: {file}.",
+ ['file' => $path]
+ );
}
/**
* Rename a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The new path of the file
+ * @param string $path Path to the existing file
+ * @param string $newpath The new path of the file
* @param bool $overwrite
*
* @return bool
@@ -84,14 +89,18 @@ public function rename($path, $newpath, $overwrite = false)
return $adapter->rename($path, $newpath, $overwrite);
};
- return $this->runChainAndLog($fn);
+ return $this->logOnFalse(
+ $this->runChain($fn),
+ "Impossible rename file from {from} to {to}.",
+ ['from' => $path, 'to' => $newpath]
+ );
}
/**
* Copy a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The new path of the file
+ * @param string $path Path to the existing file
+ * @param string $newpath The new path of the file
*
* @return bool
*
@@ -103,7 +112,11 @@ public function copy($path, $newpath)
return $adapter->copy($path, $newpath);
};
- return $this->runChainAndLog($fn);
+ return $this->logOnFalse(
+ $this->runChain($fn),
+ "Impossible copy file from {from} to {to}.",
+ ['from' => $path, 'to' => $newpath]
+ );
}
/**
@@ -121,7 +134,7 @@ public function delete($path)
return $adapter->delete($path);
};
- return $this->runChainAndLog($fn);
+ return $this->logOnFalse($this->runChain($fn), "Impossible delete file {file}.", ['file' => $path]);
}
/**
@@ -140,7 +153,7 @@ public function put($path, $contents)
return $adapter->put($path, $contents);
};
- return $this->runChainAndLog($fn);
+ return $this->logOnFalse($this->runChain($fn), "Impossible put file {file}.", ['file' => $path]);
}
/**
@@ -149,7 +162,7 @@ public function put($path, $contents)
* @param string $path The path to the file
* @param resource $resource The file handle
*
- * @throws \Cmp\Storage\InvalidArgumentException Thrown if $resource is not a resource
+ * @throws \InvalidArgumentException Thrown if $resource is not a resource
*
* @return bool True on success, false on failure
*/
@@ -159,57 +172,6 @@ public function putStream($path, $resource)
return $adapter->putStream($path, $resource);
};
- return $this->runChainAndLog($fn);
- }
-
- /**
- * Executes the operation in all adapters, returning on the first success or false if at least one executed the
- * operation without raising exceptions.
- *
- * @param callable $fn
- *
- * @return mixed If all adapters raised exceptions, the first one will be thrown
- *
- * @throws bool
- */
- private function runChainAndLog(callable $fn)
- {
- $firstException = false;
- $call = false;
- $result = false;
- foreach ($this->getAdapters() as $adapter) {
- try {
- $result = $fn($adapter);
- $call = true;
- if ($result !== false) {
- return $result;
- }
- } catch (\Exception $exception) {
- if (!$firstException) {
- $firstException = $exception;
- }
- $this->logAdapterException($adapter, $exception);
- }
- }
-
- // Result will be set if at least one adapters executed the operation without exceptions
- if ($call) {
- return $result;
- }
-
- throw $firstException;
- }
-
- /**
- * @param \Cmp\Storage\VirtualStorageInterface $adapter
- * @param \Exception $e
- */
- private function logAdapterException($adapter, $e)
- {
- $this->log(
- LogLevel::ERROR,
- 'Adapter "'.$adapter->getName().'" fails.',
- ['exception' => $e]
- );
+ return $this->logOnFalse($this->runChain($fn), "Impossible put file stream {file}.", ['file' => $path]);
}
}
diff --git a/src/Cmp/Storage/Strategy/RunAndLogTrait.php b/src/Cmp/Storage/Strategy/RunAndLogTrait.php
new file mode 100644
index 0000000..1b972c8
--- /dev/null
+++ b/src/Cmp/Storage/Strategy/RunAndLogTrait.php
@@ -0,0 +1,129 @@
+getAdapters() as $adapter) {
+ try {
+ $result = $fn($adapter);
+ $call = true;
+ if ($result !== false) {
+ return $result;
+ }
+ } catch (\Exception $exception) {
+ if (!$firstException) {
+ $firstException = $exception;
+ }
+ $this->logAdapterException($adapter, $exception);
+ }
+ }
+
+ // Result will be set if at least one adapters executed the operation without exceptions
+ if ($call) {
+ return $result;
+ }
+
+ throw $firstException;
+ }
+
+ /**
+ * @param AdapterInterface $adapter
+ * @param \Exception $exception
+ */
+ private function logAdapterException(AdapterInterface $adapter, \Exception $exception)
+ {
+ $this->logger->log(
+ LogLevel::ERROR,
+ 'Adapter "'.$adapter->getName().'" fails.',
+ ['exception' => $exception]
+ );
+ }
+
+ /**
+ * @param $result
+ * @param $msg
+ * @param array $context
+ *
+ * @return mixed
+ */
+ private function logOnFalse($result, $msg, array $context = [])
+ {
+ if ($result) {
+ return $result;
+ }
+
+ $this->logger->log(LogLevel::ERROR, $msg, $context);
+
+ return $result;
+ }
+
+ /**
+ * @param callable $fn
+ *
+ * @return bool
+ */
+ private function runAll(callable $fn)
+ {
+ $result = false;
+
+ foreach ($this->getAdapters() as $adapter) {
+ try {
+ $result = $fn($adapter) || $result;
+ } catch (\Exception $e) {
+ $this->logAdapterException($adapter, $e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Gets one file from all the adapters.
+ *
+ * @param callable $fn
+ * @param string $path
+ *
+ * @return mixed
+ *
+ * @throws AdapterException|\Exception
+ */
+ private function runOne(callable $fn, $path)
+ {
+ $defaultException = new FileNotFoundException($path);
+ foreach ($this->getAdapters() as $adapter) {
+ try {
+ $file = $fn($adapter);
+ if ($file !== false) {
+ return $file;
+ }
+ } catch (\Exception $exception) {
+ $defaultException = new AdapterException($path, $exception);
+ $this->logAdapterException($adapter, $exception);
+ }
+ }
+
+ throw $defaultException;
+ }
+}
diff --git a/src/Cmp/Storage/VirtualPath.php b/src/Cmp/Storage/VirtualPath.php
index cd6017d..0a1ac74 100644
--- a/src/Cmp/Storage/VirtualPath.php
+++ b/src/Cmp/Storage/VirtualPath.php
@@ -2,9 +2,6 @@
namespace Cmp\Storage;
-use Cmp\Storage\Exception\InvalidPathException;
-use Cmp\Storage\Exception\RelativePathNotAllowed;
-
/**
* Class VirtualPath.
*/
@@ -22,20 +19,10 @@ class VirtualPath
*/
public function __construct($path)
{
-
$this->path = $this->makePathAbsolute($path);
$this->path = $this->canonicalize($this->path);
}
- /**
- * @return string
- */
- public function getPath()
- {
- return $this->path;
- }
-
-
/**
* @param $path
*
@@ -48,7 +35,6 @@ public function makePathAbsolute($path)
}
return $path;
-
}
/**
@@ -76,9 +62,9 @@ public function isAbsolutePath($path)
*/
private function canonicalize($path)
{
- $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
- $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
- $absolutes = array();
+ $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
+ $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
+ $absolutes = [];
foreach ($parts as $part) {
if ('.' == $part) {
continue;
@@ -106,4 +92,12 @@ public function isChild(VirtualPath $path)
return strpos($path->getPath(), $this->getPath()) === 0;
}
+
+ /**
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
}
diff --git a/src/Cmp/Storage/VirtualStorageInterface.php b/src/Cmp/Storage/VirtualStorageInterface.php
index 292c5f4..9e4a968 100644
--- a/src/Cmp/Storage/VirtualStorageInterface.php
+++ b/src/Cmp/Storage/VirtualStorageInterface.php
@@ -2,6 +2,10 @@
namespace Cmp\Storage;
+use Cmp\Storage\Exception\FileNotFoundException;
+use Cmp\Storage\Exception\InvalidPathException;
+use InvalidArgumentException;
+
/**
* Interface VirtualStorageInterface.
*/
@@ -21,7 +25,7 @@ public function exists($path);
*
* @param string $path The path to the file
*
- * @throws \Cmp\Storage\FileNotFoundException
+ * @throws FileNotFoundException
*
* @return string The file contents or false on failure
*/
@@ -32,7 +36,7 @@ public function get($path);
*
* @param string $path The path to the file
*
- * @throws \Cmp\Storage\FileNotFoundException
+ * @throws FileNotFoundException
*
* @return resource The path resource or false on failure
*/
@@ -41,20 +45,19 @@ public function getStream($path);
/**
* Rename a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The new path of the file
+ * @param string $path Path to the existing file
+ * @param string $newpath The new path of the file
* @param bool $overwrite
*
* @return bool
*/
public function rename($path, $newpath, $overwrite = false);
-
/**
* Rename a file.
*
- * @param string $path Path to the existing file
- * @param string $newpath The destination path of the copy
+ * @param string $path Path to the existing file
+ * @param string $newpath The destination path of the copy
*
* @return bool
*/
@@ -77,7 +80,7 @@ public function delete($path);
*
* @return bool True on success, false on failure
*
- * @throws \Cmp\Storage\InvalidPathException
+ * @throws InvalidPathException
*/
public function put($path, $contents);
@@ -87,8 +90,8 @@ public function put($path, $contents);
* @param string $path The path to the file
* @param resource $resource The file handle
*
- * @throws \Cmp\Storage\InvalidPathException
- * @throws \Cmp\Storage\InvalidArgumentException Thrown if $resource is not a resource
+ * @throws InvalidPathException
+ * @throws InvalidArgumentException Thrown if $resource is not a resource
*
* @return bool True on success, false on failure
*/
diff --git a/test/spec/Cmp/Storage/StorageBuilderSpec.php b/test/spec/Cmp/Storage/StorageBuilderSpec.php
index 6208df2..34149d6 100644
--- a/test/spec/Cmp/Storage/StorageBuilderSpec.php
+++ b/test/spec/Cmp/Storage/StorageBuilderSpec.php
@@ -28,11 +28,6 @@ public function it_allows_setting_a_logger(LoggerInterface $l)
$this->getLogger()->shouldBe($l);
}
- public function it_allows_add_builtin_adapters()
- {
- $this->addAdapter('FileSystem')->shouldBe($this);
- }
-
public function it_allows_add_already_initialized_adapter(AdapterInterface $vi)
{
$this->addAdapter($vi)->shouldBe($this);
@@ -40,9 +35,8 @@ public function it_allows_add_already_initialized_adapter(AdapterInterface $vi)
public function it_throw_and_exception_when_the_adapter_is_not_valid()
{
- $this->shouldThrow('\Cmp\Storage\Exception\StorageAdapterNotFoundException')->during('addAdapter', ['string']);
+ $this->shouldThrow('\Cmp\Storage\Exception\InvalidStorageAdapterException')->during('addAdapter', ['string']);
$s = new \stdClass();
- $this->shouldThrow('\Cmp\Storage\Exception\InvalidStorageAdapterException')->during('addAdapter', [$s, []]);
$this->shouldThrow('\Cmp\Storage\Exception\InvalidStorageAdapterException')->during('addAdapter', [$s]);
}
@@ -57,7 +51,10 @@ public function it_injects_logger_if_is_possible(LoggerInterface $loggerInterfac
$this->setLogger($loggerInterface);
$this->addAdapter($adapterWithLogger);
+ $this->build();
+
$adapterWithLogger->setLogger(Argument::any())->shouldHaveBeenCalled();
+
}
public function it_loads_the_the_default_if_no_other_has_been_been_added(AbstractStorageCallStrategy $callStrategy)
@@ -95,12 +92,4 @@ public function it_allows_specify_different_call_strategies(
$storage->getStrategyName()->shouldBe($strategyName);
}
- public function it_builds_a_virtual_storage_with_specific_call_strategy_and_logger_provider(
- AdapterInterface $a,
- AbstractStorageCallStrategy $callStrategy,
- LoggerInterface $loggerInterface
- ) {
- $this->addAdapter($a);
- $this->build($callStrategy, $loggerInterface)->shouldHaveType('\Cmp\Storage\VirtualStorageInterface');
- }
}
diff --git a/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php b/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php
index 59f9206..f0f93ad 100644
--- a/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php
+++ b/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php
@@ -2,6 +2,7 @@
namespace spec\Cmp\Storage\Strategy;
+use Cmp\Storage\Adapter\FileSystemAdapter;
use Cmp\Storage\AdapterInterface;
use Cmp\Storage\Exception\FileNotFoundException;
use PhpSpec\ObjectBehavior;
@@ -81,7 +82,7 @@ public function it_wraps_the_rename_call(
AdapterInterface $adapter2,
AdapterInterface $adapter3
) {
- $path = '/b/c';
+ $path = '/b/c';
$newpath = '/b/d';
$adapter1->rename($path, $newpath, false)->willReturn(true);
$adapter2->rename($path, $newpath, false)->willReturn(true);
@@ -94,13 +95,12 @@ public function it_wraps_the_rename_call(
$this->rename($path, $newpath, true)->shouldBe(true);
}
-
public function it_wraps_the_copy_call(
AdapterInterface $adapter1,
AdapterInterface $adapter2,
AdapterInterface $adapter3
) {
- $path = '/b/c';
+ $path = '/b/c';
$newpath = '/b/d';
$adapter1->copy($path, $newpath)->willReturn(true);
$adapter2->copy($path, $newpath)->willReturn(true);
@@ -108,7 +108,6 @@ public function it_wraps_the_copy_call(
$this->copy($path, $newpath)->shouldBe(true);
}
-
public function it_wraps_the_delete_call(
AdapterInterface $adapter1,
AdapterInterface $adapter2,
@@ -138,7 +137,7 @@ public function it_wraps_the_get_and_getStream_calls(
AdapterInterface $adapter2,
AdapterInterface $adapter3
) {
- $path = '/b/c';
+ $path = '/b/c';
$contents = 'hi!';
$adapter1->get($path)->willReturn($contents);
$this->get($path)->shouldBe($contents);
@@ -169,9 +168,9 @@ public function it_wraps_the_put_and_putStream_calls(
AdapterInterface $adapter2,
AdapterInterface $adapter3
) {
- $path = '/b/c';
+ $path = '/b/c';
$contents = 'hi!';
- $stream = 'stream';
+ $stream = 'stream';
$adapter1->put($path, $contents)->willReturn(true);
$adapter2->put($path, $contents)->willReturn(true);
$adapter3->put($path, $contents)->willReturn(true);
@@ -182,4 +181,21 @@ public function it_wraps_the_put_and_putStream_calls(
$adapter3->putStream($path, $stream)->willReturn(true);
$this->putStream($path, $stream)->shouldBe(true);
}
+
+ public function it_log_on_action_error(FileSystemAdapter $dummyAdapter, LoggerInterface $logger)
+ {
+
+ $dummyAdapter->getName()->willReturn("Dummy adapter");
+ $this->setLogger($logger);
+ $dummyAdapter->setLogger($logger);
+ $logger->log(LogLevel::INFO,'Add adapter "{adapter}" to strategy "{strategy}".',["adapter" => "Dummy adapter", "strategy" => "CallAllStrategy"])->shouldBeCalled();
+ $this->addAdapter($dummyAdapter);
+
+ $path = '/b/c';
+ $contents = 'hi!';
+ $dummyAdapter->put($path, $contents)->willReturn(false);
+ $logger->log(LogLevel::ERROR,"Impossible put file {file}.",["file" => "/b/c"])->shouldBeCalled();
+ $this->put($path, $contents)->shouldBe(false);
+
+ }
}