標籤彙整: psr-0

CodeIgniter不是一個現代框架

CodeIgniter在今年4月出了第三版。

我在Facebook、批踢踢至今沒看到有人撰文批評它,我覺得這樣不對。

要跟全世界的PHP工程師競爭,只學習CodeIgniter是不夠的。

CodeIgniter不是一個現代框架。必須有人指出這點。

CodeIgniter 3的釋出

這則新聞第一時間就在Reddit出現:

Codeigniter 3 is out

得分最高的評論如下:

是怎樣的被虐狂才會想在2015年用CodeIgniter來開發專案啊?沒有namespaces,沒有autoloading,沒有composer!我乾脆投河自盡好了。

這則評論看起來很偏激嗎?但它拿到最高評論可不只是因為搞笑。

CodeIgniter不是一個現代框架。它沒跟上PHP的幾個趨勢。

怎樣算是現代PHP框架?

現代框架沒有正式定義,但我認為至少要具備三點:

  • 符合PHP元件推薦規範(PHP Standard Recommendations)
  • 支援Composer套件管理
  • 提供成熟的測試環境

為什麼要有這三點才算是現代框架?

因為它們解決了PHP社群幾年來的三個大問題:

  • 一盤散沙的社群
  • 四散的套件
  • web需要軟體測試

一盤散沙的社群

不像Ruby社群,大家全力貢獻在Rails。

PHP社群四散各地、同一個功能在不同框架被重複開發、

框架間難以使用彼此的元件、不同社群難以使用其他社群開發的套件。

這樣的現象在近年終於獲得改善:各框架派出了幾位代表,大家協商出一套coding convention。

從此有了PHP框架互用性小組,制定了PHP元件推薦規範

以後若想要開發新框架,可以從現有的框架中拿幾個元件來組合著用。

PSR規範中對於namespace有清楚的規範,

而CodeIgniter 3中還是到處充滿CI_Controller、CI_Model這種過時的前綴字(prefix)寫法。

四散的套件

在開發套件的時候,PHP有元老級的PEAR、CodeIgniter的Sparks、

Laravel的Bundles、Zend Framework 2的Modules、CakePHP的Bakery。

這種現象是整個PHP社群的損失:PHP社群如此龐大,但是大家各搞各的,心力無法集中。

所幸,Composer的出現解決了這點。

現在大家都開發Composer套件了,整個PHP社群開始共享彼此的成果。

套件相依性的問題也在Composer一併解決,大幅加速了開發的速度與軟體的品質。

CodeIgniter 3還是沒直接支援Composer。

你還是要寫:

$this->load->library('xxx'); 

之類的囉唆東西。

web需要軟體測試

隨著web application越來越複雜、架構越來越龐大,

軟體測試開始成為web圈的討論重點。

軟體測試不但協助設計架構,確保軟體品質,還能協助重構。

web社群已經從「測試要寫很多很多」爭吵到「測試過多沒意義,足夠就好」。

DHH在2012年已經開始主張「測試覆蓋率不要追求100%」、

「code和測試的比例超過1比2就是出問題」

Testing like the TSA

他在2014年更進一步論述「測試先行已死」

TDD is dead. Long live testing.

PHP近年總算有了PHPUnit廣泛出現在各框架,大幅改善了測試環境。

而CodeIgniter呢?連像樣的基本測試都做不到。

結論

CodeIgniter的主要contributor,Phil Sturgeon,在2012年就曾經貼文闡述

「CodeIgniter不重寫就做不到的5件事」:

5 Things CodeIgniter Cannot Do (without a rewrite)

原文我就不翻譯了。他指出的五個缺失如下:

  • 自動載入
  • 命名空間
  • 資料庫結構抽象化
  • 單元測試
  • migration機制

綜合以上,我的結論就是:

要跟全世界的PHP工程師競爭,只學習CodeIgniter是不夠的。

CodeIgniter不是一個現代框架。


Q&A

Q1: 我耳聞Composer很久了,但到底怎麼入門呢?

COMPOSER設計原理與基本用法

COMPOSER進階原理:PHP命名空間與PSR-0


Q2: 我暫時不可能離開CodeIgniter,有什麼辦法嘗試軟體測試?

CI 2曾經有人把simpletest整合進去。不知道CI 3做不做得到就是了。

CodeIgniter with SimpleTest


Q3: CodeIgniter也許是有些小缺點,但真有那麼落後嗎?

我在2014年2月曾經寫信詢問Phil Sturgeon意見。他的答覆如下:

Hey Tony,

(上略)

我不會再理CodeIgniter了,它玩完了。試試看Laravel吧,然後看一下這些短片:

https://laracasts.com/

你會學到很多:)


Phil Sturgeon

原文

Hey Tony,

You definitely don’t want to rate yourself based on your own age and ability. I’m 25 and there are lot of people around who are younger and smarter, or older and smarter, and more successful. I rate myself based on my improvements over time, and as long as I’m always learning then I’m always getting better.

I’d ignore CodeIgniter completely, its done. Try getting into Laravel, and watch these screencasts:

https://laracasts.com/

You’ll learn a lot 🙂


Phil Sturgeon


社群看法

PHP台灣

Composer進階原理:PHP命名空間與PSR-0

上次的Composer設計原理與基本用法說明了PHP套件管理的歷史與社群提出的解決之道,本篇文章接著談類別管理的進階議題。

當類別名稱一樣…

當專案大了起來,有時候會有類別名稱重複的問題。
假設今天要撰寫一個論壇模組,提供討論區與留言板功能。
你一定很想將討論區的文章與留言板的文章都命名類別為Article:

// BoardArticle.php

// ForumArticle.php

當然了,這麼做會得到一個結果:

Fatal error: Cannot redeclare class Article

這種問題有一個簡單的解決辦法,就是加上前綴字。
類別分別命名為ForumArticle與BoardArticle就可以了。

Q1: 等一下!這個解法好陽春!我看到至少4個問題:
1. 類別名稱容易變得冗長。
2. 有些類別一開始你以為不會跟人重複,結果之後真的重複了。難道永遠替類別加前綴字?
3. 類別名稱寫Article俐落多了!文章就是概念上的文章,不要逼我告訴你是討論區還是留言板!如果專案用到兩種留言板模組,分別由以前的兩個前輩寫好,難道還要逼我把作者名稱寫進去?

class TonyBoardArticle{
  //...
}
class JackBoardArticle{
  //...
}


4. 如果我在打造框架(framework)呢?幾乎會把所有常見名詞用過一次(像是Request、Loader、Response、Controller、Model等類別)!難道前面全部前綴?
看看Codeigniter的原始碼,全部用CI_當作前綴。超醜的。

命名空間(namespace)登場

於是PHP從5.3版之後支援了命名空間(namespace)。
所以可以用Article替類別定義了:

// BoardArticle.php

// ForumArticle.php

使用類別時只要加上命名空間即可:

//index.php

如果當前的php檔只用到其中一個Article類別,可以指定當前的php檔只用哪個命名空間+類別的組合:


如此一來,當php找不到Article類別時,便會去使用use關鍵字宣告的組合。
當然了,就算用use指定過,原本的宣告方式還是可以用的。(如最下方兩行所示)

當東西多了起來...

OK,可以繼續完成我們的論壇模組了!
討論區跟留言板有各自的文章,再來還需要「推文」:



使用剛剛學到的命名空間去載入他們:


果然是漂亮的各種命名阿!


Q2: include有好多行!上次的Composer設計原理與基本用法提到了Composer可以解決這種問題,當引入命名空間之後,Composer也能發揮作用嗎?

是的。

Composer登場

跟上次初學Composer一樣,建立一個composer.json檔:

{
    "autoload": {
        "files": [
            "ForumArticle.php",
            "ForumComment.php",
            "BoardArticle.php",
            "BoardComment.php"
        ]
    }
}

注意,上次我們用"classmap"指定資料夾、把資料夾內檔案全掃一次,這次我們用"files"分別設定各個檔案。

再來,在terminal輸入

composer install

執行完畢之後,跟上次一樣,只要載入一個檔案:


就可以使用各個類別囉!


Q3: 等一下!看起來跟沒有命名空間的時候差不多啊!一樣是把php檔自動require進去而已?

對啊,你最上面的原始寫法,也只是手動載入好幾個檔案,在載入的時候本來就沒有特別之處:


載入php檔就只是載入,跟命名空間是兩回事。


Q4: 還是不太對啊!上次我用classmap一次把好幾個資料夾內容掃過,這次我用files分別指定每個檔案幹嘛?Composer不是厲害在能指定資料夾去自動掃過?

......你說的沒錯。
開個my_lib資料夾,把4個php檔都丟進去吧。composer.json這樣寫就好了:

{
    "autoload": {
        "classmap": [
            "my_lib"
        ]
    }
}

再來,在terminal輸入

composer install

這樣就搞定了!
其實我只是想示範,除了用classmap設定資料夾之外,也可以用files直接指定檔案。

Q5: OK,原諒你。不過,我必須說,我今天什麼都沒學到。最後還是在composer.json寫classmap而已,跟上次一模一樣。

是的...我剛說了,載入php檔就只是載入,跟命名空間是兩回事。
我今天介紹的namespace功能是PHP本身提供的。而Composer只是協助你載入的工具、當然不可能改變程式語言本身。
Composer只是幫助你少打那一串require而已。


Q6: ㄟ等等...有個地方我覺得很醜。我們現在的my_lib資料夾裏面有4個檔案,檔名很醜:

"ForumArticle.php"
"ForumComment.php"
"BoardArticle.php"
"BoardComment.php"


類別名稱本身都是俐落的Article跟Comment了,檔案名稱還是有前綴字。但也不可能有兩個Article.php、兩個Comment.php。你有沒有辦法解決這個美感問題?

這個問題簡單,把那個my_lib資料夾刪掉,開一個Forum資料夾、一個Board資料夾。本來的ForumArticle.php改成Article.php丟進Forum資料夾、本來的BoardArticle.php改成Article.php丟進Board資料夾。composer.json改寫成這樣:

{
    "autoload": {
        "classmap": [
            "Board",
            "Forum"
        ]
    }
}

再來,在terminal輸入

composer install

這招不錯吧!檔案名稱就是類別名稱,乾淨俐落!
而且資料夾的名字本身就是namespace的名稱!
以後都這樣做啦!一用到命名空間就開個同名資料夾把檔案丟進去!

Q7: 這招我覺得還好耶...。本來檔案都放在my_lib,我在composer.json只要填my_lib一行就好,現在變成要填兩行。要是我這個論壇模組有一大堆類別、用了一大堆命名空間呢?那我classmap底下不就要填入好幾行?那我寧可全部丟進my_lib,只填my_lib一行!

唔,這樣說也是有道理。那重新建立my_lib資料夾,把Board跟Forum資料夾丟進my_lib資料夾。composer.json改回這樣寫:

{
    "autoload": {
        "classmap": [
            "my_lib"
        ]
    }
}

再來,在terminal輸入

composer install

classmap不只是告訴Composer去載入哪幾個資料夾內的檔案,還會把資料夾內的資料夾也全部掃過一次。
怎麼樣,Composer夠神吧。


Q8: 原來classmap底下會遞迴掃描下去...。我決定了,我的Forum跟Board都是在提供線上討論功能,我決定替我這個模組命名為Discussion。我要在my_lib底下開Discussion資料夾,然後把原本的Forum跟Board資料夾都丟進去。你覺得這個想法如何?

還不錯。一個Discussion資料夾就是你的整個Discussion模組。
提供了討論區、留言板功能的Discussion模組,我喜歡。


Q9: 好像忘了什麼...。啊,剛剛說命名空間跟檔案結構符合會最漂亮。那我要把那4個檔案的namespace改成這樣:



剛剛說了,載入檔案就只是載入檔案,跟命名空間無關。
現在檔案結構沒變,所以我應該不用重新輸入composer install。
讓我試試...。
靠!怎麼噴error了!你騙我?

Fatal error: Class 'Discussion\Forum\Comment' not found 

呃...,我前面的說法確實有點誤導。
PHP自動載入的基本函式長這樣:

void __autoload ( string $class )

如你所見,PHP至少需要Composer提供資訊指出$class該去哪個檔案找。
namespace改變之後,PHP會找不到對應的$class在哪。
所以還是輸入一下composer install吧!Composer會把需要的資訊整理好的。


Q10: OKOK,我知道了,我駕馭這一切了。我覺得這個Dicsussion模組真的超屌的,不但命名空間漂亮,連檔案結構都漂亮。我要把這個Discussion資料夾整個丟給我朋友,他們公司最近需要論壇模組。
讓我打電話給他...。
「什麼?你們已經做好半個論壇模組了?你只需要我模組的其中幾個功能?你們的模組也是放在Discussion資料夾?」
糟糕,資料夾名稱重複了!所以我的模組拿給別人還是有不相容的可能,怎麼辦?

沒有錯..還記得你Q1提到的第3個狀況嗎?確實有把作者名稱加進去的必要!
別怕,我教你。你開一個Tony資料夾,把整個Discussion資料夾丟進去。
接著所有檔案namespace改成像這樣:


要用的時候就這樣喔:


是變得有點長啦。
但這下搞定了吧!作者名稱再撞到的話,就改個獨特的名稱就是了!

終於。讓我們談談PSR-0

你一定常聽到PSR-0對吧!
PSR-0是PHP跨框架相容性統一標準組織訂出來的自動載入慣例。

來談談PSR-0幾個最重要的要求吧!

* 命名空間加上類別名稱一定要長這樣:

\\(\)*

* 前面一定要是作者名稱
* 中間可以有任意層次的命名空間、最後是類別名稱。
* 中間任意層次的命名空間直接對應到檔案結構。

發現了嗎?在剛剛Q1~Q10的過程中,其實你已經把PSR-0學完了,連設計原理都一起搞懂了。

懂這些之後,你也可以做好自己的模組、發佈到Packagist給全世界透過composer下載、使用了!

最後,如果遵守psr-0的話,composer.json可以這樣寫:

{
    "autoload": {
        "psr-0": {
            "Tony\\Discussion\\": "my_lib/"
        }        
    }
}

注意,雙引號、兩次反斜線並沒有特別意思,只是json規定的格式。

跟classmap一樣都可以完成任務。兩者其實是有差別的...,我們下次再談。

結語

Composer工具以及PHP-FIG組織的出現,讓一直以來散落各地的PHP社群開始有集中的趨勢。
換句話說,各社群終於能共享彼此的library了。
然而,如你所見,psr-0不但導致檔案結構容易變得深層,還要求檔案結構必須配合程式碼,這也招致了不少批評
除此之外,composer autoload內的classmap跟psr-0到底如何分工?效能差異又為何?這些問題也都還在爭論與驗證當中。

不過,PSR-0在各框架已被廣泛支援,因此建議你還是需要有所瞭解。

最後...

現在已經出現psr-0的替代方案,稱為psr-4

PSR-0從2014-10-21開始被註明為不建議使用。
至於PSR-4..我們下次再談。