Laravel 儲存 (Storage) 原始碼分析

myAdream發表於2019-08-14

fake 方法

   /**
     * Replace the given disk with a local testing disk.
     *
     * @param  string|null  $disk
     *
     * @return void
     */
    public static function fake($disk = null)
    {
        $disk = $disk ?: self::$app['config']->get('filesystems.default');

        (new Filesystem)->cleanDirectory(
            $root = storage_path('framework/testing/disks/'.$disk)
        );

        static::set($disk, self::createLocalDriver(['root' => $root]));
    }

獲取儲存驅動名稱和子路徑

$disk = $disk ?: self:$app['config']->get('filesystems.default');

判斷是否有傳遞測試路徑 用於儲存

刪除存在的目錄

(new Filesystem)->cleanDirectory(
  $root = storage_path('framework/testing/disks/'.$disk)
);

建立檔案物件(Filesystem)通過cleanDirectory方法 來刪除已經存在的目錄

Filesystem 檔案 cleanDirectory 刪除程式碼
        /**
     * Empty the specified directory of all files and folders.
     *
     * @param  string  $directory
     * @return bool
     */
    public function cleanDirectory($directory)
    {
        return $this->deleteDirectory($directory, true);
    }

cleanDirectory可以看到呼叫自身的deleteDirectory方法

    /**
     * Recursively delete a directory.
     *
     * The directory itself may be optionally preserved.
     *
     * @param  string  $directory
     * @param  bool    $preserve
     * @return bool
     */
    public function deleteDirectory($directory, $preserve = false)
    {
        if (! $this->isDirectory($directory)) {
            return false;
        }

        $items = new FilesystemIterator($directory);

        foreach ($items as $item) {
            // If the item is a directory, we can just recurse into the function and
            // delete that sub-directory otherwise we'll just delete the file and
            // keep iterating through each file until the directory is cleaned.
            if ($item->isDir() && ! $item->isLink()) {
                $this->deleteDirectory($item->getPathname());
            }

            // If the item is just a file, we can go ahead and delete it since we're
            // just looping through and waxing all of the files in this directory
            // and calling directories recursively, so we delete the real path.
            else {
                $this->delete($item->getPathname());
            }
        }

        if (! $preserve) {
            @rmdir($directory);
        }

        return true;
    }

deleteDirectory 方法中的第一行是驗證方法是否存在

    if (! $this->isDirectory($directory)) {
      return false;
    }

isDirectory 方法

    /**
     * Determine if the given path is a directory.
     *
     * @param  string  $directory
     * @return bool
     */
    public function isDirectory($directory)
    {
        return is_dir($directory);
    }

deleteDirectory 第二行程式碼建立一個 FilesystemIterator 來獲取當前目錄下所有檔案和刪除所有檔案和目錄

    $items = new FilesystemIterator($directory);

    foreach ($items as $item) {
      // If the item is a directory, we can just recurse into the function and
      // delete that sub-directory otherwise we'll just delete the file and
      // keep iterating through each file until the directory is cleaned.
      if ($item->isDir() && ! $item->isLink()) {
        $this->deleteDirectory($item->getPathname());
      }

      // If the item is just a file, we can go ahead and delete it since we're
      // just looping through and waxing all of the files in this directory
      // and calling directories recursively, so we delete the real path.
      else {
        $this->delete($item->getPathname());
      }
    }
  • 第一個條件驗證是否目錄和是否有完整路徑, 呼叫自身遞迴刪除
  • 如果不是目錄呼叫自身 delete 方法刪除檔案和軟連線
    /**
     * Delete the file at a given path.
     *
     * @param  string|array  $paths
     * @return bool
     */
    public function delete($paths)
    {
        $paths = is_array($paths) ? $paths : func_get_args();

        $success = true;

        foreach ($paths as $path) {
            try {
                if (! @unlink($path)) {
                    $success = false;
                }
            } catch (ErrorException $e) {
                $success = false;
            }
        }

        return $success;
    }

建立目錄

   static::set($disk, self::createLocalDriver(['root' => $root]));

呼叫 Illuminate\Filesystem\FilesystemManager 類中的方法建立本地測試驅動

    /**
     * Create an instance of the local driver.
     *
     * @param  array  $config
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    public function createLocalDriver(array $config)
    {
        $permissions = $config['permissions'] ?? [];

        $links = ($config['links'] ?? null) === 'skip'
            ? LocalAdapter::SKIP_LINKS
            : LocalAdapter::DISALLOW_LINKS;

        return $this->adapt($this->createFlysystem(new LocalAdapter(
            $config['root'], LOCK_EX, $links, $permissions
        ), $config));
    }

獲取檔案和目錄寫入的許可權

$permissions = $config['permissions'] ?? [];

獲取是否有軟連線寫入

$links = ($config['links'] ?? null) === 'skip'
            ? LocalAdapter::SKIP_LINKS
            : LocalAdapter::DISALLOW_LINKS;
建立一個新的本地檔案介面卡 (League\Flysystem\Adapter\LocalAdapter) 類物件
    /**
     * Constructor.
     *
     * @param string $root
     * @param int    $writeFlags
     * @param int    $linkHandling
     * @param array  $permissions
     *
     * @throws LogicException
     */
    public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = [])
    {
        $root = is_link($root) ? realpath($root) : $root;
        $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);
        $this->ensureDirectory($root);

        if ( ! is_dir($root) || ! is_readable($root)) {
            throw new LogicException('The root path ' . $root . ' is not readable.');
        }

        $this->setPathPrefix($root);
        $this->writeFlags = $writeFlags;
        $this->linkHandling = $linkHandling;
    }

獲取檔案絕對路徑, 驗證是否絕對路徑如果不是轉換為絕對路徑

   $root = is_link($root) ? realpath($root) : $root;

獲取檔案或者資料夾許可權

   $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);

ensureDirectory 驗證目錄是否存在不存在重新建立

    /**
     * Ensure the root directory exists.
     *
     * @param string $root root directory path
     *
     * @return void
     *
     * @throws Exception in case the root directory can not be created
     */
    protected function ensureDirectory($root)
    {
        if ( ! is_dir($root)) {
            $umask = umask(0);

            if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) {
                $mkdirError = error_get_last();
            }

            umask($umask);
            clearstatcache(false, $root);

            if ( ! is_dir($root)) {
                $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
                throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage));
            }
        }
    }

setPathPrefix 設定絕對目錄

    /**
     * Set the path prefix.
     *
     * @param string $prefix
     *
     * @return void
     */
    public function setPathPrefix($prefix)
    {
        $prefix = (string) $prefix;

        if ($prefix === '') {
            $this->pathPrefix = null;

            return;
        }

        $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator;
    }
建立檔案物件 createFlysystem

建立程式碼

$this->createFlysystem(
   new LocalAdapter($config['root'], LOCK_EX, $links, $permissions),
   $config);
    /**
     * Create a Flysystem instance with the given adapter.
     *
     * @param  \League\Flysystem\AdapterInterface  $adapter
     * @param  array  $config
     * @return \League\Flysystem\FilesystemInterface
     */
    protected function createFlysystem(AdapterInterface $adapter, array $config)
    {
        $cache = Arr::pull($config, 'cache');

        $config = Arr::only($config, ['visibility', 'disable_asserts', 'url']);

        if ($cache) {
            $adapter = new CachedAdapter($adapter, $this->createCacheStore($cache));
        }

        return new Flysystem($adapter, count($config) > 0 ? $config : null);
    }

獲取配置資料

$cache = Arr::pull($config, 'cache');

$config = Arr::only($config, ['visibility', 'disable_asserts', 'url']);

看是否需要快取配置, 需要的話建立 快取介面卡 替換之前的 本地介面卡

if ($cache) {
   $adapter = new CachedAdapter($adapter, $this->createCacheStore($cache));
}

建立 League\Flysystem\Filesystem

   return new Flysystem($adapter, count($config) > 0 ? $config : null);

儲存使用紀錄

static::set($disk, self::createLocalDriver(['root' => $root]));
    /**
     * Set the given disk instance.
     *
     * @param  string  $name
     * @param  mixed  $disk
     * @return $this
     */
    public function set($name, $disk)
    {
        $this->disks[$name] = $disk;

        return $this;
    }

關於 Storage 門面如何對映 FilesystemManager 說明

通過 laravel框架提供 IOC (控制反轉) 來進行對映

Illuminate\Support\Facades\Storage 門面方法中 getFacadeAccessor 提供的名稱來訪問(對映)
其中 Storage 中的 getFacadeAccessor 返回了 filesystem 名稱

<?php

namespace Illuminate\Support\Facades;

use Illuminate\Filesystem\Filesystem;

/**
 * @method static \Illuminate\Contracts\Filesystem\Filesystem disk(string $name = null)
 *
 * @see \Illuminate\Filesystem\FilesystemManager
 */
class Storage extends Facade
{
        ...

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'filesystem';
    }
}

然後在 Illuminate\Filesystem\FilesystemServiceProvider 檔案服務提供者註冊單例

<?php

namespace Illuminate\Filesystem;

use Illuminate\Support\ServiceProvider;

class FilesystemServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerNativeFilesystem();

        $this->registerFlysystem();
    }

    /**
     * Register the native filesystem implementation.
     *
     * @return void
     */
    protected function registerNativeFilesystem()
    {
        $this->app->singleton('files', function () {
            return new Filesystem;
        });
    }

    /**
     * Register the driver based filesystem.
     *
     * @return void
     */
    protected function registerFlysystem()
    {
        $this->registerManager();

        $this->app->singleton('filesystem.disk', function () {
            return $this->app['filesystem']->disk($this->getDefaultDriver());
        });

        $this->app->singleton('filesystem.cloud', function () {
            return $this->app['filesystem']->disk($this->getCloudDriver());
        });
    }

    /**
     * Register the filesystem manager.
     *
     * @return void
     */
    protected function registerManager()
    {
        $this->app->singleton('filesystem', function () {
            return new FilesystemManager($this->app);
        });
    }

   ...
}

說明
由於個人技術知識有限, 在部分說明中可能會出現錯誤解釋, 已經說明不完整的地方, 可以指出幫忙改正, 謝謝

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章