前往
大廳
主題

用PHP的OOP寫CLI程式的小筆記

新手方 | 2022-07-03 23:45:23 | 巴幣 1100 | 人氣 352

最近在"試吃"水球軟體學院,成為同一批學員裡唯一用PHP撰寫作業的人。
我是直接使用原生PHP專案搭配composer裝phpunit撰寫測試,所以對於平常使用Laravel慣了的我來說,想當然爾是遇到一些瓶頸,所以把這些瓶頸與解法附上來提醒自己,也希望能夠幫助到你。

零、VSCode的套件安裝

這邊推薦幾個寫OOP用的套件,若不是用VSCode的就跳過唄。
寫OOP會遇到希望變數別人能看到,但無法設定的情況。這時就能透過自動產生Setter做到。
覺得use太麻煩而不用OOP嗎?這個套件在psr4規範下可以做到省去你一堆引入物件的麻煩。
覺得建立class太麻煩嗎?在psr4規範下這個套件可以幫你建立class/interface及trait,並幫你設好namespace!
常打錯字嗎?裝就對了!

一、環境/專案設定


1. PHP版本 :

想要使用Enum的話必須把PHP的版本升至8.1以上,所以強烈建議整個操作都用docker。

2. composer :

使用docker指令執行,例如安裝
docker run --rm -i --tty -v ${PWD}:/app composer init
若使用cmd的話要把${PWD}換成%PWD%,此差異以下指令不特別說

3. 新增helper function:

若選擇該composer專案為project,預設會幫你建立一個src資料夾,我是選擇將helper放至裡面。若需要寫一些共用方法就可以放裡面。請在composer.json中新增以下資料。
    "autoload": {
        "files": [
            "src/helper.php"
        ]
    }

然後執行
composer dump
就可以在helper中新增helper function了

二、基本功能寫法


1. 比較類別大小


PHP沒有內建Compartor interface,所以我選擇使用global function的方式實作compartor。
案例:大老二中比較卡牌順序。
/src/helper.php
function cardCompare(Card $cardA, Card $cardB){
    if ($cardA->getRank() == $cardB->getRank()) {
        return $cardA->getSuit()->value > $cardB->getSuit()->value ? 1:-1;
    }
    return $cardA->getRank()->value > $cardB->getRank()->value ? 1:-1;
}
這樣就解鎖了php內建的sort系列function了!
再寫一個排序方法
function cardSort(array &$cards){
    usort($cards, 'cardCompare');
}
這樣就能夠將傳入的卡牌由小到大排序,並省略一堆匿名方法

2. Enum

PHP 在 8.1時新增了Enum的功能,所以若是跟我一樣用docker執行php的人就可以享有這個內建功能。
由於實作上蠻倉促的,寫得沒有很好。故我這邊不貼完整實作,僅告訴各位幾個好處:

a. 可以指定Key對應的類型。

像是我針對大老二的花色及數字實作Enum,並指定int作為對應類型。這樣比大小就直接用value欄位來排序就好(上述helper中的cardCompare就是這樣實作)

b. 可以像類別一樣自由拓展方法,更可以實作interface。

不過還是有些限制,建議看過此教學再決定是否使用。

3. stdin

PHP在輸入的操作,大致上跟C一樣,所以資工系的學生可以直接跳過。

大致上我會用到兩個方法: fget跟fscanf

a. fscanf(STDIN, $format, $string)

重點是可以根據$format去解析輸入,假設你要取用一個空格分開的數字時就能這樣做
fscanf(STDIN, '%d %d\n', $this->from, $this->to);
以下為抓取該行除換行文字的寫法。
fscanf(STDIN, "%s\n", $this->name);

b. $string = fget(STDIN)

直接抓一整行,適合用於不預期格式的輸入。
以下是抓該行並移除最後換行符號的寫法。(等價於fscanf的第二個案例)
$command = trim(fgets(STDIN));


三、測試相關


1. 建置環境

a. 從composer安裝

composer require phpunit/phpunit -dev

b. 在composer中設置測試資料夾

composer.json
    "autoload": {
        "psr-4": {
            "Test\\": "tests/"
        },
    }

c. 在專案中新增tests資料夾


2. 測試.in跟.out

如果有這兩個檔案可以拿來做測試的話,你要感謝出題者。
因為phpunit寫這種測試是非常簡單的事情,以下直接給程式碼
tests/GameTest.php
    public function getTestGameByFileData(){
        return [
            'normal-no-error-play1' => ['normal-no-error-play1'],
            'straight' => ['straight'],
            'always-play-first-card' => ['always-play-first-card'],
        ];
    }

    /**
     * @dataProvider getTestGameByFileData
     */
    public function testGameByFile(string $fileName){
        $result = shell_exec("php /app/main.php < /app/tests/data/{$fileName}.in");

        $expected = file_get_contents(__DIR__ . "/data/{$fileName}.out");

        $this->assertEquals($expected, $result);
    }
眼尖的人應該注意到一件事情 : 這裡的path是寫死的。
沒錯,因為本人開發這個專案時是直接用docker執行的,所以這樣寫沒問題。
若環境不同就得改寫法。

3. 執行測試

docker container run --rm -v ${PWD}:/app/ php:8.1 bash -c '/app/vendor/bin/phpunit /app/tests'
.

結語

用PHP寫OOP真的需要很多工具跟強大的腦袋去因應。不過PHP的確是做得到OOP的!
藉著這段時間的練習,除了了解OOP,我也更了解原生php了。

創作回應

更多創作