Extend storage location adapters into separate classes.
This commit is contained in:
parent
9b9a19aa7d
commit
6f6d5ab692
|
@ -4,6 +4,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entity\Enums;
|
||||
|
||||
use App\Entity\StorageLocationAdapter\DropboxStorageLocationAdapter;
|
||||
use App\Entity\StorageLocationAdapter\LocalStorageLocationAdapter;
|
||||
use App\Entity\StorageLocationAdapter\S3StorageLocationAdapter;
|
||||
use App\Entity\StorageLocationAdapter\SftpStorageLocationAdapter;
|
||||
use App\Entity\StorageLocationAdapter\StorageLocationAdapterInterface;
|
||||
|
||||
enum StorageLocationAdapters: string
|
||||
{
|
||||
case Local = 'local';
|
||||
|
@ -25,4 +31,17 @@ enum StorageLocationAdapters: string
|
|||
self::Sftp => 'SFTP',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<StorageLocationAdapterInterface>
|
||||
*/
|
||||
public function getAdapterClass(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Local => LocalStorageLocationAdapter::class,
|
||||
self::S3 => S3StorageLocationAdapter::class,
|
||||
self::Dropbox => DropboxStorageLocationAdapter::class,
|
||||
self::Sftp => SftpStorageLocationAdapter::class
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,29 +7,16 @@ namespace App\Entity;
|
|||
use App\Entity\Enums\StorageLocationAdapters;
|
||||
use App\Entity\Enums\StorageLocationTypes;
|
||||
use App\Entity\Interfaces\IdentifiableEntityInterface;
|
||||
use App\Entity\StorageLocationAdapter\StorageLocationAdapterInterface;
|
||||
use App\Exception\StorageLocationFullException;
|
||||
use App\Radio\Quota;
|
||||
use App\Validator\Constraints as AppAssert;
|
||||
use Aws\S3\S3Client;
|
||||
use Azura\Files\Adapter\AwsS3\AwsS3Adapter;
|
||||
use Azura\Files\Adapter\Dropbox\DropboxAdapter;
|
||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
||||
use Azura\Files\Adapter\Local\LocalFilesystemAdapter;
|
||||
use Azura\Files\Adapter\LocalAdapterInterface;
|
||||
use Azura\Files\Adapter\Sftp\SftpAdapter;
|
||||
use Azura\Files\ExtendedFilesystemInterface;
|
||||
use Azura\Files\LocalFilesystem;
|
||||
use Azura\Files\RemoteFilesystem;
|
||||
use Brick\Math\BigInteger;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use League\Flysystem\PhpseclibV3\SftpConnectionProvider;
|
||||
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
|
||||
use League\Flysystem\Visibility;
|
||||
use RuntimeException;
|
||||
use Spatie\Dropbox\Client;
|
||||
use Stringable;
|
||||
|
||||
#[
|
||||
|
@ -152,23 +139,6 @@ class StorageLocation implements Stringable, IdentifiableEntityInterface
|
|||
return $this->path;
|
||||
}
|
||||
|
||||
public function getFilteredPath(): string
|
||||
{
|
||||
return match ($this->getAdapterEnum()) {
|
||||
StorageLocationAdapters::S3, StorageLocationAdapters::Dropbox => trim($this->path, '/'),
|
||||
default => rtrim($this->path, '/')
|
||||
};
|
||||
}
|
||||
|
||||
public function applyPath(?string $suffix = null): string
|
||||
{
|
||||
$suffix = (null !== $suffix)
|
||||
? '/' . ltrim($suffix, '/')
|
||||
: '';
|
||||
|
||||
return $this->path . $suffix;
|
||||
}
|
||||
|
||||
public function setPath(string $path): void
|
||||
{
|
||||
$this->path = $this->truncateString($path);
|
||||
|
@ -496,137 +466,35 @@ class StorageLocation implements Stringable, IdentifiableEntityInterface
|
|||
return $this->media;
|
||||
}
|
||||
|
||||
public function getUri(?string $suffix = null): string
|
||||
public function getStorageLocationAdapter(): StorageLocationAdapterInterface
|
||||
{
|
||||
$path = $this->applyPath($suffix);
|
||||
|
||||
return match ($this->getAdapterEnum()) {
|
||||
StorageLocationAdapters::S3 => $this->getS3ObjectUri($suffix),
|
||||
StorageLocationAdapters::Dropbox => 'dropbox://' . $this->dropboxAuthToken . ltrim($path, '/'),
|
||||
default => $path,
|
||||
};
|
||||
$adapterClass = $this->getAdapterEnum()->getAdapterClass();
|
||||
return new $adapterClass($this);
|
||||
}
|
||||
|
||||
protected function getS3ObjectUri(?string $suffix = null): string
|
||||
public function getUri(?string $suffix = null): string
|
||||
{
|
||||
$path = $this->applyPath($suffix);
|
||||
return $this->getStorageLocationAdapter()->getUri($suffix);
|
||||
}
|
||||
|
||||
$bucket = $this->s3Bucket;
|
||||
if (null === $bucket) {
|
||||
return 'No S3 Bucket Specified';
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->getS3Client();
|
||||
if (empty($path)) {
|
||||
$objectUrl = $client->getObjectUrl($bucket, '/');
|
||||
return rtrim($objectUrl, '/');
|
||||
}
|
||||
|
||||
return $client->getObjectUrl($bucket, ltrim($path, '/'));
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return 'Invalid URI (' . $e->getMessage() . ')';
|
||||
}
|
||||
public function getFilteredPath(): string
|
||||
{
|
||||
return $this->getStorageLocationAdapter()::filterPath($this->path);
|
||||
}
|
||||
|
||||
public function validate(): void
|
||||
{
|
||||
if (StorageLocationAdapters::S3 === $this->getAdapterEnum()) {
|
||||
$client = $this->getS3Client();
|
||||
$client->listObjectsV2(
|
||||
[
|
||||
'Bucket' => $this->s3Bucket,
|
||||
'max-keys' => 1,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$adapter = $this->getStorageAdapter();
|
||||
$adapter->fileExists('/test');
|
||||
$this->getStorageLocationAdapter()->validate();
|
||||
}
|
||||
|
||||
public function getStorageAdapter(): ExtendedAdapterInterface
|
||||
{
|
||||
$filteredPath = $this->getFilteredPath();
|
||||
|
||||
switch ($this->getAdapterEnum()) {
|
||||
case StorageLocationAdapters::S3:
|
||||
$bucket = $this->s3Bucket;
|
||||
if (null === $bucket) {
|
||||
throw new RuntimeException('Amazon S3 bucket is empty.');
|
||||
}
|
||||
return new AwsS3Adapter($this->getS3Client(), $bucket, $filteredPath);
|
||||
|
||||
case StorageLocationAdapters::Dropbox:
|
||||
return new DropboxAdapter($this->getDropboxClient(), $filteredPath);
|
||||
|
||||
case StorageLocationAdapters::Sftp:
|
||||
return new SftpAdapter($this->getSftpConnectionProvider(), $filteredPath);
|
||||
|
||||
default:
|
||||
return new LocalFilesystemAdapter($filteredPath);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getS3Client(): S3Client
|
||||
{
|
||||
if (StorageLocationAdapters::S3 !== $this->getAdapterEnum()) {
|
||||
throw new InvalidArgumentException('This storage location is not using the S3 adapter.');
|
||||
}
|
||||
|
||||
$s3Options = array_filter(
|
||||
[
|
||||
'credentials' => [
|
||||
'key' => $this->s3CredentialKey,
|
||||
'secret' => $this->s3CredentialSecret,
|
||||
],
|
||||
'region' => $this->s3Region,
|
||||
'version' => $this->s3Version,
|
||||
'endpoint' => $this->s3Endpoint,
|
||||
]
|
||||
);
|
||||
return new S3Client($s3Options);
|
||||
}
|
||||
|
||||
protected function getDropboxClient(): Client
|
||||
{
|
||||
if (StorageLocationAdapters::Dropbox !== $this->getAdapterEnum()) {
|
||||
throw new InvalidArgumentException('This storage location is not using the Dropbox adapter.');
|
||||
}
|
||||
|
||||
$creds = (!empty($this->dropboxAppKey) && !empty($this->dropboxAppSecret))
|
||||
? [$this->dropboxAppKey, $this->dropboxAppSecret]
|
||||
: $this->dropboxAuthToken;
|
||||
|
||||
return new Client($creds);
|
||||
}
|
||||
|
||||
protected function getSftpConnectionProvider(): SftpConnectionProvider
|
||||
{
|
||||
if (StorageLocationAdapters::Sftp !== $this->getAdapterEnum()) {
|
||||
throw new InvalidArgumentException('This storage location is not using the SFTP adapter.');
|
||||
}
|
||||
|
||||
return new SftpConnectionProvider(
|
||||
$this->sftpHost ?? '',
|
||||
$this->sftpUsername ?? '',
|
||||
$this->sftpPassword,
|
||||
$this->sftpPrivateKey,
|
||||
$this->sftpPrivateKeyPassPhrase,
|
||||
$this->sftpPort ?? 22
|
||||
);
|
||||
return $this->getStorageLocationAdapter()->getStorageAdapter();
|
||||
}
|
||||
|
||||
public function getFilesystem(): ExtendedFilesystemInterface
|
||||
{
|
||||
$adapter = $this->getStorageAdapter();
|
||||
|
||||
return ($adapter instanceof LocalAdapterInterface)
|
||||
? new LocalFilesystem(
|
||||
adapter: $adapter,
|
||||
visibilityConverter: new PortableVisibilityConverter(defaultForDirectories: Visibility::PUBLIC)
|
||||
)
|
||||
: new RemoteFilesystem($adapter);
|
||||
return $this->getStorageLocationAdapter()->getFilesystem();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\StorageLocationAdapter;
|
||||
|
||||
use App\Entity\StorageLocation;
|
||||
use Azura\Files\ExtendedFilesystemInterface;
|
||||
use Azura\Files\RemoteFilesystem;
|
||||
|
||||
abstract class AbstractStorageLocationLocationAdapter implements StorageLocationAdapterInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected readonly StorageLocation $storageLocation
|
||||
) {
|
||||
if ($this->getType() !== $storageLocation->getAdapterEnum()) {
|
||||
throw new \InvalidArgumentException('This storage location is not using the specified adapter.');
|
||||
}
|
||||
}
|
||||
|
||||
public static function filterPath(string $path): string
|
||||
{
|
||||
return rtrim($path, '/');
|
||||
}
|
||||
|
||||
public function getUri(?string $suffix = null): string
|
||||
{
|
||||
return $this->applyPath($suffix);
|
||||
}
|
||||
|
||||
protected function applyPath(?string $suffix = null): string
|
||||
{
|
||||
$suffix = (null !== $suffix)
|
||||
? '/' . ltrim($suffix, '/')
|
||||
: '';
|
||||
|
||||
return $this->storageLocation->getPath() . $suffix;
|
||||
}
|
||||
|
||||
public function getFilesystem(): ExtendedFilesystemInterface
|
||||
{
|
||||
return new RemoteFilesystem($this->getStorageAdapter());
|
||||
}
|
||||
|
||||
public function validate(): void
|
||||
{
|
||||
$adapter = $this->getStorageAdapter();
|
||||
$adapter->fileExists('/test');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\StorageLocationAdapter;
|
||||
|
||||
use App\Entity\Enums\StorageLocationAdapters;
|
||||
use Azura\Files\Adapter\Dropbox\DropboxAdapter;
|
||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
||||
use Spatie\Dropbox\Client;
|
||||
|
||||
final class DropboxStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||
{
|
||||
public function getType(): StorageLocationAdapters
|
||||
{
|
||||
return StorageLocationAdapters::Dropbox;
|
||||
}
|
||||
|
||||
public static function filterPath(string $path): string
|
||||
{
|
||||
return trim($path, '/');
|
||||
}
|
||||
|
||||
public function getUri(?string $suffix = null): string
|
||||
{
|
||||
$path = $this->applyPath($suffix);
|
||||
$appKey = $this->storageLocation->getDropboxAppKey();
|
||||
|
||||
$uriPrefix = (!empty($appKey))
|
||||
? $appKey
|
||||
: $this->storageLocation->getDropboxAuthToken();
|
||||
|
||||
return 'dropbox://' . $uriPrefix . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
public function getStorageAdapter(): ExtendedAdapterInterface
|
||||
{
|
||||
$filteredPath = self::filterPath($this->storageLocation->getPath());
|
||||
|
||||
return new DropboxAdapter($this->getClient(), $filteredPath);
|
||||
}
|
||||
|
||||
private function getClient(): Client
|
||||
{
|
||||
$appKey = $this->storageLocation->getDropboxAppKey();
|
||||
$appSecret = $this->storageLocation->getDropboxAppSecret();
|
||||
$authToken = $this->storageLocation->getDropboxAuthToken();
|
||||
|
||||
$creds = (!empty($appKey) && !empty($appSecret))
|
||||
? [$appKey, $appSecret]
|
||||
: $authToken;
|
||||
|
||||
return new Client($creds);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\StorageLocationAdapter;
|
||||
|
||||
use App\Entity\Enums\StorageLocationAdapters;
|
||||
use Azura\Files\Adapter\Local\LocalFilesystemAdapter;
|
||||
use Azura\Files\Adapter\LocalAdapterInterface;
|
||||
use Azura\Files\ExtendedFilesystemInterface;
|
||||
use Azura\Files\LocalFilesystem;
|
||||
|
||||
final class LocalStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||
{
|
||||
public function getType(): StorageLocationAdapters
|
||||
{
|
||||
return StorageLocationAdapters::Local;
|
||||
}
|
||||
|
||||
public function getStorageAdapter(): LocalAdapterInterface
|
||||
{
|
||||
$filteredPath = self::filterPath($this->storageLocation->getPath());
|
||||
|
||||
return new LocalFilesystemAdapter($filteredPath);
|
||||
}
|
||||
|
||||
public function getFilesystem(): ExtendedFilesystemInterface
|
||||
{
|
||||
return new LocalFilesystem($this->getStorageAdapter());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\StorageLocationAdapter;
|
||||
|
||||
use App\Entity\Enums\StorageLocationAdapters;
|
||||
use Aws\S3\S3Client;
|
||||
use Azura\Files\Adapter\AwsS3\AwsS3Adapter;
|
||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class S3StorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||
{
|
||||
public function getType(): StorageLocationAdapters
|
||||
{
|
||||
return StorageLocationAdapters::S3;
|
||||
}
|
||||
|
||||
public static function filterPath(string $path): string
|
||||
{
|
||||
return trim($path, '/');
|
||||
}
|
||||
|
||||
public function getUri(?string $suffix = null): string
|
||||
{
|
||||
$path = $this->applyPath($suffix);
|
||||
|
||||
$bucket = $this->storageLocation->getS3Bucket();
|
||||
if (null === $bucket) {
|
||||
return 'No S3 Bucket Specified';
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->getClient();
|
||||
if (empty($path)) {
|
||||
$objectUrl = $client->getObjectUrl($bucket, '/');
|
||||
return rtrim($objectUrl, '/');
|
||||
}
|
||||
|
||||
return $client->getObjectUrl($bucket, ltrim($path, '/'));
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return 'Invalid URI (' . $e->getMessage() . ')';
|
||||
}
|
||||
}
|
||||
|
||||
public function getStorageAdapter(): ExtendedAdapterInterface
|
||||
{
|
||||
$filteredPath = self::filterPath($this->storageLocation->getPath());
|
||||
|
||||
$bucket = $this->storageLocation->getS3Bucket();
|
||||
if (null === $bucket) {
|
||||
throw new \RuntimeException('Amazon S3 bucket is empty.');
|
||||
}
|
||||
|
||||
return new AwsS3Adapter($this->getClient(), $bucket, $filteredPath);
|
||||
}
|
||||
|
||||
public function validate(): void
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$client->listObjectsV2(
|
||||
[
|
||||
'Bucket' => $this->storageLocation->getS3Bucket(),
|
||||
'max-keys' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
parent::validate();
|
||||
}
|
||||
|
||||
private function getClient(): S3Client
|
||||
{
|
||||
$s3Options = array_filter(
|
||||
[
|
||||
'credentials' => [
|
||||
'key' => $this->storageLocation->getS3CredentialKey(),
|
||||
'secret' => $this->storageLocation->getS3CredentialSecret(),
|
||||
],
|
||||
'region' => $this->storageLocation->getS3Region(),
|
||||
'version' => $this->storageLocation->getS3Version(),
|
||||
'endpoint' => $this->storageLocation->getS3Endpoint(),
|
||||
]
|
||||
);
|
||||
return new S3Client($s3Options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\StorageLocationAdapter;
|
||||
|
||||
use App\Entity\Enums\StorageLocationAdapters;
|
||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
||||
use Azura\Files\Adapter\Sftp\SftpAdapter;
|
||||
use League\Flysystem\PhpseclibV3\SftpConnectionProvider;
|
||||
|
||||
final class SftpStorageLocationAdapter extends AbstractStorageLocationLocationAdapter
|
||||
{
|
||||
public function getType(): StorageLocationAdapters
|
||||
{
|
||||
return StorageLocationAdapters::Sftp;
|
||||
}
|
||||
|
||||
public function getStorageAdapter(): ExtendedAdapterInterface
|
||||
{
|
||||
$filteredPath = self::filterPath($this->storageLocation->getPath());
|
||||
return new SftpAdapter($this->getSftpConnectionProvider(), $filteredPath);
|
||||
}
|
||||
|
||||
private function getSftpConnectionProvider(): SftpConnectionProvider
|
||||
{
|
||||
return new SftpConnectionProvider(
|
||||
$this->storageLocation->getSftpHost() ?? '',
|
||||
$this->storageLocation->getSftpUsername() ?? '',
|
||||
$this->storageLocation->getSftpPassword(),
|
||||
$this->storageLocation->getSftpPrivateKey(),
|
||||
$this->storageLocation->getSftpPrivateKeyPassPhrase(),
|
||||
$this->storageLocation->getSftpPort() ?? 22
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\StorageLocationAdapter;
|
||||
|
||||
use App\Entity\Enums\StorageLocationAdapters;
|
||||
use Azura\Files\Adapter\ExtendedAdapterInterface;
|
||||
use Azura\Files\ExtendedFilesystemInterface;
|
||||
|
||||
interface StorageLocationAdapterInterface
|
||||
{
|
||||
public function getType(): StorageLocationAdapters;
|
||||
|
||||
public function getUri(?string $suffix = null): string;
|
||||
|
||||
public function getStorageAdapter(): ExtendedAdapterInterface;
|
||||
|
||||
public function getFilesystem(): ExtendedFilesystemInterface;
|
||||
|
||||
public function validate(): void;
|
||||
|
||||
public static function filterPath(string $path): string;
|
||||
}
|
Loading…
Reference in New Issue