Laravel 储存 (Storage) 源码分析

储存门面(Storage)

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 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

你好:
请教一下,下面图片的这行代码是怎么调用到「Illuminate\Filesystem\FilesystemManager」这个类的方法的呢
file

4年前 评论

通过 IOC (Illuminate\Support\Facades\Storage) 中的返回访问名称来映射

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

和服务提供者注册单例的服务 Illuminate\Filesystem\FilesystemServiceProvider

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);
        });
    }
        ...
}

其中由 registerManager 注册文件储存

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

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!