说明:本文主要学习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模块的总体架构以下两张图:php
开发环境:Laravel5.2+MAMP+PHP7+MySQL5.6
架构
Laravel中每个Service模块都有对应的ServiceProvider,主要帮助把该Service注册到Container中,方便在应用程序中利用Facade调用该Service。一样,Filesystem Service有对应的FilesystemServiceProvider,帮助注册files
和filesystem
等Service:app
// 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为IlluminateSupportFacadesFile
,filesystem
的Facade为IlluminateSupportFacadesFilesystem
。ide
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');
那这样的代码内部实现逻辑是怎样的呢?this
翻一下Illuminate\Filesystem\FilesystemManager代码就很容易知道了。首先Storage::disk()是利用了Facade模式,Storage是名为filesystem
的Facade,而filesystem
从上文知道实际是FilesystemManager的对象,因此能够看作(new FilesystemManager)->disk(),看disk()方法源码:spa
// 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')实际上返回的是这样一段相似代码:code
(new \Illuminate\Filesystem\FilesystemAdapter(new \League\Flysystem\Filesystem(new S3Adapter(new S3Client(),$options), $config))))
因此,Storage::disk('s3')->allFiles($parameters)或者Storage::disk('s3')->exists($parameters),实际上调用的是IlluminateFilesystemFilesystemAdapter这个对象的allFiles($parameters)和exists($parameters)方法。
orm
查看FilesystemAdapter的源码,提供了关于filesystem的增删改查的一系列方法:对象
/** * 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($path)
能够调用不?
能够的。实际上,\Illuminate\Filesystem\FilesystemAdapter使用了PHP的重载(Laravel5.2之PHP重载(overloading)),经过__call($method, $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($driver, Closure $callback),上文中提到该对象的resolve($name)代码时会先检查自定义驱动有没有,有的话调用自定义驱动,再看下resolve()代码:
/** * 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里:
/** * 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。到时见。