说明:本文主要学习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,帮助注册files
和filesystem
等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为IlluminateSupportFacadesFile
,filesystem
的Facade为IlluminateSupportFacadesFilesystem
。
2. IlluminateFilesystemFilesystemManager
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');
那这样的代码内部实现逻辑是怎样的呢?
翻一下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')实际上返回的是这样一段类似代码:
代码语言:javascript复制(new IlluminateFilesystemFilesystemAdapter(new LeagueFlysystemFilesystem(new S3Adapter(new S3Client(),$options), $config))))
所以,Storage::disk('s3')->allFiles(parameters)和exists(parameters)方法。
3. IlluminateFilesystemFilesystemAdapter
查看FilesystemAdapter的源码,提供了关于filesystem的增删改查的一系列方法:
代码语言:javascript复制 /**
* 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(parameters)魔术方法调用driver里的method,而这个driver实际上就是(new LeagueFlysystemFilesystem),该Filesystem Abstract Layer中有readStream方法,可以调用。
Laravelgu官网中介绍通过Storage::extend(driver, Closure callback)来自定义driver,这里我们知道实际上调用的是(new IlluminateFilesystemFilesystemManager(parameters))->extend(callback),上文中提到该对象的resolve(name)代码时会先检查自定义驱动有没有,有的话调用自定义驱动,再看下resolve()代码:
代码语言:javascript复制 /**
* 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里:
代码语言: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。到时见。