說明:本文主要學習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。到時見。