這一章主要講三個方面。php
設計模式、依賴管理和PHP的代碼整潔。html
是 PHP 用來管理依賴(dependency)關係的工具。你能夠在本身的項目中聲明所依賴的外部工具庫(libraries),Composer 會幫你安裝這些依賴的庫文件。前端
其實一門語言的將來,是要靠生態來決定的。而生態呢,又須要制定標準。node
在現代編程語言中,幾乎都是共享庫/包的方式來組合項目的。那麼就須要一個權威的、被承認的標準包管理工具來管理這些包。python
世界上最大的包管理工具是npm,上面託管了幾十萬上百萬個包。linux
每一門主流語言都須要包管理工具,JavaScript和node.js有npm,Java有Maven,Dart有pub,python有pip,go就比較混亂了,有好幾個,目前最主流的應該是govendor。php的呢,就是composer。git
甚至能夠這麼理解,在某種程度上,composer是PHP的將來。github
解決了這個場景:你的項目依賴於若干個庫,其中一些庫依賴於其餘庫。你聲明你的項目所依賴的東西,包管理器會找出哪一個版本的包須要安裝,並將它們下載到你的項目中。golang
windows的安裝至關簡單。下載下來安裝包後,一直點擊下一步便可。npm
下載地址https://getcomposer.org/download/
安裝完成後,使用composer --version
命令來查看是不是否安裝成功。出現版本號即表明安裝成功。
mac或linux的安裝方式參見官方文檔:https://docs.phpcomposer.com/00-intro.html
什麼是鏡像?爲何使用鏡像?
源服務器離咱們很遠,或者咱們國家對相應的域名/地址進行了限制,致使沒法鏈接或者網速很慢。
爲了解決這種現象帶來的問題。不少大公司都在本身的服務器上面搭建了一個和源服務器一摸同樣的環境,再經過CDN和雲存儲來提供服務給咱們下載,而且每隔一段時間同步一下,好比10分鐘,這就是鏡像。
Packagist 中國全量鏡像豆瓣搭建的pip鏡像;阿里搭建的npm鏡像cnpm;github的golang鏡像;google在中國搭建的dart鏡像,它們的目的都是解決以上問題。
Composer的鏡像是由Packagist 中國全量鏡像搭建的。
用法有兩種。
系統全局配置
單項目配置
在命令行終端輸入:
composer config -g repo.packagist composer https://packagist.phpcomposer.com
在項目根目錄下輸入:
composer config repo.packagist composer https://packagist.phpcomposer.com
這條命令會在當前項目的composer.json
文件的末尾自動添加鏡像配置信息。由於咱們尚未項目的配置,因此這裏暫時先不使用這種方式。
經過如下命令解除鏡像,配置會重置爲官方源。
$composer config -g --unset repos.packagist
composer的使用方式和npm極爲類似,由於composer是受到了npm的啓發創造出來的。
經過一個composer.json
文件來聲明依賴關係,格式以下:
{ "require": { "monolog/monolog": "1.2.*" } }
composer.json文件所在的路徑,就被認爲是項目的根路徑,若是你用過npm,這很好理解。
經過install命令安裝項目依賴。
$ composer install
除了使用 install 命令外,咱們也可使用 require 命令快速的安裝一個依賴而不須要手動在 composer.json 裏添加依賴信息:
$ composer require monolog/monolog
# 更新全部依賴
$ composer update
# 更新指定的包
$ composer update monolog/monolog
# 更新指定的多個包
$ composer update monolog/monolog symfony/dependency-injection
# 還能夠經過通配符匹配包
$ composer update monolog/monolog symfony/*
$ composer remove monolog/monolog
$ composer search monolog
# 列出全部已經安裝的包
$ composer show
# 能夠經過通配符進行篩選
$ composer show monolog/*
# 顯示具體某個包的信息
$ composer show monolog/monolog
composer包的版本與npm是一致的。採用語義化版本控制,格式遵循semver 2.0規範。
版本號的格式由X.Y.Z
組成,X爲主版本號,只有更新了不向下兼容的API時進行修改主版本號;Y爲次版本號,當模塊增長了向下兼容的功能時進行修改;Z爲修訂版本號,當模塊進行了向下兼容的bug修改後進行修改。
版本的安裝約束通常有5種。
1.精確鎖定版本
1.1.1
2.範圍版本
使用連字符 - 來指定版本範圍。
有必定的操做符>,>=,<,<=,!=和邏輯操做符||
> = 1.一、>=1.2 <2.0、>=1.2 <2.0 || 3.1.一、1.1.1-2.2.2
3.通配符
1.*
等於 1.0.0 - 2.0.0
4.波浪號
定義最小版本,~1.2
等於>=1.2 <2.0.0
5.脫字符
容許升級版本到安全的版本。
^1.2.3
至關於>=1.2.3 <2.0.0
能夠在這個網站搜索你想要的包:https://packagist.org/
wikipedia上給出的答案是:依賴注入是一種容許咱們從硬編碼的依賴中解耦出來,從而在運行時或者編譯時可以修改的軟件設計模式。
看上去不怎麼好理解。這裏我給出我所理解的什麼是依賴注入?的答案:
依賴注入的本質是按需加載,高度解耦。就這麼簡單。
依賴注入是一種設計模式,遵照依賴倒置原則。
依賴倒置原則主要有三個核心:
高層模塊不該該依賴低層模塊。兩個都應該依賴抽象
抽象不該該依賴細節,細節應該依賴抽象
針對接口編程,不要針對實現編程
由於明天就要進入學習框架的階段了,框架應該學什麼?這個問題很差回答。我我的認爲,學框架,得明白什麼是框架。其實大多數面嚮對象語言的服務端框架都是MVC的模式。裏面會充斥着各類設計模式。而此類框架設計的重中之重,應該是依賴注入。
其實這部分未必可以在實際中用到,若是你對依賴注入比較瞭解,或者不感興趣,你能夠選擇不看這一部分。
明白了上面的概念,咱們來實際體驗一下依賴注入。
假設有以下場景:
小明須要玩王者榮耀,但須要一部手機。
根據這一句話的業務,咱們能夠抽象出,人類,手機,遊戲。
這個業務的本質是什麼?玩王者榮耀。
誰來玩?小明。
前置條件是?須要一部手機。
分析完畢,寫出如下代碼。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Human { public $name; public function __construct($name) { $this->name = $name; } public function play() { $samsung_s10 = new Phone('三星s10'); $wang_zhe_rong_yao = new Game('王者榮耀'); echo $this->name . '用' . $samsung_s10->getInfo() . '玩' . $wang_zhe_rong_yao->getInfo(); } } $ming = new Human('小明'); $ming->play();
這樣就能夠實現了業務。可是若是換了一個手機來玩別的遊戲該怎麼辦呢?好比小明此次要使用iPhone xs來玩消消樂。
若是繼續使用上面這種方式的話,就須要來動態改變Human類中的內容。此時若是咱們將手機和遊戲在Human類的構造中注入進去,就能夠比較好的解決這個問題。
思考完畢,修改代碼。因爲這裏Phone和Game類都屬於Human類一個行爲的依賴,咱們能夠將它們放置到另外一個php文件中,並建立一個名命空間 paly。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Human { public $name; public $phone; public $game; public function __construct($name, Phone $phone, Game $game) { $this->name = $name; $this->phone = $phone; $this->game = $game; } public function play() { echo $this->name . '用' . $this->phone->getInfo() . '玩' . $this->game->getInfo(); } } $iphone_xs = new Phone('iPhone xs'); $xiao_xiao_le = new Game('消消樂'); $ming = new Human('小明', $iphone_xs, $xiao_xiao_le); $ming->play();
這樣解決了這個問題。
但同時又出現了新的問題,將上面的兩個場景合起來, 業務場景就變成了這樣:
小明用三星s10玩王者榮耀,接着用蘋果xs玩消消樂。
若是繼續使用上面這種方式的話,就須要再建立一個Human,傳遞不一樣的參數進去,這樣作並不合適。咱們須要有一個方法來註冊不一樣的手機和遊戲,而且同時只須要一個小明的實例。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Human { public $name; public $phone; public $game; public function __construct($name) { $this->name = $name; } public function setPhone($phone) { $this->phone = $phone; } public function setGame($game) { $this->game = $game; } public function play() { echo $this->name . '用' . $this->phone->getInfo() . '玩' . $this->game->getInfo() . '<br/>'; } } $ming = new Human('小明'); $samsung_s10 = new Phone('三星s10'); $wang_zhe_rong_yao = new Game('王者榮耀'); $ming->setPhone($samsung_s10); $ming->setGame($wang_zhe_rong_yao); $ming->play(); $iphone_xs = new Phone('iPhone xs'); $xiao_xiao_le = new Game('消消樂'); $ming->setPhone($iphone_xs); $ming->setGame($xiao_xiao_le); $ming->play();
接下來,咱們的業務需求再繼續細化,添加了時間和地點。好比:
小明今天中午在教室先用三星s10玩王者榮耀,下午用蘋果xs玩消消樂。
咱們可能會寫出這種代碼:
// 僞代碼。 $ming = new Human('小明'); $samsung_s10 = new Phone('三星s10'); $wang_zhe_rong_yao = new Game('王者榮耀'); $xx_time = new Time('xx'); $xx_location = new Location('xx'); $ming->setPhone($samsung_s10); $ming->setGame($wang_zhe_rong_yao); $ming->setTime($xx_time); $ming->setLoaction($xx_location); $ming->play(); $iphone_xs = new Phone('iPhone xs'); $xiao_xiao_le = new Game('消消樂'); $yy_time = new Time('yy'); $yy_location = new Location('yy'); $ming->setPhone($iphone_xs); $ming->setGame($xiao_xiao_le); $ming->setTime($yy_time); $ming->setLoaction($yy_location); $ming->play();
咱們發現,一旦業務邏輯所依賴的組件過多時,仍然會讓程序難以維護。咱們須要消滅掉那些set。
咱們能夠用工廠函數來對其進行一層封裝。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Time { public $time; public function __construct($time) { $this->time = $time; } public function getInfo() { return $this->time; } } class Location { public $location; public function __construct($location) { $this->location = $location; } public function getInfo() { return $this->location; } } class Human { public $name; public $phone; public $game; public $time; public $location; public function __construct($name, $phone, $game, $time, $location) { $this->name = $name; $this->phone = $phone; $this->game = $game; $this->time = $time; $this->location = $location; } public static function factory() { $name = '小明'; $phone = new Phone('iphone xs'); $game = new Game('王者榮耀'); $time = new Time('上午'); $location = new Location('教室'); return new self($name, $phone, $game, $time, $location); } public function play() { echo $this->name . '' . '' . $this->time->getInfo() . '在' . $this->location->getInfo() . '用' . $this->phone->getInfo() . '玩' . $this->game->getInfo() . '<br/>'; } } $ming = Human::factory(); $ming->play();
但這又回到了最初的問題,在類的內部建立依賴。
咱們須要再次認真思考,到底怎麼樣才能更好的解決這個問題。
咱們能夠嘗試建立一個高級的註冊抽象類,這個抽象類自己也是一個容器。
class DI { private $dep; public function set($key, $val) { array_push($this->dep, $key); $dep[$key] = $val; } public function get($key) { return array_search($this->dep, $key); } }
因爲代碼量太多,影響閱讀,這裏就再也不貼出來了。
咱們將Human類的構造函數參數改爲di,而後Human類中每個依賴都拆分紅一個方法,從di中獲取。
這樣DI容器類就成了一個橋樑,一個全局註冊表,將複雜性隔離出去。代碼耦合更低。
若是不須要某一個依賴,甚至能夠不初始化。
一個完善的依賴注入框架,還會有其餘特性,好比自動綁定、自動解析、註釋解析器、延遲注入等。這些就比較複雜了,須要花時間去理解和探索,咱們到此爲止,瞭解依賴注入是個什麼東西,解決了什麼事就能夠了。
我寫的示例代碼有些重度設計,可是實際業務中的代碼不會這麼簡單,手機內部確定會包含不少屬性和方法,如電量、溫度、信號等。遊戲也會包含一些屬性和方法,如加載、更新、版本、高性能模式、FPS、延遲等。但這些都不是重點,我這麼作的最終目的是讓咱們便於理解。
如今咱們明白了,使用依賴注入解決咱們的問題。不是在代碼內部建立依賴關係,而是讓其做爲一個參數傳遞,這使得咱們的程序更容易維護,下降程序代碼的耦合度,實現一種鬆耦合。
做爲PHP語言自己內容的最後一部分,我認爲應該就是學一下代碼整潔之道了,代碼的整潔程度,直接影響了項目的可讀性和可維護性。要知道,除了極少數的極端項目之外,代碼的可讀性和可維護性是比性能還要重要的部分。
好的代碼不止是計器能讀懂,還得讓人也讀懂。
這裏簡單說一下幾個注意點:
名命有意義。
同種類變量使用同一個名字。
使用易於搜索的變量名。
明確的名字比隱晦的名字更好。
不要添加不必的上下文。
參數保持簡潔(2個之內),參數超過2個使用對象或者數組。
函數保持簡潔,一個函數只作一件事。
函數只進行一層抽象,當超過一層抽象時,繼續拆分功能。達到最高的可重用性和易用性。
刪除重複代碼。
經過對象賦值,設置默認值。
避免函數反作用。這個很重要,可以大大下降程序出現BUG的概率。
避免寫全局函數。這樣作的好處是防止污染命名空間。
封裝條件語句。把判斷寫成函數。
避免消極條件。就是不要使用!來作判斷。
避免條件聲明。不要使用if。使用switch和case,一個函數只作一件事。
避免類型檢查。不要給PHP代碼寫類型檢測,作不到就是用類型聲明。
移除殭屍代碼。好比聲明後從未調用的函數。
這一部分的內容,不少語言都是互通的,我也不給每一條寫例子了,若是看了以上內容後仍有疑惑,你能夠打開如下連接進行自學。裏面包含了大量的對比示例。
https://github.com/yangweijie/clean-code-php
這一章經過學習依賴管理器Composer,瞭解了PHP項目是如何構建和組成的。以後學些了依賴注入,並將概念落實到代碼上,對理解框架的設計原理會有所幫助。最後經過學習代碼整潔之道,避免咱們寫出質量不好的代碼。
這一章爲後續學習框架的使用及理解框架背後的運行原理提供了幫助。