diff --git a/.gitignore b/.gitignore
index ff2e469..43c4eb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ bin
coverage
coverage.xml
dev
+/.phpunit.result.cache
diff --git a/composer.json b/composer.json
index 3972104..09318c5 100755
--- a/composer.json
+++ b/composer.json
@@ -9,25 +9,25 @@
}
],
"require": {
- "php": ">=5.5.0",
- "league/flysystem": "~1.0",
- "google/cloud-storage": "~1.0"
+ "php": "^8.0",
+ "google/cloud-storage": "~1.0",
+ "league/flysystem": "^3.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.0",
- "mockery/mockery": "0.9.*"
+ "phpunit/phpunit": "^9.5",
+ "roave/security-advisories": "dev-latest"
},
"autoload": {
"psr-4": {
"Superbalist\\Flysystem\\GoogleStorage\\": "src/"
}
},
- "config": {
- "bin-dir": "bin"
- },
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
+ "autoload-dev": {
+ "psr-4": {
+ "Superbalist\\Flysystem\\GoogleStorage\\Test\\": "tests/"
}
+ },
+ "config": {
+ "sort-packages": true
}
}
diff --git a/phpunit.xml b/phpunit.xml
index d870901..7d8e09d 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -8,25 +8,26 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
- syntaxCheck="true"
verbose="true"
>
+
./tests/
-
-
- ./src/
-
-
-
-
-
+
+
+
+ src
+
+
+
+
+
diff --git a/src/GoogleStorageAdapter.php b/src/GoogleStorageAdapter.php
index d5e6c9d..1be392d 100755
--- a/src/GoogleStorageAdapter.php
+++ b/src/GoogleStorageAdapter.php
@@ -8,123 +8,187 @@
use Google\Cloud\Storage\StorageClient;
use Google\Cloud\Storage\StorageObject;
use GuzzleHttp\Psr7\StreamWrapper;
-use League\Flysystem\Adapter\AbstractAdapter;
-use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
-use League\Flysystem\Util;
+use League\Flysystem\FileAttributes;
+use League\Flysystem\FilesystemAdapter;
+use League\Flysystem\UnableToCopyFile;
+use League\Flysystem\Visibility;
-class GoogleStorageAdapter extends AbstractAdapter
+class GoogleStorageAdapter implements FilesystemAdapter
{
- /**
- * @const STORAGE_API_URI_DEFAULT
- */
- const STORAGE_API_URI_DEFAULT = 'https://storage.googleapis.com';
+ public const STORAGE_API_URI_DEFAULT = 'https://storage.googleapis.com';
+
+ protected StorageClient $storageClient;
+ protected Bucket $bucket;
+ protected ?string $pathPrefix = null;
+ protected string $pathSeparator = '/';
+ protected string $storageApiUri;
+
+ public function __construct(
+ StorageClient $storageClient,
+ Bucket $bucket,
+ string $pathPrefix = null,
+ string $storageApiUri = null
+ ) {
+ $this->storageClient = $storageClient;
+ $this->bucket = $bucket;
- /**
- * @var StorageClient
- */
- protected $storageClient;
+ if ($pathPrefix) {
+ $this->setPathPrefix($pathPrefix);
+ }
+
+ $this->storageApiUri = ($storageApiUri) ?: self::STORAGE_API_URI_DEFAULT;
+ }
/**
- * @var Bucket
+ * Prefix a path.
+ *
+ * The method grabbed from class \League\Flysystem\Adapter\AbstractAdapter of league/flysystem:dev-1.0.x.
+ * It is public for the backward compatibility only.
*/
- protected $bucket;
+ public function applyPathPrefix(string $path): string
+ {
+ return $this->getPathPrefix() . ltrim($path, '\\/');
+ }
+
+ public function directoryExists(string $path): bool
+ {
+ $object = $this->getObject($path);
+
+ return str_ends_with($object->name(), '/') && $object->exists();
+ }
+
+ public function fileExists(string $path): bool
+ {
+ return $this->getObject($path)->exists();
+ }
+
+ public function fileSize(string $path): FileAttributes
+ {
+ $metadata = $this->getMetadata($path);
+
+ return new FileAttributes($metadata['path'], $metadata['size']);
+ }
+
+ public function getFileAttributes(string $path): FileAttributes
+ {
+ $metadata = $this->getMetadata($path);
+
+ return new FileAttributes(
+ $metadata['path'],
+ $metadata['size'],
+ $this->getRawVisibility($path),
+ $metadata['timestamp'],
+ $metadata['mimetype'],
+ [
+ 'dirname' => $metadata['dirname'],
+ 'type' => $metadata['type'],
+ ],
+ );
+ }
/**
- * @var string
+ * The method grabbed from class \League\Flysystem\Adapter\AbstractAdapter of league/flysystem:dev-1.0.x.
+ * It is public for the backward compatibility only.
*/
- protected $storageApiUri;
+ public function getPathPrefix(): ?string
+ {
+ return $this->pathPrefix;
+ }
/**
- * @param StorageClient $storageClient
- * @param Bucket $bucket
- * @param string $pathPrefix
- * @param string $storageApiUri
+ * The method grabbed from class \League\Flysystem\Adapter\AbstractAdapter of league/flysystem:dev-1.0.x.
+ * It is public for the backward compatibility only.
*/
- public function __construct(StorageClient $storageClient, Bucket $bucket, $pathPrefix = null, $storageApiUri = null)
+ public function setPathPrefix(?string $prefix): void
{
- $this->storageClient = $storageClient;
- $this->bucket = $bucket;
+ $prefix = (string) $prefix;
- if ($pathPrefix) {
- $this->setPathPrefix($pathPrefix);
+ if ($prefix === '') {
+ $this->pathPrefix = null;
+
+ return;
}
- $this->storageApiUri = ($storageApiUri) ?: self::STORAGE_API_URI_DEFAULT;
+ $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator;
}
- /**
- * Returns the StorageClient.
- *
- * @return StorageClient
- */
- public function getStorageClient()
+ public function getStorageClient(): StorageClient
{
return $this->storageClient;
}
- /**
- * Return the Bucket.
- *
- * @return \Google\Cloud\Storage\Bucket
- */
- public function getBucket()
+ public function getBucket(): Bucket
{
return $this->bucket;
}
- /**
- * Set the storage api uri.
- *
- * @param string $uri
- */
- public function setStorageApiUri($uri)
+ public function setStorageApiUri(string $uri): void
{
$this->storageApiUri = $uri;
}
+ public function getStorageApiUri(): string
+ {
+ return $this->storageApiUri;
+ }
+
+ public function lastModified(string $path): FileAttributes
+ {
+ $metadata = $this->getMetadata($path);
+
+ return new FileAttributes($metadata['path'], null, null, $metadata['timestamp']);
+ }
+
+ public function mimeType(string $path): FileAttributes
+ {
+ $metadata = $this->getMetadata($path);
+
+ return new FileAttributes($metadata['path'], null, null, null, $metadata['mimetype']);
+ }
+
/**
- * Return the storage api uri.
- *
- * @return string
+ * The method grabbed from class \League\Flysystem\Adapter\AbstractAdapter of league/flysystem:dev-1.0.x.
+ * It is public for the backward compatibility only.
*/
- public function getStorageApiUri()
+ public function removePathPrefix(string $path): string
{
- return $this->storageApiUri;
+ return substr($path, strlen($this->getPathPrefix()));
+ }
+
+ public function visibility(string $path): FileAttributes
+ {
+ return new FileAttributes($path, null, $this->getRawVisibility($path));
}
/**
* {@inheritdoc}
*/
- public function write($path, $contents, Config $config)
+ public function write(string $path, string $contents, Config $config): void
{
- return $this->upload($path, $contents, $config);
+ $this->upload($path, $contents, $config);
}
/**
* {@inheritdoc}
*/
- public function writeStream($path, $resource, Config $config)
+ public function writeStream(string $path, $contents, Config $config): void
{
- return $this->upload($path, $resource, $config);
+ $this->upload($path, $contents, $config);
}
/**
- * {@inheritdoc}
- *
* @codeCoverageIgnore
*/
- public function update($path, $contents, Config $config)
+ public function update(string $path, $contents, Config $config): array
{
return $this->upload($path, $contents, $config);
}
/**
- * {@inheritdoc}
- *
* @codeCoverageIgnore
*/
- public function updateStream($path, $resource, Config $config)
+ public function updateStream(string $path, $resource, Config $config): array
{
return $this->upload($path, $resource, $config);
}
@@ -132,21 +196,15 @@ public function updateStream($path, $resource, Config $config)
/**
* Returns an array of options from the config.
*
- * @param Config $config
- *
- * @return array
+ * @return array
*/
- protected function getOptionsFromConfig(Config $config)
+ protected function getOptionsFromConfig(Config $config): array
{
$options = [];
-
- if ($visibility = $config->get('visibility')) {
- $options['predefinedAcl'] = $this->getPredefinedAclForVisibility($visibility);
- } else {
- // if a file is created without an acl, it isn't accessible via the console
- // we therefore default to private
- $options['predefinedAcl'] = $this->getPredefinedAclForVisibility(AdapterInterface::VISIBILITY_PRIVATE);
- }
+ // if a file is created without an acl, it isn't accessible via the console
+ // we therefore default to private
+ $visibility = $config->get('visibility') ?: Visibility::PRIVATE;
+ $options['predefinedAcl'] = $this->getPredefinedAclForVisibility($visibility);
if ($metadata = $config->get('metadata')) {
$options['metadata'] = $metadata;
@@ -158,13 +216,18 @@ protected function getOptionsFromConfig(Config $config)
/**
* Uploads a file to the Google Cloud Storage service.
*
- * @param string $path
* @param string|resource $contents
- * @param Config $config
*
- * @return array
+ * @return array{
+ * type: string
+ * dirname: string
+ * path: string
+ * timestamp: int
+ * mimetype: string
+ * size: int
+ * }
*/
- protected function upload($path, $contents, Config $config)
+ protected function upload(string $path, $contents, Config $config): array
{
$path = $this->applyPathPrefix($path);
@@ -179,26 +242,31 @@ protected function upload($path, $contents, Config $config)
/**
* Returns a dictionary of object metadata from an object.
*
- * @param StorageObject $object
- *
- * @return array
+ * @return array{
+ * type: string
+ * dirname: string
+ * path: string
+ * timestamp: int
+ * mimetype: string
+ * size: int
+ * }
*/
- protected function normaliseObject(StorageObject $object)
+ protected function normaliseObject(StorageObject $object): array
{
$name = $this->removePathPrefix($object->name());
$info = $object->info();
- $isDir = substr($name, -1) === '/';
+ $isDir = str_ends_with($name, '/');
if ($isDir) {
$name = rtrim($name, '/');
}
return [
'type' => $isDir ? 'dir' : 'file',
- 'dirname' => Util::dirname($name),
+ 'dirname' => $this->dirname($name),
'path' => $name,
'timestamp' => strtotime($info['updated']),
- 'mimetype' => isset($info['contentType']) ? $info['contentType'] : '',
+ 'mimetype' => $info['contentType'] ?? '',
'size' => $info['size'],
];
}
@@ -206,55 +274,61 @@ protected function normaliseObject(StorageObject $object)
/**
* {@inheritdoc}
*/
- public function rename($path, $newpath)
+ public function move(string $source, string $destination, Config $config): void
{
- if (!$this->copy($path, $newpath)) {
- return false;
+ try {
+ $this->copy($source, $destination, $config);
+ } catch (UnableToCopyFile $exception) {
+ $this->delete($source);
}
-
- return $this->delete($path);
}
/**
* {@inheritdoc}
*/
- public function copy($path, $newpath)
+ public function copy(string $source, string $destination, Config $config): void
{
- $newpath = $this->applyPathPrefix($newpath);
+ $destination = $this->applyPathPrefix($destination);
// we want the new file to have the same visibility as the original file
- $visibility = $this->getRawVisibility($path);
+ $visibility = $this->getRawVisibility($source);
$options = [
- 'name' => $newpath,
+ 'name' => $destination,
'predefinedAcl' => $this->getPredefinedAclForVisibility($visibility),
];
- $this->getObject($path)->copy($this->bucket, $options);
-
- return true;
+ if (!$this->getObject($source)->copy($this->bucket, $options)->exists()) {
+ throw UnableToCopyFile::fromLocationTo($source, $destination);
+ }
}
/**
* {@inheritdoc}
*/
- public function delete($path)
+ public function delete(string $path): void
{
$this->getObject($path)->delete();
-
- return true;
}
/**
- * {@inheritdoc}
+ * @deprecated Use {@see self::deleteDirectory() }
+ * @codeCoverageIgnore
*/
- public function deleteDir($dirname)
+ public function deleteDir(string $dirname): void
+ {
+ @trigger_error(sprintf('Method "%s:deleteDir()" id deprecated. Use "%1$s:deleteDirectory()"', __CLASS__), \E_USER_DEPRECATED);
+
+ $this->deleteDirectory($dirname);
+ }
+
+ public function deleteDirectory(string $path): void
{
- $dirname = $this->normaliseDirName($dirname);
- $objects = $this->listContents($dirname, true);
+ $path = $this->normalizeDirPostfix($path);
+ $objects = $this->listContents($path, true);
// We first delete the file, so that we can delete
// the empty folder at the end.
- uasort($objects, function ($a, $b) {
+ uasort($objects, static function (array $a, array $b): int {
return $b['type'] === 'file' ? 1 : -1;
});
@@ -263,10 +337,10 @@ public function deleteDir($dirname)
foreach ($objects as $object) {
// normalise directories path
if ($object['type'] === 'dir') {
- $object['path'] = $this->normaliseDirName($object['path']);
+ $object['path'] = $this->normalizeDirPostfix($object['path']);
}
- if (strpos($object['path'], $dirname) !== false) {
+ if (str_contains($object['path'], $path)) {
$filtered_objects[] = $object;
}
}
@@ -275,139 +349,183 @@ public function deleteDir($dirname)
foreach ($filtered_objects as $object) {
$this->delete($object['path']);
}
-
- return true;
}
/**
- * {@inheritdoc}
+ * @deprecated Use {@see self::createDirectory() }
+ * @codeCoverageIgnore
+ *
+ * @return array{
+ * type: string
+ * dirname: string
+ * path: string
+ * timestamp: int
+ * mimetype: string
+ * size: int
+ * }
*/
- public function createDir($dirname, Config $config)
+ public function createDir(string $dirname, Config $config): array
{
- return $this->upload($this->normaliseDirName($dirname), '', $config);
+ @trigger_error(sprintf('Method "%s:createDir()" id deprecated. Use "%1$s:createDirectory()"', __CLASS__), \E_USER_DEPRECATED);
+
+ return $this->upload($this->normalizeDirPostfix($dirname), '', $config);
}
/**
- * Returns a normalised directory name from the given path.
- *
- * @param string $dirname
- *
- * @return string
+ * {@inheritdoc}
*/
- protected function normaliseDirName($dirname)
+ public function createDirectory(string $path, Config $config): void
{
- return rtrim($dirname, '/') . '/';
+ $this->upload($this->normalizeDirPostfix($path), '', $config);
}
/**
* {@inheritdoc}
*/
- public function setVisibility($path, $visibility)
+ public function setVisibility(string $path, string $visibility): void
{
$object = $this->getObject($path);
- if ($visibility === AdapterInterface::VISIBILITY_PRIVATE) {
+ if ($visibility === Visibility::PRIVATE) {
$object->acl()->delete('allUsers');
- } elseif ($visibility === AdapterInterface::VISIBILITY_PUBLIC) {
+ } elseif ($visibility === Visibility::PUBLIC) {
$object->acl()->add('allUsers', Acl::ROLE_READER);
}
-
- $normalised = $this->normaliseObject($object);
- $normalised['visibility'] = $visibility;
-
- return $normalised;
}
/**
- * {@inheritdoc}
+ * @deprecated Use {@see self::fileExists() }
+ * @codeCoverageIgnore
*/
- public function has($path)
+ public function has(string $path): bool
{
+ @trigger_error(sprintf('Method "%s:has()" id deprecated. Use "%1$s:fileExists()"', __CLASS__), \E_USER_DEPRECATED);
+
return $this->getObject($path)->exists();
}
/**
* {@inheritdoc}
*/
- public function read($path)
+ public function read(string $path): string
{
- $object = $this->getObject($path);
- $contents = $object->downloadAsString();
-
- $data = $this->normaliseObject($object);
- $data['contents'] = $contents;
-
- return $data;
+ return $this->getObject($path)->downloadAsString();
}
/**
* {@inheritdoc}
*/
- public function readStream($path)
+ public function readStream(string $path)
{
$object = $this->getObject($path);
- $data = $this->normaliseObject($object);
- $data['stream'] = StreamWrapper::getResource($object->downloadAsStream());
-
- return $data;
+ return StreamWrapper::getResource($object->downloadAsStream());
}
/**
* {@inheritdoc}
*/
- public function listContents($directory = '', $recursive = false)
+ public function listContents(string $path = '', bool $deep = false): iterable
{
- $directory = $this->applyPathPrefix($directory);
+ $path = $this->applyPathPrefix($path);
- $objects = $this->bucket->objects(['prefix' => $directory]);
+ $objects = $this->bucket->objects(['prefix' => $path]);
$normalised = [];
foreach ($objects as $object) {
$normalised[] = $this->normaliseObject($object);
}
- return Util::emulateDirectories($normalised);
+ return $this->emulateDirectories($normalised);
}
/**
- * {@inheritdoc}
+ * @return array{
+ * type: string
+ * dirname: string
+ * path: string
+ * timestamp: int
+ * mimetype: string
+ * size: int
+ * }
*/
- public function getMetadata($path)
+ public function getMetadata(string $path): array
{
$object = $this->getObject($path);
+
return $this->normaliseObject($object);
}
/**
- * {@inheritdoc}
+ * @deprecated Use {@see self::fileSize() }
+ * @codeCoverageIgnore
+ *
+ * @return array{
+ * type: string
+ * dirname: string
+ * path: string
+ * timestamp: int
+ * mimetype: string
+ * size: int
+ * }
*/
- public function getSize($path)
+ public function getSize(string $path): array
{
+ @trigger_error(sprintf('Method "%s:getSize()" id deprecated. Use "%1$s:fileSize()"', __CLASS__), \E_USER_DEPRECATED);
+
return $this->getMetadata($path);
}
/**
- * {@inheritdoc}
+ * @deprecated Use {@see self::mimeType() }
+ * @codeCoverageIgnore
+ *
+ * @return array{
+ * type: string
+ * dirname: string
+ * path: string
+ * timestamp: int
+ * mimetype: string
+ * size: int
+ * }
*/
- public function getMimetype($path)
+ public function getMimetype(string $path): array
{
+ @trigger_error(sprintf('Method "%s:getMimetype()" id deprecated. Use "%1$s:mimeType()"', __CLASS__), \E_USER_DEPRECATED);
+
return $this->getMetadata($path);
}
/**
- * {@inheritdoc}
+ * @deprecated Use {@see self::lastModified() }
+ * @codeCoverageIgnore
+ *
+ * @return array{
+ * type: string
+ * dirname: string
+ * path: string
+ * timestamp: int
+ * mimetype: string
+ * size: int
+ * }
*/
- public function getTimestamp($path)
+ public function getTimestamp(string $path): array
{
+ @trigger_error(sprintf('Method "%s:getTimestamp()" id deprecated. Use "%1$s:lastModified()"', __CLASS__), \E_USER_DEPRECATED);
+
return $this->getMetadata($path);
}
/**
- * {@inheritdoc}
+ * @deprecated Use {@see self::lastModified() }
+ * @codeCoverageIgnore
+ *
+ * @return array { visibility: string }
*/
- public function getVisibility($path)
+ public function getVisibility(string $path): array
{
+ @trigger_error(sprintf('Method "%s:getVisibility()" id deprecated. Use "%1$s:visibility()"', __CLASS__), \E_USER_DEPRECATED);
+
return [
'visibility' => $this->getRawVisibility($path),
];
@@ -416,13 +534,9 @@ public function getVisibility($path)
/**
* Return a public url to a file.
*
- * Note: The file must have `AdapterInterface::VISIBILITY_PUBLIC` visibility.
- *
- * @param string $path
- *
- * @return string
+ * Note: The file must have `Visibility::PUBLIC` visibility.
*/
- public function getUrl($path)
+ public function getUrl(string $path): string
{
$uri = rtrim($this->storageApiUri, '/');
$path = $this->applyPathPrefix($path);
@@ -444,7 +558,7 @@ public function getUrl($path)
/**
* Get a temporary URL (Signed) for the file at the given path.
- * @param string $path
+ *
* @param \DateTimeInterface|int $expiration Specifies when the URL
* will expire. May provide an instance of [http://php.net/datetimeimmutable](`\DateTimeImmutable`),
* or a UNIX timestamp as an integer.
@@ -482,15 +596,14 @@ public function getUrl($path)
* @type bool $forceOpenssl If true, OpenSSL will be used regardless of
* whether phpseclib is available. **Defaults to** `false`.
* }
- * @return string
*/
- public function getTemporaryUrl($path, $expiration, $options = [])
+ public function getTemporaryUrl(string $path, $expiration, array $options = []): string
{
$object = $this->getObject($path);
$signedUrl = $object->signedUrl($expiration, $options);
if ($this->getStorageApiUri() !== self::STORAGE_API_URI_DEFAULT) {
- list($url, $params) = explode('?', $signedUrl, 2);
+ [, $params] = explode('?', $signedUrl, 2);
$signedUrl = $this->getUrl($path) . '?' . $params;
}
@@ -498,43 +611,149 @@ public function getTemporaryUrl($path, $expiration, $options = [])
}
/**
- * @param string $path
- *
- * @return string
+ * The method grabbed from class \League\Flysystem\Util of league/flysystem:dev-1.0.x.
+ */
+ protected function basename(string $path): string
+ {
+ $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/';
+
+ $path = rtrim($path, $separators);
+
+ $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path);
+
+ if (DIRECTORY_SEPARATOR === '/') {
+ return $basename;
+ }
+ // @codeCoverageIgnoreStart
+ // Extra Windows path munging. This is tested via AppVeyor, but code
+ // coverage is not reported.
+
+ // Handle relative paths with drive letters. c:file.txt.
+ while (preg_match('#^[a-zA-Z]:[^\\\/]#', $basename)) {
+ $basename = substr($basename, 2);
+ }
+
+ // Remove colon for standalone drive letter names.
+ if (preg_match('#^[a-zA-Z]:$#', $basename)) {
+ $basename = rtrim($basename, ':');
+ }
+
+ return $basename;
+ // @codeCoverageIgnoreEnd
+ }
+
+ /**
+ * The method grabbed from class \League\Flysystem\Util of league/flysystem:dev-1.0.x.
+ */
+ protected function dirname(string $path): string
+ {
+ return $this->normalizeDotName(dirname($path));
+ }
+
+ /**
+ * The method grabbed from class \League\Flysystem\Util of league/flysystem:dev-1.0.x.
*/
- protected function getRawVisibility($path)
+ protected function emulateDirectories(array $listing): array
+ {
+ $directories = [];
+ $listedDirectories = [];
+
+ foreach ($listing as $object) {
+ [$directories, $listedDirectories] = $this->emulateObjectDirectories($object, $directories, $listedDirectories);
+ }
+
+ $directories = array_diff(array_unique($directories), array_unique($listedDirectories));
+
+ foreach ($directories as $directory) {
+ $listing[] = $this->getPathInfo($directory) + ['type' => 'dir'];
+ }
+
+ return $listing;
+ }
+
+ /**
+ * The method grabbed from class \League\Flysystem\Util of league/flysystem:dev-1.0.x.
+ */
+ protected function emulateObjectDirectories(array $object, array $directories, array $listedDirectories): array
+ {
+ if ($object['type'] === 'dir') {
+ $listedDirectories[] = $object['path'];
+ }
+
+ if (!isset($object['dirname']) || trim($object['dirname']) === '') {
+ return [$directories, $listedDirectories];
+ }
+
+ $parent = $object['dirname'];
+
+ while ($parent && trim($parent) !== '' && !\in_array($parent, $directories, true)) {
+ $directories[] = $parent;
+ $parent = $this->dirname($parent);
+ }
+
+ if (isset($object['type']) && $object['type'] === 'dir') {
+ $listedDirectories[] = $object['path'];
+
+ return [$directories, $listedDirectories];
+ }
+
+ return [$directories, $listedDirectories];
+ }
+
+ protected function getRawVisibility(string $path): string
{
try {
$acl = $this->getObject($path)->acl()->get(['entity' => 'allUsers']);
- return $acl['role'] === Acl::ROLE_READER ?
- AdapterInterface::VISIBILITY_PUBLIC :
- AdapterInterface::VISIBILITY_PRIVATE;
+
+ return $acl['role'] === Acl::ROLE_READER ? Visibility::PUBLIC : Visibility::PRIVATE;
} catch (NotFoundException $e) {
// object may not have an acl entry, so handle that gracefully
- return AdapterInterface::VISIBILITY_PRIVATE;
+ return Visibility::PRIVATE;
}
}
/**
* Returns a storage object for the given path.
- *
- * @param string $path
- *
- * @return \Google\Cloud\Storage\StorageObject
*/
- protected function getObject($path)
+ protected function getObject(string $path): StorageObject
{
$path = $this->applyPathPrefix($path);
+
return $this->bucket->object($path);
}
+ protected function getPredefinedAclForVisibility(string $visibility): string
+ {
+ return $visibility === Visibility::PUBLIC ? 'publicRead' : 'projectPrivate';
+ }
+
/**
- * @param string $visibility
- *
- * @return string
+ * The method grabbed from class \League\Flysystem\Util of league/flysystem:dev-1.0.x.
*/
- protected function getPredefinedAclForVisibility($visibility)
+ protected function getPathInfo(string $path): array
+ {
+ $pathinfo = compact('path');
+
+ if ('' !== $dirname = dirname($path)) {
+ $pathinfo['dirname'] = $this->normalizeDotName($dirname);
+ }
+
+ $pathinfo['basename'] = $this->basename($path);
+ $pathinfo += pathinfo($pathinfo['basename']);
+
+ return $pathinfo + ['dirname' => ''];
+ }
+
+ /**
+ * Returns a normalised directory name from the given path.
+ */
+ protected function normalizeDirPostfix(string $dirname): string
+ {
+ return rtrim($dirname, '/') . '/';
+ }
+
+ protected function normalizeDotName(string $dirname): string
{
- return $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'publicRead' : 'projectPrivate';
+ return $dirname === '.' ? '' : $dirname;
}
}
diff --git a/tests/GoogleStorageAdapterTests.php b/tests/GoogleStorageAdapterTests.php
index 450dc51..7c3f1c8 100755
--- a/tests/GoogleStorageAdapterTests.php
+++ b/tests/GoogleStorageAdapterTests.php
@@ -1,157 +1,165 @@
createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('exists')
+ ->willReturn(true);
+
+ $storageObject
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn('dir_name/');
+
+ $bucket
+ ->expects($this->once())
+ ->method('object')
+ ->with('prefix/dir_name')
+ ->willReturn($storageObject);
+
+ $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
+
+ self::assertTrue($adapter->directoryExists('dir_name'));
+ }
+
+ public function testGetStorageClient(): void
+ {
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket);
$this->assertSame($storageClient, $adapter->getStorageClient());
}
- public function testGetBucket()
+ public function testGetBucket(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket);
$this->assertSame($bucket, $adapter->getBucket());
}
- public function testWrite()
- {
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file1.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ /**
+ * @dataProvider getDataForTestWriteContent
+ */
+ public function testWriteContent(
+ array $expected,
+ string $contents,
+ string $predefinedAcl,
+ ?string $visibility
+ ): void {
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->exactly(2))
+ ->method('name')
+ ->willReturn('prefix/file1.txt');
+ $storageObject
+ ->expects($this->exactly(2))
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('upload')
- ->withArgs([
- 'This is the file contents.',
+ $bucket
+ ->expects($this->once())
+ ->method('upload')
+ ->with(
+ $contents,
[
'name' => 'prefix/file1.txt',
- 'predefinedAcl' => 'projectPrivate',
+ 'predefinedAcl' => $predefinedAcl,
],
- ])
- ->once()
- ->andReturn($storageObject);
+ )
+ ->willReturn($storageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $bucket
+ ->expects($this->once())
+ ->method('object')
+ ->with('prefix/file1.txt', [])
+ ->willReturn($storageObject);
+
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $data = $adapter->write('file1.txt', 'This is the file contents.', new Config());
+ $configOptions = [];
+ if ($visibility) {
+ $configOptions['visibility'] = $visibility;
+ }
- $expected = [
- 'type' => 'file',
- 'dirname' => '',
- 'path' => 'file1.txt',
- 'timestamp' => 1474901082,
- 'mimetype' => 'text/plain',
- 'size' => 5,
- ];
- $this->assertEquals($expected, $data);
+ $adapter->write('file1.txt', 'This is the file contents.', new Config($configOptions));
+
+ $this->assertEquals($expected, $adapter->getMetadata('file1.txt'));
}
- public function testWriteWithPrivateVisibility()
+ public function testWriteStream(): void
{
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file1.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $stream = tmpfile();
+
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->exactly(2))
+ ->method('name')
+ ->willReturn('prefix/file1.txt');
+ $storageObject
+ ->expects($this->exactly(2))
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('upload')
- ->withArgs([
- 'This is the file contents.',
+ $bucket->expects($this->once())
+ ->method('upload')
+ ->with(
+ $stream,
[
'name' => 'prefix/file1.txt',
'predefinedAcl' => 'projectPrivate',
],
- ])
- ->once()
- ->andReturn($storageObject);
+ )
+ ->willReturn($storageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $bucket
+ ->expects($this->once())
+ ->method('object')
+ ->with('prefix/file1.txt', [])
+ ->willReturn($storageObject);
- $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
-
- $data = $adapter->write('file1.txt', 'This is the file contents.', new Config(['visibility' => AdapterInterface::VISIBILITY_PRIVATE]));
-
- $expected = [
- 'type' => 'file',
- 'dirname' => '',
- 'path' => 'file1.txt',
- 'timestamp' => 1474901082,
- 'mimetype' => 'text/plain',
- 'size' => 5,
- ];
- $this->assertEquals($expected, $data);
- }
-
- public function testWriteWithPublicVisibility()
- {
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file1.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
- 'updated' => '2016-09-26T14:44:42+00:00',
- 'contentType' => 'text/plain',
- 'size' => 5,
- ]);
-
- $bucket->shouldReceive('upload')
- ->withArgs([
- 'This is the file contents.',
- [
- 'name' => 'prefix/file1.txt',
- 'predefinedAcl' => 'publicRead',
- ],
- ])
- ->once()
- ->andReturn($storageObject);
-
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $data = $adapter->write('file1.txt', 'This is the file contents.', new Config(['visibility' => AdapterInterface::VISIBILITY_PUBLIC]));
+ $adapter->writeStream('file1.txt', $stream, new Config());
+
+ fclose($stream);
$expected = [
'type' => 'file',
@@ -161,467 +169,456 @@ public function testWriteWithPublicVisibility()
'mimetype' => 'text/plain',
'size' => 5,
];
- $this->assertEquals($expected, $data);
+ $this->assertEquals($expected, $adapter->getMetadata('file1.txt'));
}
- public function testWriteStream()
+ public function testRename(): void
{
- $stream = tmpfile();
+ $bucket = $this->createMock(Bucket::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file1.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
- 'updated' => '2016-09-26T14:44:42+00:00',
- 'contentType' => 'text/plain',
- 'size' => 5,
+ $oldStorageObjectAcl = $this->createMock(Acl::class);
+ $oldStorageObjectAcl
+ ->expects($this->once())
+ ->method('get')
+ ->with(['entity' => 'allUsers'])
+ ->willReturn([
+ 'role' => Acl::ROLE_OWNER,
]);
- $bucket->shouldReceive('upload')
- ->withArgs([
- $stream,
+ $newStorageObject = $this->createMock(StorageObject::class);
+ $newStorageObject
+ ->method('exists')
+ ->willReturn(true);
+
+ $oldStorageObject = $this->createMock(StorageObject::class);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($oldStorageObjectAcl);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('copy')
+ ->with(
+ $bucket,
[
- 'name' => 'prefix/file1.txt',
+ 'name' => 'prefix/new_file.txt',
'predefinedAcl' => 'projectPrivate',
],
- ])
- ->once()
- ->andReturn($storageObject);
+ )
+ ->willReturn($newStorageObject);
+ $oldStorageObject
+ ->expects($this->exactly(0))
+ ->method('delete');
+
+ $bucket
+ ->expects($this->exactly(2))
+ ->method('object')
+ ->with('prefix/old_file.txt')
+ ->willReturn($oldStorageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $data = $adapter->writeStream('file1.txt', $stream, new Config());
-
- fclose($stream);
-
- $expected = [
- 'type' => 'file',
- 'dirname' => '',
- 'path' => 'file1.txt',
- 'timestamp' => 1474901082,
- 'mimetype' => 'text/plain',
- 'size' => 5,
- ];
- $this->assertEquals($expected, $data);
+ $adapter->move('old_file.txt', 'new_file.txt', new Config());
}
- public function testRename()
+ public function testDeleteOnRename(): void
{
- $bucket = Mockery::mock(Bucket::class);
+ $bucket = $this->createMock(Bucket::class);
- $oldStorageObjectAcl = Mockery::mock(Acl::class);
- $oldStorageObjectAcl->shouldReceive('get')
+ $oldStorageObjectAcl = $this->createMock(Acl::class);
+ $oldStorageObjectAcl
+ ->expects($this->once())
+ ->method('get')
->with(['entity' => 'allUsers'])
- ->once()
- ->andReturn([
+ ->willReturn([
'role' => Acl::ROLE_OWNER,
]);
- $oldStorageObject = Mockery::mock(StorageObject::class);
- $oldStorageObject->shouldReceive('acl')
- ->once()
- ->andReturn($oldStorageObjectAcl);
- $oldStorageObject->shouldReceive('copy')
- ->withArgs([
+ $newStorageObject = $this->createMock(StorageObject::class);
+ $newStorageObject
+ ->method('exists')
+ ->willReturn(false);
+
+ $oldStorageObject = $this->createMock(StorageObject::class);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($oldStorageObjectAcl);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('copy')
+ ->with(
$bucket,
[
'name' => 'prefix/new_file.txt',
'predefinedAcl' => 'projectPrivate',
],
- ])
- ->once();
- $oldStorageObject->shouldReceive('delete')
- ->once();
-
- $bucket->shouldReceive('object')
+ )
+ ->willReturn($newStorageObject);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('delete');
+
+ $bucket
+ ->expects($this->exactly(3))
+ ->method('object')
->with('prefix/old_file.txt')
- ->times(3)
- ->andReturn($oldStorageObject);
+ ->willReturn($oldStorageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $adapter->rename('old_file.txt', 'new_file.txt');
+ $adapter->move('old_file.txt', 'new_file.txt', new Config());
}
- public function testCopy()
+ public function testCopy(): void
{
- $bucket = Mockery::mock(Bucket::class);
+ $bucket = $this->createMock(Bucket::class);
- $oldStorageObjectAcl = Mockery::mock(Acl::class);
- $oldStorageObjectAcl->shouldReceive('get')
+ $oldStorageObjectAcl = $this->createMock(Acl::class);
+ $oldStorageObjectAcl
+ ->expects($this->once())
+ ->method('get')
->with(['entity' => 'allUsers'])
- ->once()
- ->andReturn([
+ ->willReturn([
'role' => Acl::ROLE_OWNER,
]);
- $oldStorageObject = Mockery::mock(StorageObject::class);
- $oldStorageObject->shouldReceive('acl')
- ->once()
- ->andReturn($oldStorageObjectAcl);
- $oldStorageObject->shouldReceive('copy')
- ->withArgs([
+ $newStorageObject = $this->createMock(StorageObject::class);
+ $newStorageObject
+ ->method('exists')
+ ->willReturn(true);
+
+ $oldStorageObject = $this->createMock(StorageObject::class);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($oldStorageObjectAcl);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('copy')
+ ->with(
$bucket,
[
'name' => 'prefix/new_file.txt',
'predefinedAcl' => 'projectPrivate',
],
- ])
- ->once();
+ )
+ ->willReturn($newStorageObject);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->exactly(2))
+ ->method('object')
->with('prefix/old_file.txt')
- ->times(2)
- ->andReturn($oldStorageObject);
+ ->willReturn($oldStorageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $adapter->copy('old_file.txt', 'new_file.txt');
+ $adapter->copy('old_file.txt', 'new_file.txt', new Config());
}
- public function testCopyWhenOriginalFileIsPublic()
+ public function testCopyWhenOriginalFileIsPublic(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
- $oldStorageObjectAcl = Mockery::mock(Acl::class);
- $oldStorageObjectAcl->shouldReceive('get')
+ $oldStorageObjectAcl = $this->createMock(Acl::class);
+ $oldStorageObjectAcl
+ ->expects($this->once())
+ ->method('get')
->with(['entity' => 'allUsers'])
- ->once()
- ->andReturn([
+ ->willReturn([
'role' => Acl::ROLE_READER,
]);
- $oldStorageObject = Mockery::mock(StorageObject::class);
- $oldStorageObject->shouldReceive('acl')
- ->once()
- ->andReturn($oldStorageObjectAcl);
- $oldStorageObject->shouldReceive('copy')
- ->withArgs([
+ $newStorageObject = $this->createMock(StorageObject::class);
+ $newStorageObject
+ ->method('exists')
+ ->willReturn(true);
+
+ $oldStorageObject = $this->createMock(StorageObject::class);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($oldStorageObjectAcl);
+ $oldStorageObject
+ ->expects($this->once())
+ ->method('copy')
+ ->with(
$bucket,
[
'name' => 'prefix/new_file.txt',
'predefinedAcl' => 'publicRead',
],
- ])
- ->once();
+ )
+ ->willReturn($newStorageObject);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->exactly(2))
+ ->method('object')
->with('prefix/old_file.txt')
- ->times(2)
- ->andReturn($oldStorageObject);
+ ->willReturn($oldStorageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $adapter->copy('old_file.txt', 'new_file.txt');
+ $adapter->copy('old_file.txt', 'new_file.txt', new Config());
}
- public function testDelete()
+ public function testDelete(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('delete')
- ->once();
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('delete');
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
$adapter->delete('file.txt');
}
- public function testDeleteDir()
- {
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('delete')
- ->times(3);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/dir_name/directory1/file1.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
- 'updated' => '2016-09-26T14:44:42+00:00',
- 'contentType' => 'text/plain',
- 'size' => 5,
- ]);
-
- $bucket->shouldReceive('object')
- ->with('prefix/dir_name/directory1/file1.txt')
- ->once()
- ->andReturn($storageObject);
-
- $bucket->shouldReceive('object')
- ->with('prefix/dir_name/directory1/')
- ->once()
- ->andReturn($storageObject);
-
- $bucket->shouldReceive('object')
- ->with('prefix/dir_name/')
- ->once()
- ->andReturn($storageObject);
-
- $bucket->shouldReceive('objects')
- ->with([
- 'prefix' => 'prefix/dir_name/'
- ])->once()
- ->andReturn([$storageObject]);
-
- $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
-
- $adapter->deleteDir('dir_name');
- }
-
- public function testDeleteDirWithTrailingSlash()
+ /**
+ * @dataProvider getDataForTestDeleteDirectory
+ */
+ public function testDeleteDirectory(string $path): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('delete')
- ->times(3);
-
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/dir_name/directory1/file1.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->exactly(3))
+ ->method('delete');
+ $storageObject
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn('prefix/dir_name/directory1/file1.txt');
+ $storageObject
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('object')
- ->with('prefix/dir_name/directory1/file1.txt')
- ->once()
- ->andReturn($storageObject);
-
- $bucket->shouldReceive('object')
- ->with('prefix/dir_name/directory1/')
- ->once()
- ->andReturn($storageObject);
+ $bucket
+ ->expects($this->exactly(3))
+ ->method('object')
+ ->withConsecutive(['prefix/dir_name/directory1/file1.txt', []], ['prefix/dir_name/directory1/', []], ['prefix/dir_name/', []])
+ ->willReturnOnConsecutiveCalls($storageObject, $storageObject, $storageObject);
- $bucket->shouldReceive('object')
- ->with('prefix/dir_name/')
- ->once()
- ->andReturn($storageObject);
-
- $bucket->shouldReceive('objects')
+ $bucket
+ ->expects($this->once())
+ ->method('objects')
->with([
- 'prefix' => 'prefix/dir_name/'
- ])->once()
- ->andReturn([$storageObject]);
+ 'prefix' => 'prefix/dir_name/',
+ ])
+ ->willReturn([$storageObject]);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $adapter->deleteDir('dir_name//');
+ $adapter->deleteDirectory($path);
}
- public function testSetVisibilityPrivate()
+ public function testSetVisibilityPrivate(): void
{
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObjectAcl = Mockery::mock(Acl::class);
- $storageObjectAcl->shouldReceive('delete')
- ->with('allUsers')
- ->once();
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('acl')
- ->once()
- ->andReturn($storageObjectAcl);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObjectAcl = $this->createMock(Acl::class);
+ $storageObjectAcl
+ ->expects($this->once())
+ ->method('delete')
+ ->with('allUsers');
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($storageObjectAcl);
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('name')
+ ->willReturn('prefix/file.txt');
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file1.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $data = $adapter->setVisibility('file1.txt', AdapterInterface::VISIBILITY_PRIVATE);
- $this->assertArrayHasKey('visibility', $data);
- $this->assertEquals(AdapterInterface::VISIBILITY_PRIVATE, $data['visibility']);
+ $adapter->setVisibility('file1.txt', Visibility::PRIVATE);
}
- public function testSetVisibilityPublic()
+ public function testSetVisibilityPublic(): void
{
- $bucket = Mockery::mock(Bucket::class);
+ $bucket = $this->createMock(Bucket::class);
- $storageObjectAcl = Mockery::mock(Acl::class);
- $storageObjectAcl->shouldReceive('add')
- ->withArgs([
+ $storageObjectAcl = $this->createMock(Acl::class);
+ $storageObjectAcl
+ ->expects($this->once())
+ ->method('add')
+ ->with(
'allUsers',
Acl::ROLE_READER,
- ])
- ->once();
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('acl')
- ->once()
- ->andReturn($storageObjectAcl);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
- 'updated' => '2016-09-26T14:44:42+00:00',
- 'contentType' => 'text/plain',
- 'size' => 5,
- ]);
-
- $bucket->shouldReceive('object')
+ );
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($storageObjectAcl);
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('name');
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('info');
+
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file1.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $data = $adapter->setVisibility('file1.txt', AdapterInterface::VISIBILITY_PUBLIC);
- $this->assertArrayHasKey('visibility', $data);
- $this->assertEquals(AdapterInterface::VISIBILITY_PUBLIC, $data['visibility']);
+ $adapter->setVisibility('file1.txt', Visibility::PUBLIC);
}
- public function testHas()
+ public function testFileExists(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('exists')
- ->once();
-
- $bucket->shouldReceive('object')
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('exists')
+ ->willReturn(true);
+
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $adapter->has('file.txt');
+ self::assertTrue($adapter->fileExists('file.txt'));
}
- public function testRead()
+ public function testRead(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('downloadAsString')
- ->once()
- ->andReturn('This is the file contents.');
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
- 'updated' => '2016-09-26T14:44:42+00:00',
- 'contentType' => 'text/plain',
- 'size' => 5,
- ]);
-
- $bucket->shouldReceive('object')
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('downloadAsString')
+ ->willReturn('This is the file contents.');
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('name');
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('info');
+
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
$data = $adapter->read('file.txt');
- $this->assertArrayHasKey('contents', $data);
- $this->assertEquals('This is the file contents.', $data['contents']);
+ $this->assertEquals('This is the file contents.', $data);
}
- public function testReadStream()
+ public function testReadStream(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $stream = Mockery::mock(StreamInterface::class);
- $stream->shouldReceive('isReadable')
- ->once()
- ->andReturn(true);
- $stream->shouldReceive('isWritable')
- ->once()
- ->andReturn(false);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('downloadAsStream')
- ->once()
- ->andReturn($stream);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
- 'updated' => '2016-09-26T14:44:42+00:00',
- 'contentType' => 'text/plain',
- 'size' => 5,
- ]);
-
- $bucket->shouldReceive('object')
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $stream = $this->createMock(StreamInterface::class);
+ $stream
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $stream
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(false);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('downloadAsStream')
+ ->willReturn($stream);
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('name');
+ $storageObject
+ ->expects($this->exactly(0))
+ ->method('info');
+
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
$data = $adapter->readStream('file.txt');
- $this->assertArrayHasKey('stream', $data);
- $this->assertInternalType('resource', $data['stream']);
+ $this->assertIsResource($data);
}
- public function testListContents()
+ public function testListContents(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
$prefix = 'prefix/';
- $bucket->shouldReceive('objects')
- ->once()
+ $bucket
+ ->expects($this->once())
+ ->method('objects')
->with([
'prefix' => $prefix,
])
- ->andReturn($this->getMockDirObjects($prefix));
+ ->willReturn($this->getMockDirObjects($prefix));
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
@@ -665,43 +662,47 @@ public function testListContents()
}
/**
- * @param string $prefix
- *
- * @return array
+ * @return StorageObject[]
*/
- protected function getMockDirObjects($prefix = '')
+ protected function getMockDirObjects(string $prefix): array
{
- $dir1 = Mockery::mock(StorageObject::class);
- $dir1->shouldReceive('name')
- ->once()
- ->andReturn($prefix . 'directory1/');
- $dir1->shouldReceive('info')
- ->once()
- ->andReturn([
+ $dir1 = $this->createMock(StorageObject::class);
+ $dir1
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn($prefix . 'directory1/');
+ $dir1
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'application/octet-stream',
'size' => 0,
]);
- $dir1file1 = Mockery::mock(StorageObject::class);
- $dir1file1->shouldReceive('name')
- ->once()
- ->andReturn($prefix . 'directory1/file1.txt');
- $dir1file1->shouldReceive('info')
- ->once()
- ->andReturn([
+ $dir1file1 = $this->createMock(StorageObject::class);
+ $dir1file1
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn($prefix . 'directory1/file1.txt');
+ $dir1file1
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $dir2file1 = Mockery::mock(StorageObject::class);
- $dir2file1->shouldReceive('name')
- ->once()
- ->andReturn($prefix . 'directory2/file1.txt');
- $dir2file1->shouldReceive('info')
- ->once()
- ->andReturn([
+ $dir2file1 = $this->createMock(StorageObject::class);
+ $dir2file1
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn($prefix . 'directory2/file1.txt');
+ $dir2file1
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
@@ -714,27 +715,30 @@ protected function getMockDirObjects($prefix = '')
];
}
- public function testGetMetadataForFile()
+ public function testGetMetadataForFile(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn('prefix/file.txt');
+ $storageObject
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
@@ -752,27 +756,30 @@ public function testGetMetadataForFile()
$this->assertEquals($expected, $metadata);
}
- public function testGetMetadataForDir()
+ public function testGetMetadataForDir(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/directory/');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn('prefix/directory/');
+ $storageObject
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'application/octet-stream',
'size' => 0,
]);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/directory')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
@@ -790,27 +797,30 @@ public function testGetMetadataForDir()
$this->assertEquals($expected, $metadata);
}
- public function testGetSize()
+ public function testGetSize(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn('prefix/file.txt');
+ $storageObject
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
@@ -820,27 +830,30 @@ public function testGetSize()
$this->assertEquals(5, $metadata['size']);
}
- public function testGetMimetype()
+ public function testGetMimetype(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn('prefix/file.txt');
+ $storageObject
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
@@ -850,27 +863,30 @@ public function testGetMimetype()
$this->assertEquals('text/plain', $metadata['mimetype']);
}
- public function testGetTimestamp()
+ public function testGetTimestamp(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
-
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('name')
- ->once()
- ->andReturn('prefix/file.txt');
- $storageObject->shouldReceive('info')
- ->once()
- ->andReturn([
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
+
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('name')
+ ->willReturn('prefix/file.txt');
+ $storageObject
+ ->expects($this->once())
+ ->method('info')
+ ->willReturn([
'updated' => '2016-09-26T14:44:42+00:00',
'contentType' => 'text/plain',
'size' => 5,
]);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
@@ -880,70 +896,76 @@ public function testGetTimestamp()
$this->assertEquals(1474901082, $metadata['timestamp']);
}
- public function testGetVisibilityWhenVisibilityIsPrivate()
+ public function testGetVisibilityWhenVisibilityIsPrivate(): void
{
- $bucket = Mockery::mock(Bucket::class);
+ $bucket = $this->createMock(Bucket::class);
- $storageObjectAcl = Mockery::mock(Acl::class);
- $storageObjectAcl->shouldReceive('get')
+ $storageObjectAcl = $this->createMock(Acl::class);
+ $storageObjectAcl
+ ->expects($this->once())
+ ->method('get')
->with(['entity' => 'allUsers'])
- ->once()
- ->andReturn([
+ ->willReturn([
'role' => Acl::ROLE_OWNER,
]);
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('acl')
- ->once()
- ->andReturn($storageObjectAcl);
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($storageObjectAcl);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $visibility = $adapter->getVisibility('file.txt');
- $this->assertEquals(['visibility' => AdapterInterface::VISIBILITY_PRIVATE], $visibility);
+ $attributes = $adapter->visibility('file.txt');
+ $this->assertEquals(Visibility::PRIVATE, $attributes->visibility());
}
- public function testGetVisibilityWhenVisibilityIsPublic()
+ public function testGetVisibilityWhenVisibilityIsPublic(): void
{
- $bucket = Mockery::mock(Bucket::class);
+ $bucket = $this->createMock(Bucket::class);
- $storageObjectAcl = Mockery::mock(Acl::class);
- $storageObjectAcl->shouldReceive('get')
+ $storageObjectAcl = $this->createMock(Acl::class);
+ $storageObjectAcl
+ ->expects($this->once())
+ ->method('get')
->with(['entity' => 'allUsers'])
- ->once()
- ->andReturn([
+ ->willReturn([
'role' => Acl::ROLE_READER,
]);
- $storageObject = Mockery::mock(StorageObject::class);
- $storageObject->shouldReceive('acl')
- ->once()
- ->andReturn($storageObjectAcl);
+ $storageObject = $this->createMock(StorageObject::class);
+ $storageObject
+ ->expects($this->once())
+ ->method('acl')
+ ->willReturn($storageObjectAcl);
- $bucket->shouldReceive('object')
+ $bucket
+ ->expects($this->once())
+ ->method('object')
->with('prefix/file.txt')
- ->once()
- ->andReturn($storageObject);
+ ->willReturn($storageObject);
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix');
- $visibility = $adapter->getVisibility('file.txt');
- $this->assertEquals(['visibility' => AdapterInterface::VISIBILITY_PUBLIC], $visibility);
+ $attributes = $adapter->visibility('file.txt');
+ $this->assertEquals(Visibility::PUBLIC, $attributes->visibility());
}
- public function testSetGetStorageApiUri()
+ public function testSetGetStorageApiUri(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
+ $storageClient = $this->createMock(StorageClient::class);
+ $bucket = $this->createMock(Bucket::class);
$adapter = new GoogleStorageAdapter($storageClient, $bucket);
$this->assertEquals('https://storage.googleapis.com', $adapter->getStorageApiUri());
@@ -955,13 +977,15 @@ public function testSetGetStorageApiUri()
$this->assertEquals('http://this.is.my.base.com', $adapter->getStorageApiUri());
}
- public function testGetUrl()
+ public function testGetUrl(): void
{
- $storageClient = Mockery::mock(StorageClient::class);
+ $storageClient = $this->createMock(StorageClient::class);
- $bucket = Mockery::mock(Bucket::class);
- $bucket->shouldReceive('name')
- ->andReturn('my-bucket');
+ $bucket = $this->createMock(Bucket::class);
+ $bucket
+ ->expects($this->exactly(3))
+ ->method('name')
+ ->willReturn('my-bucket');
$adapter = new GoogleStorageAdapter($storageClient, $bucket);
$this->assertEquals('https://storage.googleapis.com/my-bucket/file.txt', $adapter->getUrl('file.txt'));
@@ -975,4 +999,27 @@ public function testGetUrl()
// no bucket name on custom domain
$this->assertEquals('http://my-domain.com/another-prefix/dir/file.txt', $adapter->getUrl('dir/file.txt'));
}
+
+ public function getDataForTestDeleteDirectory(): iterable
+ {
+ yield ['dir_name'];
+ yield ['dir_name//'];
+ }
+
+ public function getDataForTestWriteContent(): iterable
+ {
+ $contents = 'This is the file contents.';
+ $expected = [
+ 'type' => 'file',
+ 'dirname' => '',
+ 'path' => 'file1.txt',
+ 'timestamp' => 1474901082,
+ 'mimetype' => 'text/plain',
+ 'size' => 5,
+ ];
+
+ yield [$expected, $contents, 'projectPrivate', null];
+ yield [$expected, $contents, 'projectPrivate', Visibility::PRIVATE];
+ yield [$expected, $contents, 'publicRead', Visibility::PUBLIC];
+ }
}