摘要:源碼解析這個類的源碼主要就是文件的操作和文件屬性的操作,而具體的操作是通過每一個實現的,看其構造函數看以上代碼知道對于操作,實際上是通過的實例來實現的。可以看下的使用上文已經說了,使得對各種的操作變得更方便了,不管是還是得。
說明:本文主要學習下LeagueFlysystem這個Filesystem Abstract Layer,學習下這個package的設計思想和編碼技巧,把自己的一點點研究心得分享出來,希望對別人有幫助。實際上,這個Filesystem Abstract Layer也不是很復雜,總的來說有幾個關鍵概念:
Adapter:定義了一個AdapterInterface并注入到LeagueFlysystemFilesystem,利用Adapter Pattern來橋接不同的filesystem。如AWS S3的filesystem SDK,只要該SDK的S3 Adapter實現了AdapterInterface,就可以作為LeagueFlysystemFilesystem文件系統驅動之一。再比如,假設阿里云的一個filesystem SDK名叫AliyunFilesystem SDK,想要把該SDK裝入進LeagueFlysystemFilesystem作為驅動之一,那只要再做一個AliyunAdapter實現AdapterInterface就行。這也是Adapter Pattern的設計巧妙的地方,當然,這種模式生活中隨處可見,不復雜,有點類似于機器人行業的模塊化組裝一樣。
Relative Path:這個相對路徑概念就比較簡單了,就是每一個文件的路徑是相對路徑,如AWS S3中如果指向一個名叫file.txt的文件路徑,可以這么定義Storage::disk("s3")->get("2016-09-09/daily/file.txt")就可以了,這里2016-09-09/daily/file.txt是相對于存儲bucket的相對路徑(bucket在AWS S3中稱為桶的意思,就是可以定義多個bucket,不同的bucket存各自的文件,互不干擾,在Laravel配置S3時得指定是哪個bucket,這里假設file.txt存儲在laravel bucket中),盡管其實際路徑為類似這樣的:https://s3.amazonaws.com/laravel/2016-09-09/daily/file.txt。很簡單的概念。
File First:這個概念簡單,意思就是相對于Directory是二等公民,File是一等公民。在創建一個file時,如2016-09-09/daily/file.txt時,如果沒有2016-09-09/daily這個directory時,會自動遞歸創建。指定一個文件時,需要給出相對路徑,如2016-09-09/daily/file.txt,但不是file.txt,這個指定無意義。
Cache:文件緩存還提高性能,但只緩存文件的meta-data,不緩存文件的內容,Cache模塊作為一個獨立的模塊利用Decorator Pattern,把一個CacheInterface和AdapterInterface裝入進CacheAdapterInterface中,所以也可以拆解不使用該模塊。Decorator Pattern也是Laravel中實現Middleware的一個重要技術手段,以后應該還會聊到這個技術。
Plugin:LeagueFlysystem還提供了Plugin供自定義該package中沒有的feature,LeagueFlysystemFilesystem中有一個addPlugin($plugin)方法供向LeagueFlysystemFilesystem裝入plugin,當然,LeagueFlysystem中也已經提供了七八個plugin供開箱即用。Plugin的設計個人感覺既合理也美妙,可以實現需要的feature,并很簡單就能裝入,值得學習下。
Mount Manager:Mount Manager是一個封裝類,簡化對多種filesystem的CRUD操作,不管該filesystem是remote還是local。這個概念有點類似于這樣的東西:MAC中裝有iCloud Drive這個云盤,把local的一個文件file.txt中復制到iCloud Drive中感覺和復制到本地盤是沒有什么區別,那用代碼來表示可以在復制操作時給文件路徑加個"協議標識",如$mountManager->copy("local://2016-09-09/daily/file.txt", "icloud://2016-09-09/daily/filenew.txt"),這樣就把本地磁盤的file.txt復制到icloud中,并且文件名稱指定為2016-09-09/daily/filenew.txt。這個概念也很好理解。
1. LeagueFlysystemFilesystem源碼解析Filesystem這個類的源碼主要就是文件的CRUD操作和文件屬性的setter/getter操作,而具體的操作是通過每一個Adapter實現的,看其構造函數:
/** * Constructor. * * @param AdapterInterface $adapter * @param Config|array $config */ public function __construct(AdapterInterface $adapter, $config = null) { $this->adapter = $adapter; $this->setConfig($config); } /** * Get the Adapter. * * @return AdapterInterface adapter */ public function getAdapter() { return $this->adapter; } /** * @inheritdoc */ public function write($path, $contents, array $config = []) { $path = Util::normalizePath($path); $this->assertAbsent($path); $config = $this->prepareConfig($config); return (bool) $this->getAdapter()->write($path, $contents, $config); }
看以上代碼知道對于write($parameters)操作,實際上是通過AdapterInterface的實例來實現的。所以,假設對于S3的write操作,看AwsS3Adapter的write($parameters)源碼就行,具體代碼可看這個依賴:
composer require league/flysystem-aws-s3-v3
所以,如果假設要在Laravel程序中使用Aliyun的filesystem,只需要干三件事情:1. 拿到Aliyun的filesystem的PHP SDK;2. 寫一個AliyunAdapter實現LeagueFlysytemAdapterInterface;3. 在Laravel中AppServiceProvider中使用Storage::extend($name, Closure $callback)注冊一個自定義的filesystem。
LeagueFlysystem已經提供了幾個adapter,如Local、Ftp等等,并且抽象了一個abstract class AbstractAdapter供繼承,所以AliyunAdapter只需要extends 這個AbstractAdapter就行了:
LeagueFlysystemFilesystem又是implements了FilesystemInterface,所以覺得這個Filesystem不太好可以自己寫個替換掉它,只要實現這個FilesystemInterface就行。
2. PluggableTrait源碼解析OK, 現在需要做一個Plugin,實現對一個文件的內容進行sha1加密,看如下代碼:
// AbstractPlugin這個抽象類league/flysystem已經提供 use LeagueFlysystemFilesystemInterface; use LeagueFlysystemPluginInterface; abstract class AbstractPlugin implements PluginInterface { /** * @var FilesystemInterface */ protected $filesystem; /** * Set the Filesystem object. * * @param FilesystemInterface $filesystem */ public function setFilesystem(FilesystemInterface $filesystem) { $this->filesystem = $filesystem; } } // 只需繼承AbstractPlugin抽象類就行 class Sha1File extends AbstractPlugin { public function getMethod () { return "sha1File"; } public function handle($path = null) { $contents = $this->filesystem->read($path); return sha1($contents); } }
這樣一個Plugin就已經造好了,如何使用:
use LeagueFlysystemFilesystem; use LeagueFlysystemAdapter; use LeagueFlysystemPlugin; $filesystem = new Filesystem(new AdapterLocal(__DIR__."/path/to/file.txt")); $filesystem->addPlugin(new PluginSha1File); $sha1 = $filesystem->sha1File("path/to/file.txt");
Plugin就是這樣制造并使用的,內部調用邏輯是怎樣的呢?
實際上,Filesystem中use PluggableTrait,這個trait提供了addPlugin($parameters)方法。但$filesystem是沒有sah1File($parameters)方法的,這是怎么工作的呢?看PluggableTrait的__call():
/** * Plugins pass-through. * * @param string $method * @param array $arguments * * @throws BadMethodCallException * * @return mixed */ public function __call($method, array $arguments) { try { return $this->invokePlugin($method, $arguments, $this); } catch (PluginNotFoundException $e) { throw new BadMethodCallException( "Call to undefined method " . get_class($this) . "::" . $method ); } } /** * Invoke a plugin by method name. * * @param string $method * @param array $arguments * * @return mixed */ protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem) { $plugin = $this->findPlugin($method); $plugin->setFilesystem($filesystem); $callback = [$plugin, "handle"]; return call_user_func_array($callback, $arguments); } /** * Find a specific plugin. * * @param string $method * * @throws LogicException * * @return PluginInterface $plugin */ protected function findPlugin($method) { if ( ! isset($this->plugins[$method])) { throw new PluginNotFoundException("Plugin not found for method: " . $method); } if ( ! method_exists($this->plugins[$method], "handle")) { throw new LogicException(get_class($this->plugins[$method]) . " does not have a handle method."); } return $this->plugins[$method]; }
看上面源碼發現,$sha1 = $filesystem->sha1File("path/to/file.txt")會調用invokePlugin($parameters),然后從$plugins[$method]中找有沒有名為"sha1File"的Plugin,看addPlugin()源碼:
/** * Register a plugin. * * @param PluginInterface $plugin * * @return $this */ public function addPlugin(PluginInterface $plugin) { $this->plugins[$plugin->getMethod()] = $plugin; return $this; }
addPlugin($parameters)就是向$plugins[$name]中注冊Plugin,這里$filesystem->addPlugin(new PluginSha1File)就是向$plugins[$name]注冊名為"sha1File" = (new PluginSha1File))->getMethod()的Plugin,然后return call_user_func_array([new PluginSha1File, "handle"], $arguments),等同于調用(new PluginSha1File)->handle($arguments),所以$sha1 = $filesystem->sha1File("path/to/file.txt")就是執行(new PluginSha1File)->handle("path/to/file.txt")這段代碼。
3. MountManager源碼解析上文已經學習了主要的幾個技術:Filesystem、Adapter和Plugin,也包括學習了它們的設計和使用,這里看下MountManager的使用。MountManager中也use PluggableTrait并定義了__call()方法,所以在MountManager中使用Plugin和Filesystem中一樣。可以看下MountManager的使用:
$ftp = new LeagueFlysystemFilesystem($ftpAdapter); $s3 = new LeagueFlysystemFilesystem($s3Adapter); $local = new LeagueFlysystemFilesystem($localAdapter); // Add them in the constructor $manager = new LeagueFlysystemMountManager([ "ftp" => $ftp, "s3" => $s3, ]); // Or mount them later $manager->mountFilesystem("local", $local); // Read from FTP $contents = $manager->read("ftp://some/file.txt"); // And write to local $manager->write("local://put/it/here.txt", $contents); $mountManager->copy("local://some/file.ext", "backup://storage/location.ext"); $mountManager->move("local://some/upload.jpeg", "cdn://users/1/profile-picture.jpeg");
上文已經說了,MountManager使得對各種filesystem的CRUD操作變得更方便了,不管是remote還是local得。MountManager還提供了copy和move操作,只需要加上prefix,就知道被操作文件是屬于哪一個filesystem。并且MountManager提供了copy和move操作,看上面代碼就像是在本地進行copy和move操作似的,毫無違和感。那read和write操作MountManager是沒有定義的,如何理解?很好理解,看__call()魔術方法:
/** * Call forwarder. * * @param string $method * @param array $arguments * * @return mixed */ public function __call($method, $arguments) { list($prefix, $arguments) = $this->filterPrefix($arguments); return $this->invokePluginOnFilesystem($method, $arguments, $prefix); } /** * Retrieve the prefix from an arguments array. * * @param array $arguments * * @return array [:prefix, :arguments] */ public function filterPrefix(array $arguments) { if (empty($arguments)) { throw new LogicException("At least one argument needed"); } $path = array_shift($arguments); if ( ! is_string($path)) { throw new InvalidArgumentException("First argument should be a string"); } if ( ! preg_match("#^.+://.*#", $path)) { throw new InvalidArgumentException("No prefix detected in path: " . $path); } list($prefix, $path) = explode("://", $path, 2); array_unshift($arguments, $path); return [$prefix, $arguments]; } /** * Invoke a plugin on a filesystem mounted on a given prefix. * * @param $method * @param $arguments * @param $prefix * * @return mixed */ public function invokePluginOnFilesystem($method, $arguments, $prefix) { $filesystem = $this->getFilesystem($prefix); try { return $this->invokePlugin($method, $arguments, $filesystem); } catch (PluginNotFoundException $e) { // Let it pass, it"s ok, don"t panic. } $callback = [$filesystem, $method]; return call_user_func_array($callback, $arguments); } /** * Get the filesystem with the corresponding prefix. * * @param string $prefix * * @throws LogicException * * @return FilesystemInterface */ public function getFilesystem($prefix) { if ( ! isset($this->filesystems[$prefix])) { throw new LogicException("No filesystem mounted with prefix " . $prefix); } return $this->filesystems[$prefix]; }
仔細研究__call()魔術方法就知道,$manager->read("ftp://some/file.txt")會把$path切割成"ftp"和"some/file.txt",然后根據"ftp"找到對應的$ftp = new LeagueFlysystemFilesystem($ftpAdapter),然后先從Plugin中去invokePlugin,如果找不到Plugin就觸發PluginNotFoundException并被捕捉,說明read()方法不是Plugin中的,那就調用call_user_func_array([$filesystem, $method], $arguments),等同于調用$ftp->write("some/file.txt")。MountManager設計的也很巧妙。
4. Cache源碼解析最后一個好的技術就是Cache模塊的設計,使用了Decorator Pattern,設計的比較巧妙,這樣只有在需要這個decorator的時候再裝載就行,就如同Laravel中的Middleware一樣。使用Cache模塊需要先裝下league/flysystem-cached-adapter這個dependency:
composer require league/flysystem-cached-adapter
看下CachedAdapter這個類的構造函數:
class CachedAdapter implements AdapterInterface { /** * @var AdapterInterface */ private $adapter; /** * @var CacheInterface */ private $cache; /** * Constructor. * * @param AdapterInterface $adapter * @param CacheInterface $cache */ public function __construct(AdapterInterface $adapter, CacheInterface $cache) { $this->adapter = $adapter; $this->cache = $cache; $this->cache->load(); } }
發現它和FilesystemAdapter實現同一個AdapterInterface接口,并且在構造函數中又需要注入AdapterInterface實例和CacheInterface實例,也就是說Decorator Pattern(裝飾者模式)是這樣實現的:對于一個local filesystem的LocalAdapter(起初是沒有Cache功能的),需要給它裝扮一個Cache模塊,那需要一個裝載類CachedAdapter,該CachedAdapter類得和LocalAdapter實現共同的接口以保證裝載后還是原來的物種(通過實現同一接口),然后把LocalAdapter裝載進去同時還得把需要裝載的裝飾器(這里是一個Cache)同時裝載進去。這樣看來,Decorator Pattern也是一個很巧妙的設計技術,而且也不復雜。看下如何把Cache這個decorator裝載進去CachedAdapter,并最終裝入Filesystem的:
use LeagueFlysystemFilesystem; use LeagueFlysystemAdapterLocal as LocalAdapter; use LeagueFlysystemCachedCachedAdapter; use LeagueFlysystemCachedStoragePredis; // Create the adapter $localAdapter = new LocalAdapter("/path/to/root"); // And use that to create the file system without cache $filesystemWithoutCache = new Filesystem($localAdapter); // Decorate the adapter $cachedAdapter = new CachedAdapter($localAdapter, new Predis); // And use that to create the file system with cache $filesystemWithCache = new Filesystem($cachedAdapter);
Cache模塊也同樣提供了文件的CRUD操作和文件的meta-data的setter/getter操作,但不緩存文件的內容。Cache設計的最巧妙之處還是利用了Decorator Pattern裝載入Filesystem中使用。學會了這一點,對理解Middleware也有好處,以后再聊Middleware的設計思想。
總結:本文主要通過Laravel的Filesystem模塊學習了LeagueFlysystem的源碼,并聊了該package的設計架構和設計技術,以后在使用中就能夠知道它的內部流程,不至于黑箱使用。下次遇到好的技術再聊吧。
歡迎關注Laravel-China。
RightCapital招聘Laravel DevOps
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30428.html
摘要:說明本文主要學習的模塊的源碼邏輯,把自己的一點點研究心得分享出來,希望對別人有所幫助。實際上,使用了的重載學習筆記之重載,通過魔術方法調用里的,而這個實際上就是,該中有方法,可以調用。 說明:本文主要學習Laravel的Filesystem模塊的源碼邏輯,把自己的一點點研究心得分享出來,希望對別人有所幫助。總的來說,Filesystem模塊的源碼也比較簡單,Laravel的Illumi...
摘要:說明本文主要學習容器的實例化過程,主要包括等四個過程。看下的源碼如果是數組,抽取別名并且注冊到中,上文已經討論實際上就是的。 說明:本文主要學習Laravel容器的實例化過程,主要包括Register Base Bindings, Register Base Service Providers , Register Core Container Aliases and Set the ...
摘要:說明本文主要講述了的文件系統的小,邏輯不復雜,主要就是把上的一個文件下載到本地,和下載到中。寫驅動由于沒有驅動,需要自定義下在中寫上名為的驅動同時在注冊下該就行。執行命令后,顯示上文件從上下載到上的文件該邏輯簡單,但很好玩。 說明:本文主要講述了Laravel的文件系統Filesystem的小Demo,邏輯不復雜,主要就是把Dropbox上的一個文件下載到本地local,和下載到AWS...
摘要:總結本文主要學習了啟動時做的七步準備工作環境檢測配置加載日志配置異常處理注冊注冊啟動。 說明:Laravel在把Request通過管道Pipeline送入中間件Middleware和路由Router之前,還做了程序的啟動Bootstrap工作,本文主要學習相關源碼,看看Laravel啟動程序做了哪些具體工作,并將個人的研究心得分享出來,希望對別人有所幫助。Laravel在入口index...
摘要:而函數作用是加載延遲服務,與容器解析關系不大,我們放在以后再說。在構造之前,服務容器會先把放入中,繼而再去解析。利用服務容器解析依賴的參數。 make解析 首先歡迎關注我的博客: www.leoyang90.cn 服務容器對對象的自動解析是服務容器的核心功能,make 函數、build 函數是實例化對象重要的核心,先大致看一下代碼: public function make($abst...
閱讀 2849·2021-08-20 09:37
閱讀 1607·2019-08-30 12:47
閱讀 1090·2019-08-29 13:27
閱讀 1685·2019-08-28 18:02
閱讀 749·2019-08-23 18:15
閱讀 3084·2019-08-23 16:51
閱讀 931·2019-08-23 14:13
閱讀 2125·2019-08-23 13:05