2026-11-03 12:26:17 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Uwil\Storage\Config;
|
|
|
|
|
|
2026-11-03 16:07:54 +01:00
|
|
|
use Uwil\Storage\Exceptions\UnexpectedStorageException;
|
2026-11-03 12:26:17 +01:00
|
|
|
use Uwil\Storage\Lib\Utils;
|
|
|
|
|
use Uwil\Storage\Lib\ValueObjects\DiskName;
|
|
|
|
|
|
|
|
|
|
final class StorageServiceConfig
|
|
|
|
|
{
|
2026-11-03 16:15:57 +01:00
|
|
|
public const array UNSUPPORTED_EXTENSIONS = ['svg', 'xml'];
|
|
|
|
|
|
2026-11-03 12:26:17 +01:00
|
|
|
private DiskName $default;
|
|
|
|
|
|
|
|
|
|
/** @var DiskName[] */
|
|
|
|
|
private array $eligibles;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string[] $eligibleDisks
|
|
|
|
|
* @param object{'disk':string,'driver':string}[] $laravelExistingDisks
|
|
|
|
|
* @param string[] $allowedExtensions
|
|
|
|
|
*/
|
|
|
|
|
public function __construct(
|
|
|
|
|
private string $defaultDisk,
|
|
|
|
|
private array $eligibleDisks,
|
|
|
|
|
private array $laravelExistingDisks,
|
|
|
|
|
private array $allowedExtensions
|
|
|
|
|
) {
|
|
|
|
|
if (Utils::diskExists($this->defaultDisk) === false) {
|
2026-11-03 16:07:54 +01:00
|
|
|
throw new UnexpectedStorageException('Default disk does not exist');
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
array_map(
|
|
|
|
|
fn ($disk) => Utils::diskExists($disk)
|
2026-11-03 16:07:54 +01:00
|
|
|
|| throw new UnexpectedStorageException("Eligible disk {$disk} does not exist"),
|
2026-11-03 12:26:17 +01:00
|
|
|
$this->eligibleDisks
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
array_map(
|
|
|
|
|
fn ($disk) => Utils::diskExists($disk->disk)
|
2026-11-03 16:07:54 +01:00
|
|
|
|| throw new UnexpectedStorageException("Laravel existing disk {$disk->disk} does not exist"),
|
2026-11-03 12:26:17 +01:00
|
|
|
$this->laravelExistingDisks
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (empty($this->laravelExistingDisks)) {
|
2026-11-03 16:07:54 +01:00
|
|
|
throw new UnexpectedStorageException('Laravel existing disks cannot be empty');
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (empty($this->eligibleDisks)) {
|
2026-11-03 16:07:54 +01:00
|
|
|
throw new UnexpectedStorageException('Eligible disks cannot be empty');
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$intersect = array_intersect(
|
|
|
|
|
$this->eligibleDisks,
|
|
|
|
|
array_map(fn ($disk) => $disk->disk, $this->laravelExistingDisks)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (empty($intersect)) {
|
2026-11-03 16:07:54 +01:00
|
|
|
throw new UnexpectedStorageException('No eligible disks exist in Laravel existing disks');
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$intersectWithDrivers = array_map(
|
|
|
|
|
fn ($disk) => $disk,
|
|
|
|
|
array_filter(
|
|
|
|
|
$this->laravelExistingDisks,
|
|
|
|
|
fn ($disk) => in_array($disk->disk, $intersect, true)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$defaultDiskWithDriver = array_filter(
|
|
|
|
|
$this->laravelExistingDisks,
|
|
|
|
|
fn ($disk) => $disk->disk === $this->defaultDisk
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (empty($defaultDiskWithDriver)) {
|
2026-11-03 16:07:54 +01:00
|
|
|
throw new UnexpectedStorageException('Default disk does not exist in Laravel existing disks');
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$defaultDiskWithDriver = array_values($defaultDiskWithDriver)[0];
|
|
|
|
|
|
|
|
|
|
$this->default = new DiskName($defaultDiskWithDriver->disk, $defaultDiskWithDriver->driver);
|
|
|
|
|
$this->eligibles = array_map(fn ($disk) => new DiskName(
|
|
|
|
|
$disk->disk,
|
|
|
|
|
$disk->driver
|
|
|
|
|
), $intersectWithDrivers);
|
|
|
|
|
|
|
|
|
|
if (empty($this->allowedExtensions)) {
|
2026-11-03 16:07:54 +01:00
|
|
|
throw new UnexpectedStorageException('Allowed extensions cannot be empty');
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($this->allowedExtensions as $extension) {
|
|
|
|
|
if (! is_string($extension) || empty(mb_trim($extension))) {
|
2026-11-03 16:07:54 +01:00
|
|
|
throw new UnexpectedStorageException("Allowed extension {$extension} is not valid");
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
2026-11-03 16:15:57 +01:00
|
|
|
|
|
|
|
|
if (in_array(strtolower(ltrim($extension, '.')), self::UNSUPPORTED_EXTENSIONS, true)) {
|
|
|
|
|
throw new UnexpectedStorageException("Extension '{$extension}' is forbidden: XML/SVG sanitization is currently not supported.");
|
|
|
|
|
}
|
2026-11-03 12:26:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getDefaultDisk(): DiskName
|
|
|
|
|
{
|
|
|
|
|
return $this->default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @return DiskName[] */
|
2026-11-03 16:16:19 +01:00
|
|
|
public function getEligibleDisks(): array
|
2026-11-03 12:26:17 +01:00
|
|
|
{
|
|
|
|
|
return array_map(fn ($disk) => $disk, $this->eligibles);
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-03 16:07:54 +01:00
|
|
|
public function isDiskEligible(DiskName | string $disk): bool
|
|
|
|
|
{
|
|
|
|
|
$diskName = $disk instanceof DiskName ? $disk->value : (string) $disk;
|
|
|
|
|
|
|
|
|
|
foreach ($this->eligibles as $eligible) {
|
|
|
|
|
if ($eligible->value === $diskName) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-03 12:26:17 +01:00
|
|
|
public function isExtensionAllowed(string $extension): bool
|
|
|
|
|
{
|
2026-11-03 16:15:57 +01:00
|
|
|
$normalized = strtolower(ltrim($extension, '.'));
|
|
|
|
|
|
|
|
|
|
if (in_array($normalized, self::UNSUPPORTED_EXTENSIONS, true)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-03 12:26:17 +01:00
|
|
|
return in_array(
|
2026-11-03 16:15:57 +01:00
|
|
|
$normalized,
|
2026-11-03 12:26:17 +01:00
|
|
|
array_map('strtolower', $this->allowedExtensions),
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @return string[] */
|
|
|
|
|
public function getAllowedExtensions(): array
|
|
|
|
|
{
|
|
|
|
|
return $this->allowedExtensions;
|
|
|
|
|
}
|
|
|
|
|
}
|