前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Laravel5.2之Filesystem源码解析(上)

Laravel5.2之Filesystem源码解析(上)

作者头像
botkenni
发布2022-01-10 09:29:28
1.4K0
发布2022-01-10 09:29:28
举报
文章被收录于专栏:IT码农IT码农

说明:本文主要学习Laravel的Filesystem模块的源码逻辑,把自己的一点点研究心得分享出来,希望对别人有所帮助。总的来说,Filesystem模块的源码也比较简单,Laravel的Illuminate\Filesystem模块主要依赖于League\Flysystem这个Filesystem Abstractor Layer,类似于是League\Flysystem的Laravel Bridge。而不同的Filesystem SDK有着各自的具体增删改查逻辑,如AWS S3 SDK,Dropbox SDK,这些SDK都是通过Adapter Pattern装载入这个Filesystem Abstractor Layer。Filesystem模块的整体架构如下两张图:

开发环境:Laravel5.2+MAMP+PHP7+MySQL5.6

1. Illuminate\Filesystem\FilesystemServiceProvider

Laravel中每一个Service模块都有对应的ServiceProvider,主要帮助把该Service注册到Container中,方便在应用程序中利用Facade调用该Service。同样,Filesystem Service有对应的FilesystemServiceProvider,帮助注册filesfilesystem等Service:

代码语言:javascript
复制
        // Illuminate\Filesystem
        $this->app->singleton('files', function () {
            return new Filesystem;
        });
        
        $this->app->singleton('filesystem', function () {
            return new FilesystemManager($this->app);
        });

使用Container的singleton单例注册,同时还注册了filesystem.disk(config/filesystems.php的default配置选项)和filesystem.cloud(config/filesystems.php的cloud配置选项)。其中,files的Facade为IlluminateSupportFacadesFilefilesystem的Facade为IlluminateSupportFacadesFilesystem

2. Illuminate\Filesystem\FilesystemManager

Laravel官网上有类似这样代码:

代码语言:javascript
复制
// Recursively List下AWS S3上路径为dir/to的所有文件,迭代所有的文件和文件夹下的文件
$s3AllFiles = Storage::disk('s3')->allFiles('dir/to');
// Check S3 上dir/to/filesystem.png该文件是否存在
$s3AllFiles = Storage::disk('s3')->exists('dir/to/filesystem.png');

那这样的代码内部实现逻辑是怎样的呢?

翻一下Illuminate\Filesystem\FilesystemManager代码就很容易知道了。首先Storage::disk()是利用了Facade模式,Storage是名为filesystem的Facade,而filesystem从上文知道实际是FilesystemManager的对象,所以可以看做(new FilesystemManager)->disk(),看disk()方法源码:

代码语言:javascript
复制
    // Illuminate\Filesystem\FilesystemManager
    /**
     * Get a filesystem instance.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    public function disk($name = null)
    {
        // 如果不传参,就默认filesystems.default的配置
        $name = $name ?: $this->getDefaultDriver();
        // 这里传s3,$this->get('s3')取S3 driver
        return $this->disks[$name] = $this->get($name);
    }
    
    /**
     * Get the default driver name.
     *
     * @return string
     */
    public function getDefaultDriver()
    {
        return $this->app['config']['filesystems.default'];
    }
    
    /**
     * Attempt to get the disk from the local cache.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    protected function get($name)
    {
        // PHP7里可以这样简洁的写 $this->disks[$name] ?? $this->resolve($name);
        return isset($this->disks[$name]) ? $this->disks[$name] : $this->resolve($name);
    }
    
    /**
     * Resolve the given disk.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        // 取出S3的配置
        $config = $this->getConfig($name);
        // 检查自定义驱动中是否已经提前定义了,自定义是通过extend($driver, Closure $callback)定制化driver,
        // 如果已经定义则取出定制化driver,下文介绍
        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($config);
        }
        // 这里有个巧妙的技巧,检查Illuminate\Filesystem\FilesystemManager中是否有createS3Driver这个方法,
        // 有的话代入$config参数执行该方法,看createS3Driver()方法
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($config);
        } else {
            throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
        }
    }
    
    /**
     * Get the filesystem connection configuration.
     *
     * @param  string  $name
     * @return array
     */
    protected function getConfig($name)
    {
        return $this->app['config']["filesystems.disks.{$name}"];
    }
    
    /**
     * Create an instance of the Amazon S3 driver.
     *
     * @param  array  $config
     * @return \Illuminate\Contracts\Filesystem\Cloud
     */
    public function createS3Driver(array $config)
    {
        $s3Config = $this->formatS3Config($config);

        $root = isset($s3Config['root']) ? $s3Config['root'] : null;

        $options = isset($config['options']) ? $config['options'] : [];

        // use League\Flysystem\AwsS3v3\AwsS3Adapter as S3Adapter,这里用了League\Flysystem\Filesystem,
        // 上文说过Laravel的Filesystem只是个Filesystem Bridge,实际上用的是League\Flysystem这个依赖。
        // League\Flysystem源码解析会在下篇中讲述,
        // 主要使用了Adapter Pattern把各个Filesystem SDK 整合到一个\League\Flysystem\FilesystemInterface实例中,
        // 有几个核心概念:Adapters, Relative Path, Files First, Plugin, MountManager(File Shortcut), Cache。
        // 下面代码类似于
        //  (new \Illuminate\Filesystem\FilesystemAdapter(
        //    new \League\Flysystem\Filesystem(
        //        new S3Adapter(new S3Client(), $options), $config)
        //    )
        //  ))
        return $this->adapt($this->createFlysystem(
            new S3Adapter(new S3Client($s3Config), $s3Config['bucket'], $root, $options), $config
        ));
    }
    
    /**
     * Create a Flysystem instance with the given adapter.
     *
     * @param  \League\Flysystem\AdapterInterface  $adapter
     * @param  array  $config
     * @return \League\Flysystem\FlysystemInterface
     */
    protected function createFlysystem(AdapterInterface $adapter, array $config)
    {
        $config = Arr::only($config, ['visibility', 'disable_asserts']);

        // use League\Flysystem\Filesystem as Flysystem
        return new Flysystem($adapter, count($config) > 0 ? $config : null);
    }
    
    /**
     * Adapt the filesystem implementation.
     *
     * @param  \League\Flysystem\FilesystemInterface  $filesystem
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    protected function adapt(FilesystemInterface $filesystem)
    {
        return new FilesystemAdapter($filesystem);
    }

通过代码里注释,可以看出Storage::disk('s3')实际上返回的是这样一段类似代码:

代码语言:javascript
复制
(new \Illuminate\Filesystem\FilesystemAdapter(new \League\Flysystem\Filesystem(new S3Adapter(new S3Client(),$options), $config))))

所以,Storage::disk('s3')->allFiles(parameters)和exists(parameters)方法。

3. Illuminate\Filesystem\FilesystemAdapter

查看FilesystemAdapter的源码,提供了关于filesystem的增删改查的一系列方法:

代码语言:javascript
复制
    /**
     * Determine if a file exists.
     *
     * @param  string  $path
     * @return bool
     */
    public function exists($path)
    {
        // 实际上又是调用的driver的has()方法,$driver又是\League\Flysystem\Filesystem对象,
        // 查看\League\Flysystem\Filesystem对象的has()方法,
        // 实际上是通过League\Flysystem\AwsS3v3\AwsS3Adapter的has()方法,
        // 当然最后调用的是AWS S3 SDK包的(new S3Client())->doesObjectExist($parameters)检查S3上该文件是否存在
        return $this->driver->has($path);
    }
    
    /**
     * Get all of the files from the given directory (recursive).
     *
     * @param  string|null  $directory
     * @return array
     */
    public function allFiles($directory = null)
    {
        return $this->files($directory, true);
    }
    
    /**
     * Get an array of all files in a directory.
     *
     * @param  string|null  $directory
     * @param  bool  $recursive
     * @return array
     */
    public function files($directory = null, $recursive = false)
    {
        $contents = $this->driver->listContents($directory, $recursive);

        return $this->filterContentsByType($contents, 'file');
    }
    
    /**
     * Pass dynamic methods call onto Flysystem.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \BadMethodCallException
     */
    public function __call($method, array $parameters)
    {
        return call_user_func_array([$this->driver, $method], $parameters);
    }

通过代码注释知道,Storage::disk('s3')->exists($parameters)实际上最后通过调用S3 SDK的(new S3Client())->doesObjectExist($parameters)检查S3上有没有该文件,Storage::disk('s3')->allFiles($parameters)也是同理,通过调用(new League\Flysystem\AwsS3v3\AwsS3Adapter(new S3Client(), $config))->listContents()来list contents of a dir.

根据上文的解释,那Storage::disk('s3')->readStream(parameters)魔术方法调用driver里的method,而这个driver实际上就是(new \League\Flysystem\Filesystem),该Filesystem Abstract Layer中有readStream方法,可以调用。

Laravelgu官网中介绍通过Storage::extend(driver, Closure callback)来自定义driver,这里我们知道实际上调用的是(new \Illuminate\Filesystem\FilesystemManager(parameters))->extend(callback),上文中提到该对象的resolve(name)代码时会先检查自定义驱动有没有,有的话调用自定义驱动,再看下resolve()代码:

代码语言:javascript
复制
    /**
     * Resolve the given disk.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        // 检查自动以驱动是否存在,存在的话,调用callCustomCreator来解析出$driver
        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($config);
        } else {
            throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
        }
    }    
   
   /**
     * Call a custom driver creator.
     *
     * @param  array  $config
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    protected function callCustomCreator(array $config)
    {
        $driver = $this->customCreators[$config['driver']]($this->app, $config);

        if ($driver instanceof FilesystemInterface) {
            return $this->adapt($driver);
        }

        return $driver;
    }     

extend()方法就是把自定义的驱动注册进$customCreators里:

代码语言:javascript
复制
    /**
     * Register a custom driver creator Closure.
     *
     * @param  string    $driver
     * @param  \Closure  $callback
     * @return $this
     */
    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;

        return $this;
    }

总结:上篇主要讲述了Laravel Filesystem Bridge,该Bridge只是把League/Flysystem这个package简单做了桥接和封装,便于在Laravel中使用。在下篇中,主要学习下League/Flysystem这个package的源码,League/Flysystem作为一个Filesystem Abstractor Layer,利用了Adapter Pattern来封装各个filesystem的SDK,如AWS S3 SDK或Dropbox SDK。到时见。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016/10/25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Illuminate\Filesystem\FilesystemServiceProvider
  • 2. Illuminate\Filesystem\FilesystemManager
  • 3. Illuminate\Filesystem\FilesystemAdapter
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档