资讯专栏INFORMATION COLUMN

Laravel学习笔记之Filesystem源码解析(上)

AlphaGooo / 2706人阅读

摘要:说明本文主要学习的模块的源码逻辑,把自己的一点点研究心得分享出来,希望对别人有所帮助。实际上,使用了的重载学习笔记之重载,通过魔术方法调用里的,而这个实际上就是,该中有方法,可以调用。

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

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

1. IlluminateFilesystemFilesystemServiceProvider

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

        // IlluminateFilesystem
        $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. IlluminateFilesystemFilesystemManager

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

// 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");

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

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

    // IlluminateFilesystemFilesystemManager
    /**
     * Get a filesystem instance.
     *
     * @param  string  $name
     * @return IlluminateContractsFilesystemFilesystem
     */
    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 IlluminateContractsFilesystemFilesystem
     */
    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 IlluminateContractsFilesystemFilesystem
     *
     * @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);
        }
        // 这里有个巧妙的技巧,检查IlluminateFilesystemFilesystemManager中是否有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 IlluminateContractsFilesystemCloud
     */
    public function createS3Driver(array $config)
    {
        $s3Config = $this->formatS3Config($config);

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

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

        // use LeagueFlysystemAwsS3v3AwsS3Adapter as S3Adapter,这里用了LeagueFlysystemFilesystem,
        // 上文说过Laravel的Filesystem只是个Filesystem Bridge,实际上用的是LeagueFlysystem这个依赖。
        // LeagueFlysystem源码解析会在下篇中讲述,
        // 主要使用了Adapter Pattern把各个Filesystem SDK 整合到一个LeagueFlysystemFilesystemInterface实例中,
        // 有几个核心概念:Adapters, Relative Path, Files First, Plugin, MountManager(File Shortcut), Cache。
        // 下面代码类似于
        //  (new IlluminateFilesystemFilesystemAdapter(
        //    new LeagueFlysystemFilesystem(
        //        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  LeagueFlysystemAdapterInterface  $adapter
     * @param  array  $config
     * @return LeagueFlysystemFlysystemInterface
     */
    protected function createFlysystem(AdapterInterface $adapter, array $config)
    {
        $config = Arr::only($config, ["visibility", "disable_asserts"]);

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

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

(new IlluminateFilesystemFilesystemAdapter(new LeagueFlysystemFilesystem(new S3Adapter(new S3Client(), $options), $config))))

所以,Storage::disk("s3")->allFiles($parameters)或者Storage::disk("s3")->exists($parameters),实际上调用的是IlluminateFilesystemFilesystemAdapter这个对象的allFiles($parameters)和exists($parameters)方法。

3. IlluminateFilesystemFilesystemAdapter

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

    /**
     * Determine if a file exists.
     *
     * @param  string  $path
     * @return bool
     */
    public function exists($path)
    {
        // 实际上又是调用的driver的has()方法,$driver又是LeagueFlysystemFilesystem对象,
        // 查看LeagueFlysystemFilesystem对象的has()方法,
        // 实际上是通过LeagueFlysystemAwsS3v3AwsS3Adapter的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 LeagueFlysystemAwsS3v3AwsS3Adapter(new S3Client(), $config))->listContents()来list contents of a dir.

根据上文的解释,那Storage::disk("s3")->readStream($path)可以调用不?
可以的。实际上,IlluminateFilesystemFilesystemAdapter使用了PHP的重载(Laravel学习笔记之PHP重载(overloading)),通过__call($method, $parameters)魔术方法调用$driver里的$method,而这个$driver实际上就是(new LeagueFlysystemFilesystem),该Filesystem Abstract Layer中有readStream方法,可以调用。

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

    /**
     * Resolve the given disk.
     *
     * @param  string  $name
     * @return IlluminateContractsFilesystemFilesystem
     *
     * @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 IlluminateContractsFilesystemFilesystem
     */
    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里:

    /**
     * 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。到时见。

欢迎关注Laravel-China。

RightCapital招聘Laravel DevOps

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30431.html

相关文章

  • Laravel学习笔记Filesystem源码解析(下)

    摘要:源码解析这个类的源码主要就是文件的操作和文件属性的操作,而具体的操作是通过每一个实现的,看其构造函数看以上代码知道对于操作,实际上是通过的实例来实现的。可以看下的使用上文已经说了,使得对各种的操作变得更方便了,不管是还是得。 说明:本文主要学习下LeagueFlysystem这个Filesystem Abstract Layer,学习下这个package的设计思想和编码技巧,把自己的一...

    Luosunce 评论0 收藏0
  • Laravel学习笔记IoC Container实例化源码解析

    摘要:说明本文主要学习容器的实例化过程,主要包括等四个过程。看下的源码如果是数组,抽取别名并且注册到中,上文已经讨论实际上就是的。 说明:本文主要学习Laravel容器的实例化过程,主要包括Register Base Bindings, Register Base Service Providers , Register Core Container Aliases and Set the ...

    ningwang 评论0 收藏0
  • Laravel学习笔记Filesystem-从Dropbox中下载文件到AWS S3

    摘要:说明本文主要讲述了的文件系统的小,逻辑不复杂,主要就是把上的一个文件下载到本地,和下载到中。写驱动由于没有驱动,需要自定义下在中写上名为的驱动同时在注册下该就行。执行命令后,显示上文件从上下载到上的文件该逻辑简单,但很好玩。 说明:本文主要讲述了Laravel的文件系统Filesystem的小Demo,逻辑不复杂,主要就是把Dropbox上的一个文件下载到本地local,和下载到AWS...

    tylin 评论0 收藏0
  • Laravel学习笔记bootstrap源码解析

    摘要:总结本文主要学习了启动时做的七步准备工作环境检测配置加载日志配置异常处理注册注册启动。 说明:Laravel在把Request通过管道Pipeline送入中间件Middleware和路由Router之前,还做了程序的启动Bootstrap工作,本文主要学习相关源码,看看Laravel启动程序做了哪些具体工作,并将个人的研究心得分享出来,希望对别人有所帮助。Laravel在入口index...

    xiaoxiaozi 评论0 收藏0
  • Laravel核心——Ioc服务容器源码解析(服务器解析

    摘要:而函数作用是加载延迟服务,与容器解析关系不大,我们放在以后再说。在构造之前,服务容器会先把放入中,继而再去解析。利用服务容器解析依赖的参数。 make解析 首先欢迎关注我的博客: www.leoyang90.cn 服务容器对对象的自动解析是服务容器的核心功能,make 函数、build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abst...

    hearaway 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<