# 第 2 章 選擇高起點
現在起,從微觀角度,我們將會著手開始搭建一個小網站;從宏觀角度,我們將踏入軟件開發這個領域。在這條路上,我們會遇到很多之前未接觸過的技術、概念、工具和框架等。在迎來自己參與搭建的千萬級高并發大型網站系統前,我們需要做好承受各種問題和壓力的心理準備,醞釀持續學習的心態,并有意識地不斷投入時間和精力。
好的開始,是成功的一半。如果你是一個初學者,或者是剛入門的新手,我建議是在一開始就選擇一個高起點。也許有人會問,選擇低起點,選擇入門成本低、學習曲線平緩的路徑不好嗎?這樣可以快速上手。沒錯,確實是這樣,但這只是短期的收益。我們還要從長期的發展來考慮。
如果僅是作為對技術的簡單嘗鮮,以及日后的目標也只是停留在普通的網站維護和開發時,那么低起點是可以的。但是,如果你也和我一樣,致力成為專業的軟件開發工程師,致力于盡我們最大的努力,開發并交付價值最大化的軟件的話,那么你應該選擇一個高起點。對于類似Windows圖形化界面和集成開發環境XAMPP,正因為它的簡單性、易用性,使得我們容易產生依賴、錯覺和習慣。
如果解決方案都是別人現成提供的,只會安裝使用,那么最多只能算是一名使用者。一旦出了問題就會措手不及、不知所措,因為從沒親自深入解決過遇到的問題。而專家都是能親自解決各種難題的人,并且專家也會經常幫助他人解決問題。錯覺則是指這些大眾化的軟件容易讓人忽略背后的原理、底層實現、數據結構和算法等冰山以下的內容,覺得軟件開發是那么簡單,殊不知背后還蘊藏著如此龐大的知識體系。有時候,不知道自己不知道是危險的。參加馬拉松長跑比賽和百米沖刺賽跑的運動員,都會有意識在比賽前不斷地鍛煉,他們會選擇與之類似的運動難度而不只是在公園內散散步就企圖希望能獲得好成績。同樣,在軟件開發中,我們也要時刻注意要鍛煉我們的思維能力,有意識地訓練我們的左右腦。當然不能只是通過簡單使用一鍵安裝式的軟件來刺激我們的大腦,而是要深入細節,深入底層,深入過程,有意識地去學習、掌握和接觸大量的技術、知識和信息。
由儉入奢易,由奢入儉難。類似地,大腦從高度集中、活躍狀態到放松狀態容易,而從放松狀態進入活躍狀態很難。特別當一個已經長期都習慣放松平衡的狀態時,更難。還記得嗎?軟件開發是一項需要高智力的腦力開發活動。你的思維方式決定了你所編寫的代碼,也決定了今后做事的方式——不管高效與否。
今天的選擇,會在潛移默化下無意識地影響我們今后幾年內工作的方式,也會間接制約我們后面所能取得的成就和成果。通俗來說,往往今天的豐富收獲,并不在于我們今天做了什么,或者是最近幾天做了什么,而在于多年前做了什么,以及這么多年以來我們一直做了什么。
假設多年后的你穿越回到當前,如果你也已經意識到了一個高起點對于專業軟件開發工程師的重要性,那么就讓我們來開始新的選擇吧——選擇一個高起點!下面,將會介紹怎樣選擇操作系統、開發環境和框架。即便你是有經驗的開發人員,相信也可以從中獲益。
## 2.1 服務器系統首選Linux
一定要抵制住對Windows服務器系統的誘惑,要抵制住對GUI圖形化界面的誘惑。
這里,并不是說Windows系統不好,也不是說CLI就一定要比GUI好。像高考填志愿一樣,沒有最好,只有更合適。后面我們將學習的網站開發所取用的技術大部分都是開源的,PHP開發語言是開源的,MySQL數據庫是開源的,Nginx服務器是開源的,鑒于此,我覺得LNMP“黃金組合”再搭配開源的Linux操作系統是非常合理的,而不是Windows商業系統。除此之外,我們在后續搭建企業級網站系統過程中還會用到很多開源的軟件、開源的框架和開源的類庫,這是一個龐大的開源生態圈。如果硬要在這個開源生態圈內注入一個商業系統,會顯得格格不入,也會對文化、技術、架構等產生一定副作用。當然,并不是說Windows系統不好,Windows系統很強大,但在這里適合選擇Linux。
另一方面,作為一名專業人士,很有必要熟練掌握命令行終端的操作,也就是對各種shell命令的使用。通過使用命令,我們可以將任務腳本化,即在一個腳本內使用多條命令就可以完成一個任務的操作。這樣可以節省每次手動重復操作的時間。更重要的是,腳本化代表的是一種自動化處理的能力,即可以完全脫離人工干預而進行的操作。試想一下,這是對寶貴的人力資源多大的解脫!舉個例子,我們需要為每個小區的每位業主發送月度物業費郵件電子賬單,假設業主有1000人。如果是人工來操作的話,需要登錄郵箱然后分別編寫郵件,找到對應的收件人,然后核實發送,這個過程顯然時間漫長并且容易出錯。如果換種方式,將這些操作全部都使用命令腳本來實現的話,只需要每個月,花一分鐘執行這個命令即可。人力成本大為降低的背后就是效率極大的提升。縱使是這樣,如果負責發送郵件通知的那個人,因為忘記或者剛好那天是國家節假日而沒發郵件的話,就會產生差錯。這時,我們可以將此腳本任務通過Linux的crontab定時任務固化下來。這樣,完全不需要任何人干預,電腦就會自動并且非常負責任地幫我們完成任務啦!
千萬不要因為Linux的高深莫測就打退堂鼓。Linux本身并不復雜,很多人,特別是初學者覺得它很難。這只是因為Linux設計時面向的人群就不是普通的電腦使用者,而是面向特定領域的專業人士,尤其是面向技術類專業人士。這一過程的轉換之所以難,是因為對我們多年來使用操作系統習慣和思維的轉換,才會覺得很難。Linux沒有提供GUI圖形界面,在上面我們不能使用鼠標左右移動,沒有良好的人機交互體驗。要想在我們的大腦和Linux操作系統之間進行通訊,我們只能使用鍵盤,敲下指令,回車,等待終端的輸出反饋。
這里有一個悖論,看似這樣的人機操作,限制了我們的操作速度,限制了我們的可視范圍,但恰恰是這樣的CLI終端,為我們打開了更廣闊的知識大門,打開了思維上的新天地。Linux秉持著簡單之美的原則,在屏幕上輸出的信息全部都是非常有用的。如果能在一屏內顯示完整,決不會給你顯示兩屏。對于那些非關鍵性、可顯示也可不顯示的信息,則通常都不會顯示。如果說我們是在和高效的命令行打交道,還不如說是我們正在和開發提供這些命令行的專家在對話,與這些來自世界各地大師級的專家在“溝通”,我們收獲的不僅是更高效完成當前任務,從長遠角度上看,收獲的是向專家級權威人士靠攏的思維方式和見解。當哪天,你能看懂頂級專家編寫的代碼和論著時,你離專家也就不遠了。
但是學習Linux這個過程,特別是剛開始的時候,可以說是步履維艱的。這點我深有體會。我也是剛畢業不久才正式學習和使用Linux操作系統的,當時連一個tar解壓打包的命令都要保存下來,每次執行時都要找出來貼上去,然后改改參數再執行,因為對shell命令很陌生,完全記不住。而現在,我已經可以熟練地掌握常用命令的操作,并且對于未曾接觸過的命令,也可以快速通過man命令來查看幫助。
選擇Linux操作系統,還有一個很大的現實原因就是:當畢業后,你會發現,你所就職的公司,基本上絕大部分的網站系統都是運行在Linux操作系統之上。誰更懂Linux,誰就能更容易解決將要發生的問題,并且更能勝任隨時到來的挑戰。
服務器系統首選Linux,這是我對全部致力于成為專業開發人士的建議之一。
## 2.2 自已動手搭建LNMP環境
確定了操作系統后,接下來,自然就到了LNMP環境。LNMP是中小型公司,乃至大型公司都會選擇的方案。因為這個“黃金組合”成熟穩定,并且熟悉的人多。前面也有簡單介紹,這里再稍微羅列下:
+ L表示Linux系列的操作系統,在本書中我們使用的是CentOS。
+ N表示Nginx,它可以實現均衡負載、反向代理、讀寫分離、URL重寫等一系列功能。
+ P表示PHP,即網站開發語言,一種解釋性腳本編程語言。
+ M表示MySQL,即開源的關系型數據庫,可用于持久化存儲數據。
與其他的教程不同,我不建議使用一鍵式集成開發環境,哪怕你現在是一個小白,我也會建議你一開始就自己動手搭建LNMP環境。
剛開始時,有可能你需要花費幾天的時間,才能把整個開發環境搭建起來。但是沒關系,不要急,也不要浮躁。要沉下心來,如果現在遇到這點難題就放棄的話,那么我們將會與千萬級訪問量的大型系統失之交臂。設想一下,如果你日后能在短時間內(例如幾分鐘內)找出使網站系統崩潰的原因并恢復正常,你需要具備哪些能力?你又需要為之做些什么呢?沒錯!就是當下,要把這個LNMP環境拿下。
就像醫生診斷病人一樣,如果他連人體的構造,各器官的位置和作用都不了解的話,他是不可能找得出病理的,更別說開藥治病。病人也不會放心把自己的健康甚至生命托付給一無所知的醫生。同樣的道理,如果我們連LNMP都不了解,公司又怎能放心把網站系統托付給我們來維護和管理呢?這里說的了解,不是指表面上知其然,會使用,還要知其所以然,清楚涉及的安裝、配置、優化、錯誤處理、實現原理等。能在今后和其他人進行技術溝通時,做到一問一答,舉一反三。而不是一問三不知。
關于LNMP開發環境的搭建,網上已經有很多教程,大家可以根據自己的情況,結合當前使用的環境進行搭建,這里不再贅述。但我接下來會講一些非常重要的關鍵點,講一些不一樣的內容。這些環節通常會容易被人遺忘或忽略,但卻又是不可輕視的。至少從很多前來應聘的初級、中級開發工程師的回答上可以看出,他們缺少對這一塊的認識。
### 2.2.1 Nginx的站點配置在哪?
Nginx的配置,在不同的系統上,因為安裝方式不同,所存放的位置也不相同。我們先來簡單看下Nginx的配置有哪些,再來看下可以通過什么途徑找到這些配置的位置。最后看下配置Nginx的原則。
#### CentOS上的配置
默認情況下,Nginx的默認配置目錄是在:/etc/nginx/,并且在這目錄下會有一個nginx.conf的默認配置文件,也就是Nginx的配置文件。這里面有比較多重要、關鍵性的配置。其中,需要注意的是,如果在這份配置里搜索“include”,你會找到類似這樣的配置:
```
http {
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
}
}
```
這里有兩處include配置。在http內但在server外的inclue的這行配置指明了,Nginx會從加載/etc/nginx/conf.d/目錄下全部以conf為文件擴展名的配置文件。換言之,在此目錄下正確配置的站點,都能通過Nginx來訪問。根據這條線索和技巧,你就能快速找到Nginx的站點配置在哪。
#### Ubuntu上的配置
因為有時候,站點的配置不一定“藏”在這里。例如在Ubuntu上,就傾向放在/etc/nginx/sites-enabled/這個目錄下。在Ubuntu系統上,Nginx.conf的配置會這樣:
```
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
```
它除了加載 /etc/nginx/conf.d/目錄下,全部以.conf為后綴的配置文件外,還會加載/etc/nginx/sites-enabled/下全部的配置文件,注意,這時不管文件是什么后綴都會加載。似乎技術開發人員故意要為難技術開發人員,另外還有一個/etc/nginx/sites-available/目錄。這個目錄又是什么呢?原來這里才是放置Nginx真身配置的地方,在sites-enabled那個目錄放置的是配置的“替身”,即指向/etc/nginx/sites-available/這個目錄的軟鏈接。舉一個例子就很容易明白了。假設有一個配置文件:
```
/etc/nginx/sites-available/www.test.com
```
需要再添加軟鏈接:
```
ln -s /etc/nginx/sites-available/www.test.com /etc/nginx/sites-enabled/www.test.com
```
然后重啟Nginx才能訪問www.test.com這個站點。
#### 編譯安裝方式的配置
有時Nginx又比較調皮。它默認的配置文件nginx.conf可能是在/usr/local/nginx/conf/nginx.conf。如果采用的是下載Nginx源代碼再手動編譯安裝的話,Nginx目錄位置根據技術人員在安裝時指定的位置不同而不同。可以在Nginx執行configure時指定,如:
```
./configure --prefix=/usr/local/nginx
```
在此nginx.conf配置文件里面,可能會看到:
```
include ./vhosts/*.conf;
```
則表示,此時Nginx的站點配置文件在/usr/local/nginx/conf/vhosts/這個目錄下,并且需要以.conf為后綴。
Nginx的配置位置,可能還會有很多情況。但如果大家掌握了技巧,不管它在哪,我們都能快速找出來。這背后,只需要對include配置和文件路徑的一點了解就可以了。要注意是絕對路徑,還是相對路徑;同時要注意是全部文件后綴,還是指定了.conf文件后綴。下面通過示例再稍微小結一下。
```
# 絕對路徑,僅限.conf后綴
include /etc/nginx/default.d/*.conf;
# 絕對路徑,不限后綴
include /etc/nginx/sites-enabled/*;
# 相對路徑,僅限.conf后綴
include ./vhosts/*.conf;
# 相對路徑,不限后綴
include ./vhosts/*;
```
> 專家技巧:根據nginx.conf文件里面的include配置項,可以快速找到Nginx站點的配置目錄。
#### Nginx配置的原則
在開始配置你的站點前,注意不要把配置追加在/etc/nginx/nginx.conf默認文件里,也不要把多個站點都配置在/etc/nginx/conf.d/下的同一份配置文件里。而是應該一個網站,一份Nginx配置,并且以站點的名稱作為文件名前綴。例如參考以下這樣的配置:
```
/etc/nginx/conf.d/www.examples.com.conf
/etc/nginx/conf.d/docs.examples.com.conf
/etc/nginx/conf.d/api.examples.com.conf
```
上面配置了三個站點,分別是:www.examples.com、docs.examples.com和api.examples.com。每個站點一份Nginx配置,以后維護更方便,也更簡單。
> 專家原則:一個網站,一份Nginx配置,并以站點域名命名。
Nginx還有很多強大的配置,我們會在后面學習過程中,不斷學習更多高級的技能。這時,我們先再來關注下,Nginx是如何和PHP-FPM通信的,也就是Nginx是怎樣執行PHP文件的?
### 2.2.2 Nginx如何與PHP-FPM通信?
知道Nginx站點配置在哪后,下一步需要了解每份Nginx配置里面都有哪些內容?Nginx的配置有很多,剛開始時,我們只需要關注以下幾項目配置。我把這稱為最小化可用配置,即使網站運行起來的最基本的配置。
+ **listen** 偵聽的端口,默認是80,可省略。
+ **server_name** 網站域名,多個域名使用空格分開。
+ **index** 默認訪問的首頁索引訪問文件,一般對于靜態網站是index.html,對于PHP則是index.php,對于C#則是index.asp,依此類推。
+ **root** 網站訪問的根路徑,通常我建議在項目內建立一個單獨的public目錄給外部訪問,這樣更安全,也更容易管理。
+ **location** 對PHP請求的轉發和處理。Nginx本身不執行PHP文件,而是將對應的PHP請求通過FastCgi交由php-fpm進程來處理。
下面來看下對應的示例配置。以剛才的/etc/nginx/conf.d/www.examples.com.conf配置為例,在里面配置以下內容。
```
server
{
listen 80;
server_name www.examples.com;
index index.html index.php;
root /path/to/www.examples.com/public;
location ~ .*\.(php|php5)?$
{
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```
可以看到,這里偵聽的是80端口,網站域名為www.examples.com。默認首頁為index.html和index.php,網站根目錄為指向www.examples.com目錄下的public子目錄。最后,則是關鍵的部分,Nginx進程與PHP-FPM進程之間如何通訊的配置。這里傳遞的方式是通過socket來通訊的:
```
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
```
但在Linux操作系統上,兩個不同的進程之間的通訊,還可以通過端口,這就是為什么有時候會這樣配置:
```
fastcgi_pass 127.0.0.1:9000;
```
當你看到的是不同的配置時,不要困惑,一旦你了解了進程之間的通訊手段,就很容易明白了。
清楚這一點有什么用處呢?當你清楚整個請求背后的大概處理流程后,當發生問題或者故障時,就可以快速定位排查啦。
#### 一個比喻,了解Nginx的執行流程
我們可以來找個比喻,你就能很容易明白上面Nginx的配置以及其執行流程了。
假設,我們開了一張新餐館。把新店的位置以及餐館正門告訴市場后,就會陸續迎來我們尊貴的客人。而對于網站,則是通過域名和端口來告訴互聯網,告訴廣大沖浪者。例如我們的www.examples.com域名和80端口。
```
listen 80;
server_name www.examples.com;
```
客人來了之前,他會拿起菜牌,挑選喜歡的菜式,然后向服務員下單。對于網站,網站訪客會瀏覽頁面上的內容和信息,然后點擊頁面鏈接進入他們感興趣的頁面。也就是說有人發起了請求,訪問了我們網站根目錄下的某些文件或頁面。
```
index index.html index.php;
root /path/to/www.examples.com/public;
```
但是服務員本人不會下廚,服務員只會把客人要點的菜交由廚師來處理,然后等待廚師完成后,再把美食呈現給客人。Nginx和這個處理方式是類似的,Nginx本身不執行PHP文件,而是把PHP的請求通過socket或者IP和端口轉發給php-fpm進程來處理。最后,等待php-fpm處理完成返回結果后,再中轉返回給客戶端。這就是為什么我們會看到:
```
location ~ .*\.(php|php5)?$
{
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
```
一旦明白了解這個過程,后面遇到任何問題,都不必驚慌。利用已經掌握的知識,稍微分析下,你就能很快找到原因了。下面我們會來看下可能會遇到哪些問題。
### 2.2.3 HOST綁定與網站訪問
現在,我們可以開始簡單測試網站的訪問了。
首先,我們先創建一個簡單的首頁。繼續前面的例子,先在www.examples.com/public目錄下,添加index.php文件,并放置以下代碼。
```php
// $ vim ./index.php
<?php
echo "<h1>Hello Wolrd!</h1>";
```
在本地開發時,我們通常需要綁定HOST后,才能訪問自己的網站。因為這時網站還沒發布,還只是在局域網內,并且公網的域名也還沒進行公網DNS解析。綁定HOST的配置很簡單,假設網站服務器的IP是192.168.1.2,那么對應的host配置是:
```
# www.examples.com的本地開發環境
192.168.1.2 www.examples.com
```
但問題是,對于Windows、Linux和Mac操作系統,配置這個host的地方又有點不同。對于Winonws,通常位于C:\Windows\System32\drivers\etc\hosts;對于Linux和Mac,通常是保存在/etc/hosts文件。
添加保存后,這里使用瀏覽器就能這樣訪問啦!

圖2-1 搭建Nginx和PHP后的示例首頁訪問效果
是不是很簡單?也許,但現實并不總是那么順利的。在搭建過程中,你有可能會遇到各種問題。
### 2.2.4 關注錯誤日志
以下是你有可能遇到的問題,這里的目的并不是為了羅列出全部的問題和解決方案,而是為了分享在面對問題時,如何快速定位問題。從而哪怕是新手,也能做到有章可循,有條不紊地解決問題。
首先,我們先來了解一件法寶。這件法寶就是錯誤日志。例如Nginx的錯誤日志和php-fpm的錯誤日志。如前面的示例中,可以這樣追加Nginx的錯誤日志配置:
```
server
{
server_name www.examples.com;
# 略……
error_log /var/log/nginx/www.examples.com.error_log;
}
```
對于php-fpm,可以在php.ini配置文件中修改error_log選項,指定錯誤日志的文件路徑。如下:
```
error_log = /var/log/php5-fpm.log
```
記得,在配置完上面內容后,都需要重啟nginx或者php-fpm。
> 專家技巧:遇到問題時,要首先關注查看錯誤日志。
好了,準備妥當后,讓我們看下將可能會遇到哪些問題,又將是如何進行分析、排查和解決。
#### 502 Bad Gateway 錯誤
不管出現什么錯誤,首先看下Nginx的錯誤日志,可以看到當出現“502 Bad Gateway”錯誤時,有類似如下的錯誤日志:
```
2018/03/10 15:18:46 [error] 21426#0: *16 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: www.examples.com, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "www.examples.com"
```

圖2-2 502 Bad Gateway錯誤
Nginx的日志都是有一定格式的,從中不難發現提示了這樣的錯誤:
```
connect() failed (111: Connection refused) while connecting to upstream
```
這里是指連接失敗,即Nginx與php-fpm通訊失敗。如果nginx配置沒錯的話,可檢查php-fpm是否已啟動。這類簡單的問題,往往是困擾新手的大問題。就好像很多維修人員上門去解決電腦無法開機時,原來是因為未插電源。
啟動php-fpm后,應該就能正常訪問了。
#### 404 File not found 錯誤
注意,前面是說應該能正常訪問,而不是一定能。因為還可能會有別的問題。例如常見的404 File not found。導致404的原因有很多種,那種明顯文件不存在,或者配置錯誤的原因就不說了。我這里分享另外一個會導致404,卻很隱晦的原因。即便是有經驗的人,也可能會忽略。
同樣,出錯404錯誤時,先來看下錯誤日志信息。會看到類似這樣的提示:
```
2018/03/10 15:23:18 [crit] 21426#0: *19 stat() "/path/to/www.examples.com/public/" failed (13: Permission denied), client: 127.0.0.1, server: www.examples.com, request: "GET / HTTP/1.1", host: "www.examples.com"
```
這里看到是權限不足。
對比查看下我們的PHP源代碼的文件權限,檢測后發現也是有執行權限的。那為什么還是沒執行權限呢?
```
ll ./www.examples.com/public/index.php
-rwxrwxr-x 1 dogstar dogstar 36 Mar 10 14:56 ./www.examples.com/public/index.php*
```
這是因為不僅需要PHP文件本身要有執行權限,還要求它的當前目錄,全部父級目錄也需要有執行權限。假設index.php文件的存儲路徑為/home/dogstar/projects/www.examples.com/public/index.php,那就要逐一檢測各個目錄的執行權限。
通過排查,可以發現dogstar這個用戶的目錄沒有執行權限。而通常在Linux創建新賬號時,其賬號對應的目錄都是沒有執行權限的,這點需要注意。
```
$ ll /home
drwxr--r-- 36 dogstar dogstar 4096 Mar 10 14:56 dogstar/
```
解決的方法很簡單,只需要追加執行權限即可,例如:
```
$ chmod +x /home/dogstar
```
再重新頁面重新訪問,就能正常訪問首頁了。如果還不行,再逐級排查。
#### 500 Internal Server Error 錯誤
可以說,500錯誤是我們日后經常會遇到的錯誤。如果php-fpm已經啟動,并且也具備全部執行權限時,若出現500錯誤時,通常都是PHP本身的問題,這樣可以方便我們進一步收攏排查的范圍。先來看下Nginx的錯誤日志。
```
2018/03/10 15:41:04 [error] 21429#0: *35 FastCGI sent in stderr: "PHP message: PHP Parse error: syntax error, unexpected $end, expecting ',' or ';' in /path/to/www.examples.com/public/index.php on line 4" ……
```
這表明是PHP語法有錯誤。查看下對應的PHP源代碼,原來是剛才我們新加的代碼有問題。
```php
<?php
echo "<h1>Hello World!</h1>";
echo "by dogstar"
```
最后一行代碼少了分號,補上即可正常訪問。
### 2.2.5 PHP的兩份配置
知道編寫PHP代碼的程序員不少,但知道PHP有幾種運行模式的人并不多。
如果在網上搜索一下,可以找到例如這5種運行模式:
+ CGI(Common Gateway Interface),通用網關接口
+ FastCGI(Long-Live CGI),常駐型CGI
+ CLI(Command Line Interface),命令行運行模式
+ Web模塊模式,Apache等Web服務器運行的模式
+ ISAPI(Internet Server Application Program Interface)
不同的運行模式,是有所差異的。而對我們技術開發人員來說,主要在以下這兩方面。
#### 配置上的差異
以常用的CGI和CLI這種運行模式為例,php-fpm使用的配置文件很有可能和命令行終端使用的配置文件是不同的。這也為什么,我們安裝了一個PHP擴展,明明在瀏覽器訪問是可以,但通過命令行來訪問卻是失敗的。又或者反過來,命令行終端正常,在瀏覽端訪問時卻失敗了。
#### 如何找到php-fpm和命令終端用的是哪份配置?
要想快速找到命令終端用的是哪份配置,非常簡單,使用以下命令即可:
```
$ php --ini
Configuration File (php.ini) Path: /etc/php5/cli
Loaded Configuration File: /etc/php5/cli/php.ini
Scan for additional .ini files in: /etc/php5/cli/conf.d
Additional .ini files parsed: /etc/php5/cli/conf.d/curl.ini,
/etc/php5/cli/conf.d/gd.ini,
/etc/php5/cli/conf.d/mcrypt.ini,
……
```
通過這個php --ini這個命令,它會告訴你當前使用的是哪份配置。例如這里的是/etc/php5/cli/php.ini。
對于php-fpm用的是哪份配置,有兩種方式,你可以繼續使用命令行,如:
```
$ ps -ef | grep php-fpm
root 22190 1 0 15:46 ? 00:00:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)
```
通過查看進程的信息,可以看到用的是哪份php-fpm.conf,然后在同目錄下找到對應的php.ini。
另外一種方式,是更常見也是更可靠的做法。就是通過phpinfo()函數來查看配置。例如添加phpinfo.php文件,并寫入:
```
// www.examples.com$ vim ./public/phpinfo.php
<?php
phpinfo();
```
然后保存,訪問,就可以看到類似這樣:

圖2-3 phpinfo頁面信息
從中可以找到php.ini的位置是在/etc/php5/fpm/php.ini。
如果php-fpm和命令行模式使用的不是同一份配置,會容易產生不一致的問題。在這里,你可以創建軟鏈,使一方指向另一方。當然,你也可以繼續手動維護兩份配置。
> 專家技巧:為降低維護成本,將php-fpm和命令行模式的php.ini配置文件指向同一份文件。
### 2.2.6 數據庫環境配置的方式
最后我們簡單來說一下,如何集成MySQL數據庫,也就是LNMP黃金組合的最后一部分。
首先,千萬不要把數據庫賬號和密碼寫死在代碼里。這是初級項目,個人項目,學校項目經常會做的事。但我們應當養成開發商業級項目的習慣,始終堅持高標準,高要求,盡量與日后線上商業系統的做法保持一致。
更好的方式,是采用配置方式來配置數據庫的賬號、密碼、用戶名等重要信息。一來避免敏感信息的泄露,二來方便不同環境下開發、測試、部署的便利。
考慮以下最初的MySQL數據庫配置:
```
// www.examples.com$ vim ./config/db.php
<?php
return array(
'name' => 'examples', // 數據庫名字
'host' => 'localhost', // 數據庫域名
'user' => 'root', // 數據庫用戶名
'password' => '123456', // 數據庫密碼
'port' => 3306, // 數據庫端口
);
```
這里采用的是硬編碼的方式。這是常用、簡單但也是有一定弊端的做法。
更好的方式,是通過環境變更來進行配置。這樣會更靈活,也更容易進行管控。例如改為:
```
// www.examples.com$ vim ./config/db.php
<?php
return array(
'name' => $_ENV['DB_NAME'], // 數據庫名字
'host' => $_ENV['DB_HOST'], // 數據庫域名
'user' => $_ENV['DB_USER'], // 數據庫用戶名
'password' => $_ENV['DB_PASS'], // 數據庫密碼
'port' => $_ENV['DB_PORT'], // 數據庫端口
);
```
不過,好的做法都需要一點成本。這里的成本就是需要我們額外添加對應的數據庫環境變量配置。打開/etc/php5/fpm/pool.d/www.conf配置文件,然后同步追加以下配置:
```
env[DB_NAME]='examples'
env[DB_HOST]='localhost'
env[DB_USER]='root'
env[DB_PASS]='123456'
env[DB_PORT]=3306
```
然后,保存并重啟php-fpm。你會發現,以上的配置不一定能正常讀取。這是因為php-fpm默認是沒有開啟ENV環境變量的。每一次,我們都可以從新的問題獲得新的知識。這時,可以修改/etc/php5/fpm/php.ini文件,并修改variables_orders配置項在前面追加表示ENV的字母E,從原來的GPCS變成EGPCS。如:
```
variables_order = "EGPCS"
```
然后,再次重啟php-fpm。這時通過瀏覽器就能正常獲取到上面配置的數據庫信息了。
但是(生活里總有很多但是),CLI命令行模式呢?是否也能正常獲取ENV環境變量?還記得我們前面說過嗎?CLI不一定共用php-fpm的配置,因此這時你很可能還要同步修改/etc/php5/cli/php.ini文件里面的variables_orders配置項。
好了,但是(又一個但是,希望讀者可以忍住罵我的聲音),CLI模式下讀取到ENV配置為空又是什么原因呢?這是因為,命令行方式下讀取的環境變量來自/etc/environment文件。這時,需要切換到root用戶權限,并追加以下配置,注意格式有所差異。
```
# vim /etc/environment
export DB_NAME='examples'
export DB_HOST='localhost'
export DB_USER='root'
export DB_PASS='123456'
export DB_PORT=3306
```
因為是Linux的環境變量,所以需要export來導入。
通過環境變量而不是hard code的方式,我們就可以在不同的環境進行部署,而不需要來回修改代碼中的配置。只需要修改不同環境上的數據庫配置,就可以實現不同環境下的數據庫連接了。
> 專家技巧:通過環境變量而不是硬編碼的方式,進行數據庫連接信息的配置。
這時,總算可以告一段落了。休息一下,我們稍候繼續!
## 2.3 項目分類
在前面做了那么準備工作后,接下來再來簡單認識下我們通常需要開發哪些類型的網站項目。因為不同的項目,其做法和要求也不盡相同,它的關注點和所遇到的問題也會有所區別。根據實際的開發經驗,在商業中,一般會接觸到以下這幾類項目:前臺網站系統、后臺管理系統、計劃任務系統、接口服務系統。下面分別簡單介紹對比。
### 2.3.1 前臺網站系統
面向最終消費者用戶人群,對用戶體驗和人機交互要求高。為了響應市場的快速變化,需要頻繁進行版本迭代和更新。與此同時,伴隨著網站的不斷成長,注冊人數的攀升,對系統的穩定性、吞吐量、高并發、安全性等非功能性要求會越來越苛刻。因此所面臨的挑戰和壓力都是非常巨大的。如果你有機會參與此類項目系統的開發,將能得到很好的歷練。
### 2.3.2 后臺管理系統
主要面向特定內部人群,小眾群體。例如提供給公司內部的運營人員使用的運營平臺,開放給第三方供應商使用的倉庫管理平臺,企業使用的ERP管理系統等,提供給技術人員使用的監控平臺系統。這類系統,主要更關注的是數據的準確性,以及數據流和對應的工作流程,需要進行不同角色下的權限控制。能夠容忍一定的延遲,并且對UI設計要求不高。重點在于能很好幫助人們進行高效地工作,有效地進行對系統的管理和對數據的維護。
### 2.3.3 接口服務系統
當業務逐漸擴張時,系統也會從原來的單個網站,慢慢發展成具備由多個子系統、多種異構系統共同構建而成的企業級系統生態圈。這時,接口服務系統會應運而生。面向的服務對象是前面的前臺網站系統、后臺管理系統,以及其他接口服務系統,甚至是將要講到的計劃任務系統。主要是為其客戶端提供可用的接口服務,以完成特定的功能或服務,實現對數據的處理、查詢、存儲和加工、校驗等。本質上為了讓數據和信息能在各系統之間更有效地流轉,而這些系統的邊界可能是小到只是公司內部的通訊,也可能會大到跨國跨企業跨行業之間的通訊。接口服務系統充當著不可或缺的角色,為多終端設備提供了統一的接口服務,但是一旦出現問題,影響面將會是巨大的。因此需要做好降級、容災、監控等一系列事宜。
### 2.3.4 計劃任務系統
最后要介紹的是計劃任務系統。這是一類神秘的系統,前臺系統和管理系統都能通過網頁的界面進行訪問和操作,接口系統也起碼可以有返回和輸出。但是計劃任務系統是最神秘的系統,因為基本上它是看不見,摸不著的。它會在背后默默幫你處理完成全部任務、事務和通知等工作。似乎看起來它可以幫我們做很多事情,但由于其看不見的特性,往往難以調試,一旦出問題也難以重現和排查。而一旦發生故障,其影響面又是深遠的,因為它會對數據進行錯誤的處理而產生臟數據,使整個系統陷入數據紊亂狀態。更讓人擔憂的是,系統會基于這些臟數據產生二次臟數據,如此循環,不斷惡化。所以,對這類系統的開發一定不能掉以輕心。
最后,簡單對比一下這四類項目,便于在日后進行項目開發時有點針對性,并做出相應的決策和管理措施。
表2-1 四種項目的對比
項目類型 |面向的人群 | 圖形化界面 | 特點
---|---|---|---
前臺網站系統|最終用戶,終端消費者| 簡潔或者設計標準極高的網站頁面|關注用戶體驗,對功能性需求和非功能性需求要求都很高
后臺管理系統|內部人群,小眾人群|可以接受水平一般的UI設計和延時|重點在于能很好幫助人們進行高效地工作,有效地進行對系統的管理和對數據的維護
接口服務系統|客戶端是其他系統,而不是人|不需要界面|通過接口服務對外提供數據或服務,為多終端設備提供了統一的接口服務,穩定性要求高
計劃任務系統|通常沒有服務的對象,自已完成后臺異步計劃任務|無界面| 在背后完成特定任務,但往往難以調試,一旦出現問題,將會產生臟數據,影響深遠
如果再結合前面剛學到的技術,就可以發現前臺網站系統、后臺管理系統和接口服務系統都是通過php-fpm的CGI模式來訪問的,而最后的計劃任務系統則通過CLI命令行模式來訪問。如果你知道不同運行模式下的差異,就能很容易在今后的項目開發中避開或者解決同類的問題。
### 2.3.5 最好不要這么做
如果在項目開發的前期,我們可以預見并意識到需要同時開發這幾類項目系統時,盡早將不同類型的系統分開是很有好處的。最好不要把前臺網站系統、后臺管理系統、接口服務系統和計劃任務系統都放在同一個站點,同一個項目內。比如,我們需要開發一個網上商城,不要把商城的官網,商品管理后臺、提供給第三方供應商系統調用的接口系統、用于進行數據分析和運營推廣的計劃任務系統都堆砌在一起。更為常見的現象是,前臺網站系統會和后臺管理系統放在一起,這在前期是可以的。但一旦項目發展到某個程度時,盡早拆分可以有效進行風險管控。
## 2.4 框架之爭
前來應聘的同學,經常會問這樣一個問題:貴公司使用的是會哪個開發框架?
關于這個問題,作為面試官,我的回答是:公司目前采用的框架有多種情況。有的項目完全沒有使用框架,有的項目使用的是內部框架,有的項目則使用開源框架,甚至有些項目使用的是基于已有框架改良提升過后的衍生框架。
那么在最初開始時,我們又應該怎么選取開發框架呢?是完全不用框架,還是自主研發框架,還是使用開源框架?
完全不用框架是不可取的,因為會導致開發速度超慢。除了開發業務功能之外,還要重復搭建通用基礎設施。隨著業務需求的不斷增長與變化,可能原來簡單封裝的類庫會頻頻出現意想不到的問題和限制業務的發展。更重要的是,很多前人已發現容易觸發的問題,都會逐一在我們身上重現。但是,自主研發框架更是成本高昂。它需要你掌握更多更全面的知識,并要求具備一定的項目經驗,才能設計并研發出經得起實際考驗的開發框架。
更好的建議,對于初學者或者中級和初級開發工程師來說,選擇開源框架是個不錯的選擇。開源框架通常都是代碼質量優異,普及度范圍廣,有活躍交流社區和充分文檔說明的。
如果選擇了開源框架,那么新問題又來了。那么多開源框架,應該選擇哪一個呢?PHP開源框架,隨便都能羅列出很多選擇。例如:Yii,Laravel,Symfony,CodeIgniter,Phalcon,ThinkPHP,Zend Framewor,Slim,PhalApi等等,而這只是長長清單里面的一小部分。
框架沒有好壞之分,還是要以合適為主。要根據將要開發的項目類型,以及需要實現的業務功能,選取與之匹配的框架,同時還要考慮當前團隊的熟悉程度和興趣偏好,最后進行綜合權衡、考慮、選擇。
選定某個開發框架之后,根據其官方文檔的入門教程,下載、安裝、部署后即可馬上開啟我們的項目開發。
## 2.5 開始編寫第一行代碼
等了那么久,終于可能開始編寫我們的第一行PHP代碼了。
### 2.5.1 PHP專業素養
在編寫代碼過程中,可能會遇到很多新的問題,關于PHP語法,關于當前使用的開發框架,或者關于其他方面。盡早全面學習一下PHP的語法是很有必要的,你可以在網上找到完整的入門教程,也可以找到PHP編程技術進階的書籍來學習。如果對任何PHP的函數,或者PHP的知識有疑問,都可以隨時查看PHP官網文檔。
> 專家技巧:有疑問,隨時翻閱PHP官網文檔:http://www.php.net/。
雖然PHP學習門檻很低,但是大家千萬不能對PHP掉以輕心,因為里面還有很多微妙的知識點需要綜合全面地學習,并牢牢掌握,才能輕松應對開發過程中各種疑難雜癥。下面我們就通過一道真實的PHP面試題來窺探其中的奧妙。
#### 一道微妙的PHP面試題
先來看下一道簡單卻非常微妙的PHP面試題。哪怕是有豐富經驗的開發人員也會回答錯誤,因為其中牽涉的理論、知識很多。
題目如下:請問,你覺得以下代碼會輸出什么,并解釋為什么。
```
<?php
$str = 'php';
$str['name'] = array('dogstar');
var_dump($str);
```
如果你不通過執行上面代碼,就能準確無誤說出正確的答案,至少說明:
+ 你是一個細心的人
+ 對PHP語言的理解非常到位
但實際情況是,在面試過程中,很多人都回答不出正確的答案,更無從解釋其中的原由。
回答最多的答案(但是錯誤的)是:輸出一個數組,即:array('dogstar')。他們認為,把一個字符串當作數組使用時,會把這個變量轉換為一個數組類型,從而得到這個結果。這時候,我是表示很懷疑的,懷疑他是不是把以前所學的編程語言知識都丟了,或者根本沒上過相應的專業課程逃課去約會了。把指針下的某個下標進行賦值,會改變當前指針所指的變量類型?!
也有人說直接掛掉,但這種說法并不多。那真正的答案是什么呢?希望讀者在繼續往下看之前,先自己停下來,思考一下如果是你來回答這道題,你會怎么回答,又如何解釋?
#### 解題過程細說
要想得知最終正確的答案,至少需要對PHP的字符串和數組有較為深刻的理解,以及PHP基本類型和變量的底層知識有一定的認識。我們可以把這道題,當作一道高中時代的方程式,一步步進行分解、拆解、轉換,便可慢慢水落石出,最終得到正確的答案,而不應不加思索就臆斷結果是什么。
編程語言,以及計算機都是理性的,更多是靠邏輯和推理,是客觀的事實,而不是靠主觀臆斷。
首先,第一行代碼完全沒有異議,是把字符串“php”賦給了變量$str。但這正是“惡夢”的開始。
```
<?php
$str = 'php';
```
真正復雜、飽含細節的在于第二行代碼,即:
```
$str['name'] = array('dogstar');
```
但在開始做題之前,需要回顧一些PHP的基本知識。雖然說是基本,但我卻非常驚訝很多開發同學居然對此都了解不多,或者說看過但沒記住或者理解,甚至完全沒去接觸過。
先來看下PHP的基本類型。PHP的基本類型有哪些?或者說,通常情況下,編程語言的基本類型有哪些?摘自PHP官方文檔(鏈接:http://php.net/manual/zh/language.types.php),基本類型有:Boolean 布爾類型、Integer 整型、Float 浮點型、String 字符串、Array 數組、Object 對象、Resource 資源類型、NULL、Callback / Callable 類型。
其次是對PHP數組下標的理解。PHP數組,實際上是HASH的實現方式。理解這一數據結構后,再來理解它的所提供的操作就順理成章了。PHP數組里面的知識很多,但這里只說一個點:PHP數組的下標。
先來簡單看下這份額外的代碼:
```
<?php
$arr = array();
$arr[0] = 'A';
$arr['x'] = 'B';
$arr[1] = 'C';
$arr['y'] = 'D';
$arr[] = 'E'; // 沒有寫下標
```
問:最后E字母對應的數組下標是多少?為什么?不少同學,在回答這個問題時也卡住了。因為不知道PHP數組的下標的規則。
上面的問題回答不出來,對我們的面試題影響不大。但接下來這個問題就非常關鍵了:PHP數組的下標有哪些類型?如果這一點不清楚,那么會對很多PHP的函數和操作,都得不到透徹深刻的理解。
其實,這些重要的信息在PHP官方文檔上都有記載。但平時在學習PHP過程中,很多人都是覺得看這些“又長又臭”的文檔沒意思,而且很花時間。此時更寧愿去網上搜索所謂的快速入門教程,但這些教程往往也是PHP開發新手編寫的,他們雖然解決了問題,但很可能他們也是理解未全面,或者說明不夠全面。簡單來說,一切資料,都應從官網文檔上尋求正統的參考。
一如這里,PHP數組的下標類型,在PHP官方文檔(鏈接:http://php.net/manual/zh/language.types.array.php)已有記載。
>
可以用 array() 語言結構來新建一個數組。它接受任意數量用逗號分隔的
鍵(key) => 值(value)對。
array( key => value
, ...
)
// 鍵(key)可是是一個整數 integer 或字符串 string
// 值(value)可以是任意類型的值
關鍵信息,高亮如下:“// 鍵(key)可是是一個整數 integer 或字符串 string”。
也就是說,PHP數組的下標有兩種類型,一種是整數,一種是字符串。需要記住這一點,后面會用到這里的知識點和規則。
接下來,看下如何進行PHP數組與字符串之間的比較。那么下一個問題來了。PHP的數組與PHP字符串有什么不同,又有什么相同之處呢?很多同學都知道數組與字符串的區別,但實際上它們也是有相同之處的。這樣說,比較抽象,我們可以通過一些示例來加快理解。比如下面代碼:
```
<?php
$lib = 'PHPUnit';
echo $lib[0], "\n";
echo $lib[1], "\n";
echo $lib[10], "\n";
```
這里有一個字符串變量,它的值是(我喜歡的單元測試):PHPUnit。然后,分別輸出它第0個位置、第1個位置、第10個位置的值。那么會輸出什么呢?
很多語言,包括C/C++,Java等,以及PHP,字符串其實也是一個有序的序列。不同的是,不同語言底層實現方式會有差異。例如C語言中,要在字符串最后加一個結束符“\0”,不然就會導致內存問題;Java語言的字符串則是緩沖區的不變值;而PHP的字符串也可以當作是一個數組的形式。例如這里的可用圖表示成:

圖2-4 PHPUnit字符串的數組形式
不難得知,$lib[0]會輸出字符P,$lib[1]會輸出字符H,最后$lib[10]的下標不存在,會出現Notice并輸出空(嚴格來說是NULL)。鋪墊了那么多,其實是為了說明,某種情況下,PHP的字符串也可以當作數組來使用,當然與數組還是有很大區別的。
#### 回到面試題:先看左邊
回到前面的面試題。嘗試解釋一下第二行代碼背后發生的事情。
```
<?php
$str['name'] = array('dogstar');
```
先來做個拆解。按照PHP語言的解析機制,執行的順序是先執行等號左邊的代碼,再到右邊的表達式。所以,先來看等號左邊部分發生了什么事情:
```
<?php
$str['name']
```
任何問題的處理,都離不開它的上下文。乍一看這樣的代碼是沒問題的,但關鍵是:$str它不是一個數組,而是一個字符串。前面我們已經知道:
+ PHP字符串也可以通過下標來操作,但實際有效下標只能是0、1、2這樣的數字位置。
+ PHP數組下標有兩種類型,可以是整數或者字符串。
那么,對于一個字符串變量,給定一個字符串的下標?這意味著什么呢?PHP又是如何處理的呢?
顯然,最起碼,這個字符串不會因此而變成一個數組,它還是一個字符串。就像質量守恒定律,一塊金屬,哪怕是飛上太空,到了月球,它的質量也是不變的。所以,給定一個字符串的下標,但只允許是整數下標,那么PHP語言,就會像其他語言一樣,進行隱式類型轉換。很多人對顯式類型轉換、向上、向下類型轉換也不太清楚。即:把字符串下標轉成整數下標。怎么轉?又是一個新問題。
關于PHP字符串轉整數的規則,這里再簡單說明一下。PHP字符串怎么轉成整數?快速問一個PHP開發人員,他都可以告訴你,可以這樣做:使用intval()函數,或者前面加個(int)。那好,你繼續問他,以下這些代碼,結果是什么?
```
<?php
echo intval('123'), "\n";
echo intval('abc'), "\n";
echo intval('123abc'), "\n";
echo intval('abc123'), "\n";
echo intval('1a2b3c'), "\n";
```
這時,就不一定能完全回答出來了。知道怎么使用PHP的函數是一回事,知道為什么結果是這樣又是一回事。理解背后的規則更為重要,意義更大。
那么,PHP字符串轉成整數的規則是什么呢?其實PHP官方文檔(鏈接:http://php.net/manual/zh/language.types.string.php#language.types.string.conversion)也早已記載,即:
>
該字符串的開始部分決定了它的值。如果該字符串以合法的數值開始,則使用該數值。否則其值為
0(零)。合法數值由可選的正負號,后面跟著一個或多個數字(可能有小數點),再跟著可選的指數部分。指數部分由
'e' 或 'E' 后面跟著一個或多個數字構成。

圖2-5 PHP官方文檔,字符串轉換為數值
簡單來說,就是從前面開始,一直連續有效的部分,都會轉換成整數。所以,對于下標name,若轉換成整數,即$str['name']會相當于:
```
$str[intval('name')];
```
結果是什么呢?根據剛學的內容,不難知道結果會是0,即字符串“name”會轉化成整數0。
小結一下,到現在的情況是:

圖2-6 字符串下標轉換為數值后
到這里,我們已經逼近真相了。我們已經知道,第二行代碼,實際上是把字符串的第一個位置(下標為0)進行賦值,所以最后$str不會變成數組類型,只是改變下標為0的值而已。至于0下標變成了什么,可以喝口水,休息一下,然后繼續討論。
#### 面試題的右半部分
利用數學的代入法,第二行代碼就變成了:
```
// $str['name'] = array('dogstar');
$str[0] = array('dogstar');
```
問題又來了,要把一個數組塞進字符串的第一個位置,會發生什么呢?

圖2-7 待寫入的數組
是直接只覆蓋一個位置,還是后面的位置也會連帶覆蓋?這里不難理解,只會覆蓋一個位置,即下標為0這個下標。那么對于只有一個坑位,怎么存放得了一個數組這樣的龐然大物呢?
先來看下,數組又是怎么轉換成字符串的。這里就不再展開細講,因為大家已經熟悉。數組轉成字符串,會變成“Array”這樣的字樣。即:
```
<?php
echo strval(array('dogstar')); // 結果是 Array
```
所以,我們的題目又簡化成了:
```
// $str['name'] = array('dogstar');
// $str[0] = array('dogstar');
$str[0] = 'Array';
```
因為只有一個坑位,可再簡化成:
```
// $str['name'] = array('dogstar');
// $str[0] = array('dogstar');
// $str[0] = 'Array';
$str[0] = 'A';
```
最后,結合上下文,應該就不能知道最終正確答案是什么了。
```
<?php
$str = 'php';
// $str['name'] = array('dogstar');
// 左邊等效于,對$str下標為0的元素進行賦值
// $str[0] = array('dogstar');
// 右邊等效于,把數組array('dogstar')轉換成字符串Array
// $str[0] = 'Array';
$str[0] = 'A';
// 最終輸出的結果是:Ahp
var_dump($str);
```
上面是面試題目的簡化版,把全部注釋去掉后,就能更清楚看出里面蘊藏的知識點了。
#### 更完整的回答
在我看來,回答正確的答案,只能得到60分,達到合格線。如果能解釋其中執行過程的細節,則可以達到80分。若能把PHP牽及的知識,進行系統的講解,則可以達到95分。最后,如果能指明執行過程中發生的因隱式類型轉換而發生的警告或者其他錯誤信息,以及在不同版本下PHP執行的差異,就更為完整了。
總而言之,這是一道基本的PHP題目,但關聯的知識點非常多,考察面廣。除了可用于評估開發同學對于PHP語言的把握程度,還能考察是否具備高級開發所應具備的軟能力——細致、嚴謹、求真。
此外,在開發前,系統性地學習,閱讀開源框架的開發手冊或者使用說明是很也有必要的。哪怕是粗讀,大概知道有哪些框架特性、注意事項和功能模塊,也會對后面的項目開發大有幫助。而不是等到有問題了,才去辛苦排查,甚至不知切入點在哪。
### 2.5.2 事務型腳本
即便使用成熟的開源框架,也很難保證最終就一定能編寫規范的代碼,交付高質量的項目。有很多初學者,都喜歡把全部的代碼堆砌在一起,喜歡放在同一個文件,甚至在同一個函數內完成全部的功能。一不留神,就會把對參數的接收和驗證,對數據庫的操作,領域業務的邏輯處理,數據的返回與輸出等混在一起,變成一個“大泥球”。這就是行業內所說的事務型腳本。
事務型腳本好不好?好,但只局限于簡單性的功能,以及停留在開發前期。每個事務型的腳本,在成熟的項目里最后都會成為一塊巨大的技術債務,只有當初編寫它的人才能勉強敢做出改動和修改,因為事務型腳本藏著不可預估的缺陷。
但是,在項目開發的前期我們不是為了求快嗎?若每方面都要考慮到以后,那豈不是很累?而且,過早優化是否會得不償失?說到這里,我們有必要再來回顧一下經典的MVC模式了。
### 2.5.3 再談MVC模式
MVC模式是使用最為廣泛的分層模式,也是誤解較多的模式。不用事務型腳本開發的話,我們可以遵循MVC模式,但是注意不要落入俗套。不要以為聽說過MVC模式,或者簡單地看過它的說明就覺得已經熟練掌握。
首先,MVC模式不是萬能的。在軟件行業內,沒有銀彈。沒有哪個方案可以解決全部的問題。MVC模式只能在理解的基礎上加以使用,如果不加判斷就生搬硬套,會很容易造成誤解和誤用。MVC模式只是眾多分層模式中的一種,另外在前端開發中還有MVP模式、MVVM模式。可以說,MVC對于前面所說的前臺網站系統和后臺管理系統是適用的,但對于本來就不需要的接口服務系統和計劃任務系統來說,強加View層就顯得格格不入了。這就是為什么在PhalApi開源框架里采用的是ADM模式(Api-Domain-Model),不僅沒有View層,而且還把Controller層改成Api接口層,并增加Domain領域業務層。你也可以根據項目的需要,設計規定不同的分層模式。
其次,不管采用哪種分層模式,都有通用的依賴原則。即:高層不應該依賴底層,并且底層不應該調用高層,如果需要調用,則應通過回調函數或者事件偵聽方式來實現。
最后,要嚴格按照分層模式各層的職責來劃分代碼的層級,分離關注點。不應該把對數據庫的操作放到Controller控制層,也不應該把對視圖的處理放到Model層,更不應該把決策調度,以及規則處理放到View層。
### 2.5.4 我心中的企業級系統代碼
每做一個項目,開發一個系統,我都會當作是自己的作品,并追求更高的質量,自我要求當前這個作品要比上一個作品好。
我希望,我編寫的代碼,能適應當下復雜多變的需求,能應對實際運行環境中各種殘酷的情況,能在未來不確定性面前可以不斷演進和迭代。
即便多年過去,這些代碼依然存活,依然被使用,依然發揮著強大的作用并持續創建價值,仿佛這些代碼就是昨天剛剛編寫的一樣。看到或維護此部分代碼的后繼開發人員,甚至會驚嘆這些代碼如同有魔法一般能自我進化并保持與時代的同步,只需要微調一下便能滿足新需求。如果是由于業務或市場等外部原因,系統下線了,代碼雖然沒有再執行,但仍然有參與和學習的價值,因為它里面蘊含著豐富的思想、原則和模式。
但我覺得,最為重要的是,當你在閱讀這份代碼時,最大的感觸就是:這份代碼仿佛是你寫的一樣,感覺到自然、親切。它是那么容易理解,那么達意,縱使是讓你來寫,你也會這樣寫,或者它本就應該這樣寫。這是因為在你和我,乃至其他成千上萬的開發人員,在心中,在腦中,在潛意識里,我們都有一套共同語言和模式,對于當前的代碼和系統有著共同的認知和解釋。
這就是我心中對企業級系統代碼的定位。
## 本章小結
對于初學者,選擇一個高起點,能讓你更好的贏在起跑線上,對今后的做事方式都會有間接而深遠的影響。
服務器首選Linux,這是我從業多年的建議。那些熟悉Linux操作的技術開發人員,往往更能迎難而上,當別人駐足在問題表面而不知所措時,他們能在極短的時間內找到問題所在,然后迅速修復。套用我的導師曾經給我的忠告,我想對讀者說:早晚你都會喜歡上Linux操作系統的(以前我的導師是對我說:你早晚會喜歡上用vim來編程的,當時我驚訝不已,覺得這是完全不可能的事。而現在我每天都在用vim,并且樂在其中)。
使用集成式、一鍵安裝的開發環境很有吸引力,但請不要這么做。自己親自動手搭建LNMP環境,會讓你收益良多。現在遇到問題不可怕,不會解決也不可怕,正因為不懂,我們才需要學習、成長和進步。投入一點時間和精力,慢慢地去理解問題背后的原因和原理,然后解決它,這會讓你更加印象深刻。日后再遇到類似的問題,你就能更容易以專家的角度來快速排查和解決。
開發網站項目可以分為:前臺網站系統、后臺管理系統、接口服務系統以及計劃任務系統。不同的項目,有不同的特點,需要注意的事項也不同。對于框架的選擇,則可以是無框架、自主研發框架、使用開源框架或者使用二次開發后的框架。對于剛入門的開發人員,建議是使用合適待開發項目的開源框架,快速進行開發。
當開始編寫代碼時,要避免把全部的代碼都塞在一個PHP文件里,變成“大泥球”似的事務型腳本。也要注意不要過于輕視MVC模式,或者認為MVC模式是萬能的,就不加考慮就生搬硬套。嚴格要求自己是有好處的,特別對于PHP編程基礎這一方面,要在平時的學習、工作和業余中有意識地不斷強化自己的PHP專業素養,始終保持細致、嚴謹、求真的態度。我們也通過一道微妙的PHP面試題為例子詳細地探討了這一工匠精神。
作為本章的總結,可以濃縮為一句話:選擇高起點,并始終以高標準嚴格要求自己。
正所謂,磨刀不誤砍柴功。本章的前期準備,將會為我們打下堅實的基礎。當項目開發完成后,就需要進行下一步了——向世界發布我們的代碼!