Это может потребоваться в тех случаях, когда в базе хранятся разные пути, например, к изображениям, но для взаимодействия с ними используется один диск. Например, один изображения имеют путь в storage/images/, а другие storage/upload и мы хотим сами формировать url
файла с помощью url()
метода.
В документации рассмотрен один вариант - на примере с драйвером Dropbox. Но это не совсем то. Нас интересует работа именно с локальными файлами.
Создаем адаптер
Создаём файл laravel/app/Filesystem/PublicFilesystemAdapter.php
.
Помещаем в него следующее содержимое:
<?php
namespace App\Filesystem;
use League\Flysystem\Adapter\Local;
class PublicFilesystemAdapter extends Local {
public function __construct(private $config)
{
parent::__construct($config['root']);
}
public function getUrl($path): string
{
$url = array_key_exists('url', $this->config) ? $this->config['url'] : '/storage';
if (!\Str::startsWith($path, '/upload')) {
return $this->concatPathToUrl($url, $path);
}
return $path;
}
/**
* Concatenate a path to a URL.
*
* @param string $url
* @param string $path
* @return string
*/
protected function concatPathToUrl(string $url, string $path): string
{
return rtrim($url, '/') . '/' . ltrim($path, '/');
}
}
Здесь создается адаптер и наследуется от стандартного ларавелевского Local адаптера. Он имеет все методы Local.
Инициализируем подключение кастомного драйвера в сервис провайдере.
Идем в файл laravel/app/Providers/AppServiceProvider.php
.
Подключаем классы:
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use App\Filesystem\PublicFilesystemAdapter;
Добавляем туда код в метод boot():
public function boot()
{
Storage::extend('public', function ($app, $config) {
return new Filesystem(new PublicFilesystemAdapter($config));
});
}
Здесь мы регистрируем свой драйвер, который будет потом в конфиге использоваться.
Обязательно должен быть возвращен Filesystem
, а он параметром обязательно принимает адаптер, а адаптеру передаем конфиг (адаптер Local по умолчанию принимает не конфиг, а путь root).
Редактируем конфиг filesystems.php.
Идем в файл laravel/config/filesystems.php
.
Добавляем новый диск:
'public' => [
'driver' => 'public',
'root' => storage_path('app/public'),
'url' => '/storage',
'visibility' => 'public',
]
Благолдаря регистрации в AppServiceProvider
, система уже знает о новом драйвере, поэтому сюда добавляем его название которое регистрировали - public
.
Теперь мы можем использоваться всеми стандартными функциями обычного диска, но метод url()
у нас будет работать по скорректированной логике.
Пример:
\Storage::disk('public')->url('path/to/file.jpg');
Немного о принципах работы Filesystem в laravel.
В рамках этой системы используется паттерн "Адаптер". Его задача предоставить единый интерфейс взаимодействия с разными сущностями, выполняющими одну задачу. В данном случае, задача - манипулирование файлами, разные сущности - это локальное файловое хранилище, облачное хранилище Dropbox, облачное хранилище Google и т.д.
В качестве основного пакета для файловой системы в laravel используется League/Flysystem. Экземпляр его класса и должен возвращаться в сервис провайдере при объявлении кастомного драйвера.
Стоит обратить внимание на метод url()
файла laravel/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php
.
public function url($path)
{
$adapter = $this->driver->getAdapter();
if ($adapter instanceof CachedAdapter) {
$adapter = $adapter->getAdapter();
}
if (method_exists($adapter, 'getUrl')) {
return $adapter->getUrl($path);
} elseif (method_exists($this->driver, 'getUrl')) {
return $this->driver->getUrl($path);
} elseif ($adapter instanceof AwsS3Adapter) {
return $this->getAwsUrl($adapter, $path);
} elseif ($adapter instanceof Ftp || $adapter instanceof Sftp) {
return $this->getFtpUrl($path);
} elseif ($adapter instanceof LocalAdapter) {
return $this->getLocalUrl($path);
} else {
throw new RuntimeException('This driver does not support retrieving URLs.');
}
}
Именно он работает когда мы хотим получить url
файла через \Storage::disk('public')->url('path/to/file.jpg');
. Он ищет функцию getUrl()
в адаптере или драйверах и выполняет первый попавшийся вариант. Этот метод мы и определили в своем кастомном адаптере.
Материалы по этой теме на просторах интернета
- Документация - https://laravel.su/docs/8.x/filesystem#custom-filesystems. Пример с пакетом Dropbox.
- Старая статья 7милетней давности - https://laracasts.com/discuss/channels/general-discussion/l5-use-flysystem-replicate-adapter-with-filesystem. Здесь предлагают наследовать
FilesystemManager
без особых подробностей где и для чего это делать. - Возможно, более простой способ переопределить стандартный драйвер файловой системы - https://spatie.be/docs/laravel-medialibrary/v10/advanced-usage/overriding-the-default-filesystem-behaviour
- Вопрос на Stackoverflow - https://stackoverflow.com/questions/42744293/how-to-extend-laravel-storage-facade. Тоже пример с
FilesystemManager
. - Статья разработчика пакета Flysystem - https://benjamincrozat.com/laravel-dropbox-driver#how-to-create-a-custom-storage-driver-in-laravel. В последнем абзаце "Как сделать кастомный драйвер". Пробовал сделать как в статье - не сработало.