-->
顯示具有 PHP 標籤的文章。 顯示所有文章
顯示具有 PHP 標籤的文章。 顯示所有文章

2020/10/12

phpbrew 無法安裝 pdo_mysql extension

phpbrew 沒辦法透過 ext install 的方式安裝 pdo_mysql。

若要安裝則需要在編譯 PHP 時直接將 pdo 和 mysql 加入編譯清單:

phpbrew install 7.4.0 +default +pdo +mysql

2020/04/21

Laravel v7.0 新功能

官方網站整理下來:


Airlock

使用 Airlock 可以更輕鬆的支援 single page application (SPA)、token-based API 等等,同時也可以針對 token 設定可存取犯元 (scope)。 (感覺上類似 OAuth)


Custom Eloquent Casts

以往使用者從 Eloquent ORM 拉出資料以後,使用者可以自訂 cast type,也就是說可用者可以自動 cast attribute 成特定格式。範例可以參考 Laravel 的 Pull Request



Blade

新增不少功能來 render HTML。詳細說明請參考 Larvel Docs。



HTTP Client

為了史 API 更靈活,Laravel 使用 Guzzle HTTP client 來與其他 API 溝通。



Routing Cache

透過新的方式來做 route 的 cache,即使大型網站 (800 routes) ,route matching 的速度也可以比以往快上 20 倍。

2020/01/14

修改專案的 tag (version) 就可以毀掉其他專案

今天剛好要處理 Zip 檔,目前看到功能比較齊全的專案應該是 Ne-Lexa/php-zip,但是用 composer require 時卻發生 error message 大噴發:



仔細一看 .... 居然有「v9.99.99」的版號,該不會要世界末日了吧?


打開 comploser.lock 看一下是怎麼回事,追蹤後得知相依性如下:

laravel v6.10.1  <=  ramsey/uuid ^v3.7

ramsey/uuid  <=  paragonie/random_compat": "^1 | ^2 | 9.99.99"

兇手抓到了,看來在 paragonie/random_compat 有一個版本號是 v9.99.99,composer 會自動拉最新的版本號來使用,因此只要任何專案 require 時沒有指定版本編號,composer 就會自動把相依性對到 v9.99.99,如果其他 package 有關連到,就是直接 dependency conflict 了。


要處理掉這個問題,只要在 composer.json 也 require paragonie/random_compat,但是加上指定版本編號如 ^v2.0,這樣 composer 就只會拉到 v2.x.x 的版本,不會去用 v9.99.99 這個版本。


ref:

2019/10/07

Laravel UrlGenerator 判斷 HTTP or HTTPS 的因素

第一個要求 Laravel 要使用 HTTP or HTTPS (以下簡稱 protocol ... 因為字太多了 .._Drz) 的方法,其實就是在「.env」做設定,例如:
APP_URL=http://zeroplex.tw

再來網路上應該會找到,要開發者在 AppServiceProvider 中加入設定:
\URL::forceScheme('https');

這邊的 URL facade,可以從 config/app.php 中追蹤到,是 \Illuminate\Routing\UrlGenerator::class 的 alias,設定了 forceSheme() 以後,未來透過 UrlGenerator 建立的 link 都會是設定好的 protocol。

BUT .... 就是這個 BUT,若是遇到另一個其他的 method 來產生 URL (例如 Illuminate\Routing\Redirector::route() ...),這樣就可能吃到開發者設定的 protocol ,則由 Laravel 會自動抓進來的 request 來判斷該用什麼 protocol,若 application 剛好是靠 Nginx 在 handle HTTP SSL 的話,request 到 Laravel 這端時 HTTPS 就已經轉成 HTTP,導致 Laravel 以為 application 沒有上 HTTPS。

若要讓 reverse proxy 後面的 Laravel application 都固定用 HTTPS 的話,可以再到 app/Http/Middleware/TrustedProxy.php 去修改設定,讓 Laravel 識別 proxy 做出對應的動作 (給正確的 protocol)。



Ref: Laravel 5 force HTTPS issue with login routing to HTTP

2019/07/30

MySQL 8.0 的新密碼加密 plugin 導致 PHP 連線失敗

今天很難得打開 phpMyAdmin 來看一下有什麼改變,但發現一直無法登入,顯示錯誤訊息「」。


查了一下才發現 MySQL 8 預設的密碼有多種格式,預設的格式 PHP 還不支援,所以會登入失敗 (但用 console 會成功)。

解決方法是暫時改為舊版本的密碼儲存方式:
alter user 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your-password';
flush privileges;

然後在設定檔裡面多新增一條規則,把預設的密碼儲存方式改回舊版的:
[mysqld]
default-authentication-plugin = mysql_native_password



2019/07/18

使用 Eloquent migration 刪除有 key constrain 的 table / index

建立 table 時可能會同時建立 foreign key:
$table->foreign('good_id')
    ->references('id')
    ->on('goods')
    ->onDelete('cascade');

$table->index('value');

這個情況下要直接 dropIndex() 是會出現錯誤的,要先把 key constraint 解掉再來刪除 index。

而 constraint key name 用 show index from TABLE 是看不到的,但是可以使用下面的語法看到 create table 時做的事情:
show create table NAME

看到 contrain key name 以後先用 dropForeign() 刪除,再 dropIndex() 即可。

ps. 記得事情處理完以後,要把 constraint 加回去喔

2019/06/28

注意 PHPUnit 的 fixture 行為

理論上,各個 test case 不應該互相影響,所以 PHPUnit 設計的 fixtures 像是 setUp() 和 tearDown() 都會在各個 test case 前後執行。

簡單做個測試:
<?php

use PHPUnit\Framework\TestCase;

class LibTest extends TestCase
{
    public function setUp(): void
    {
        $this->data = null;
    }

    public function tearDown(): void
    {
        $this->data = null;
    }

    public function testOne()
    {
        $this->data = 123;
        $this->assertEquals(123, $this->data);

        return $this->data;
    }

    /**
     * @depends testOne
     */
    public function testTwo($data)
    {
        $this->assertEquals(123, $this->data);
    }
}

// -----

PHPUnit 8.2.3 by Sebastian Bergmann and contributors.

.F

即使在 test case 加上 @depends 標記,fixture 也會運作,所以若是要讓下一個 test case 繼續使用的測試值,都必須用 return 的方式傳值。

----

另外,Laravel 提供的 testing tools 也有相同的行為。例如:「RefreshDatabase」,在 test case 執行結束以後就會把資料清空,因此如果要對 DB 做多像檢查,就必須在同一個 test case 中建立多個 assertions (個人不喜歡這樣做),不然即使加上 @depends,DB 中的資料也會被 refresh 掉。

2019/06/27

在 Laravel unit test 手動建立 HTTP request

現在有個 controller 收到 HTTP POST data 以後才會動作,且這個 controller 為 AJAX controller,沒有 view 沒辦法用 Selenium 測試:
class TargetController extends Controller
{
    public function doSomething(Request $req)
    {
        // do something ....
    }
}

要測試的話,其實可以透過一些工具手動測試,像是 PostMan 等類似的工具。但這種測試會遇到一些問題:Middle 有檢查使用者身份、需要拿到 access token 才能進 controller 等等。這樣會讓測試多了不少不確定因素 (變因),導致測試結果不準確。

要進行測試較好的方法,就是建立一個 HTTP request 來模擬實際情況:
$req = new \lluminate\Http\Request();

$ctrl = new TargetController()
$result = $ctrl->doSomething($req);

不過可惜的是 Request 這個類別,設計原始的用意是從 $_GET、$_POST、$_FILE 等全域變數整理成物件方便 access,所以若要在 request 中加入參數,目前只能透過 replace() 來增加。例如需要新增 key/value pair,必須這樣撰寫:
$request->replace([
    'key' => 'value',
]);

至於模擬檔案上傳 .... 我還在找方法,確定了以後再來寫筆記。



Ref:

2019/01/16

PHP 5.6 new release

官方原本要在 2018/12 中止對 PHP 5.x 的支援,但 2019/01 還是發布了 5.6 的更新,自動內容大致如下:

  • GD library 的 security fix
  • mbstring 的 bug fix
  • Phar 的 security fix
  • xmlrpc 的 security fix
還沒更新上 PHP 7.x 的人記得更新。

2018/10/30

近期撰寫程式碼的量

以前大多用 3rd-party library 來拼拼湊湊出自己要使用的功能,近一年狀況比較特殊,幾乎所有東西都要自幹:
$ cd /project/root/src
$ du -sh
148K    .

一年的 code 就 150 KB,算是自開始寫程式以來的個人記錄了吧。

希望這 150 KB 的程式是好的程式,而不是垃圾。

2018/10/17

PHP 5.6 將在 2018 年底中止維護

目前 PHP 5 最後一個承載維護的版本:5.6,將於今年年底中止維護。也就是不會再有新功能,且就算有錯誤、安全漏洞也不會再繼續修補。

所以建議大家盡快升級手邊的 PHP 專案至 PHP 7。



個人的經驗,可以跳過 7.0 直接上 7.1,畢竟 7.1 主要是新增一些 7.0 還不支援的功能,沒有他太大的異動。7.2 主要是對效能調整,和新增一些語法,要直接跳 7.2 也不會花太多時間改程式。

2018/10/15

PHP server connector 一些 un-documented behaviour

今天是測試 Memcache::connect() 踩到的問題。

先來看看文件上的說明:
Returns TRUE on success or FALSE on failure.
所以我的程式就這樣寫:
$c = new Memcache();
$stat = $c->connect('localhost');

if (false === $stat) {
    echo 'connection failed';
} else {
    echo 'connected to cache server';
}

測試時把 Memcached shutdown 來看看是否會偵測到連線失敗。執行時卻噴了一堆錯誤訊息:
Warning: Memcache::connect(): Can't connect to localhost:11211, Connection refused (111) in /home/u/he5702/tmp/asd.php on line 4

Call Stack:
    0.0001     355600   1. {main}() /home/u/he5702/tmp/asd.php:0
    0.0001     355640   2. Memcache->connect() /home/u/he5702/tmp/asd.php:4

connection failed

文件上面並沒有提到 library 除了回傳 false 以外,還會噴一堆錯誤訊息。萬一這隻是 JSON API 就一定會導致 client parse error。

所以應變方法用「@」來隱藏錯誤訊息:
$stat = @$c->connect('localhost');



寫程式真的很怕遇到 undefine behavior,如果文件上都寫清楚的話,在 dev / staging 上都可以事先檢查、防範。但連文件都沒有寫,就只能等個被廣大的客戶客訴到死 ....。

2018/08/29

PHP SplPriorityQueue .... WTF!

我還以為 PHP SPL 的類別應該實作上應該有比較嚴謹,但 ... 我錯了 QQ

先來看一下 SplPriorityQueue 的官方文件定義:
public void insert ( mixed $value , mixed $priority )
priority 和我預期的不一樣,原本以為只能放數字 (numbers) 但這邊的 priority type hint 卻標示為「mixed」,表示 priority 可以是任何資料型態 ...?


先從最一般的 case 來測試吧:
$q = new SplPriorityQueue();

$q->insert(1, 'A');
$q->insert(2, 'B');
$q->insert(3, 'C');
$q->insert(4, 'D');
$q->insert(5, 'E');

while ($q->count() > 0) {
    echo $q->extract() . PHP_EOL;
}

output:
5
4
3
2
1

看起來會用 ASCII 數值來比較,數值大的優先 dequeue。看起來沒問題。



再來看一下遇到相同 priority 中有多個數值時,到底會發生什麼事:
$q = new SplPriorityQueue();

$q->insert(1, 1);
$q->insert(2, 1);
$q->insert(3, 1);
$q->insert(4, 1);
$q->insert(5, 1);

while ($q->count() > 0) {
    echo $q->extract() . PHP_EOL;
}

output:
1
5
4
3
2

「5, 4, 3, 2」還算正常,但是為什麼「1」會突然跑到最前面!?



最後,priority 真的可以放其他資料型態嗎?為了更清楚的看到執行結果,我這邊改使用 psysh 來操作:
Psy Shell v0.8.18 (PHP 7.1.18 — cli) by Justin Hileman
Unable to check for updates
>>> $q = new SplPriorityQueue();
=> SplPriorityQueue {#201
     heap: [],
   }
>>> $q->insert(1, [1, 2, 3]);
=> true
>>> $q->insert(2, []);
=> true
>>> $q->insert(3, new Exception())
=> true
靠北啊,Exception 也可以當作 priority 使用 ....

>>> while ($q->count() > 0) {
... echo $q->extract() . PHP_EOL;
... }
3
1
2
.... 我不想講了。

總之,若你要使用 SplPriorityQueue,請注意 undefined behavior。


Reference:

2018/07/30

PHP array_keys() 會自動轉換資料型態

給定一個 array:
$list = [
    '200' => 'OK',
    '404' => 'not found',
    '500' => 'internal server error',
];

使用 array_keys() 取得 keys 以後,key 的資料型態若可以被轉為 int 則會被自動轉換:
$keys = array_keys($list);

// array(3) {
//   [0]=>
//   int(200)
//   [1]=>
//   int(404)
//   [2]=>
//   int(500)
// }

文件中可以看到,透過第三個參數「$strict」可以要求保留原始資料型態,但無法避開第二個參數「$search_value」。所以第三個參數基本上是放好看的 (WTF)

如果要處理的資料是比較敏感的,建議在 key 前面加個 prefix,或是取得 keys 以後再手動轉換資料型態。

2018/06/14

PHP 的 Memcache 與 Memcached 函式庫無法共用資料

剛接觸的應該會被命名搞的一頭誤水,畢竟名字相同、不然就是幾乎相同 XD

負責提供記憶體讀寫快取的服務叫做 Memcached,這個服務和 PHP 八竿子打不著關係,就算你不是寫 PHP 也這樣可以使用他。

再來要說的是 PHP 底下的二個函式庫:MemcacheMemcached。這二個函式庫,主要是提供一個方法讓 PHP 連接前面提到的 Memcached 讀寫資料。另外要注意的是 Memcache 和 Memcached 函式庫雖然名字很接近,但卻是二個完全不同的函式庫,無法同時使用,只能二選一。

以下透過 psysh 實際操作,來看一下同時使用會發什麼事情。

使用 Memcache 寫入一筆資料:
>>> $m = new Memcache
=> Memcache {#201}
>>> $m->connect('localhost')
=> true
>>> $m->set('test', ['Hello', 'World'])
=> true
>>> $m->get('test')
=> [
     "Hello",
     "World",
   ]

再來使用 Memcahed 函式庫,連到 localhost 的 Memcached server 看看會發生什麼事:
>>> $d = new Memcached
=> Memcached {#198}
>>> $d->addServer('localhost', 11211)
=> true
>>> $d->getAllKeys()
=> [
     "test",
   ]
>>> $d->get('test')
=> 0

可以看出這二個函式庫提供的 methods 看起來很接近,但是底層處理資料的方式不一樣,只能挑其中一個來使用。

2018/05/31

由網頁觸發、執行 PHPUnit

通常都是在 console 底下執行 PHPUnit 跑測試:
$ phpunit Test.php 
... Run by vendor/bin/phpunit ...
PHPUnit 7.1.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 30 ms, Memory: 4.00MB

OK (1 test, 1 assertion)

但若開發環境無法使用 console 的話,只能靠寫 PHP script 來 require PHPUnit library 來執行。在這邊發現其實 PHPUnit 有保留 interface 讓使用者可以不透過 console 來操作 PHPUnit,可以在 PHP script 裡面 require library 來使用。

先使用 composer 把 library 拉下來:
composer require 'phpunit/phpunit'

在 PHPUnut/TextUI 底下,有實作幾個 controller 來模擬 console 的操作。以下使用 Command 來執行 PHPUnit:
<?php

require __DIR__ . '/vendor/autoload.php';

use PHPUnit\TextUI\Command;

$cmd = new Command();

$cmd->run([
    'phpunit',  // 1st arg
    'Test.php', // 2nd arg
]);

如果執行上面這段 PHP script,會得到與在 console 執行 PHPUnit 一樣的結果。
不過要注意的是測試結果會直接輸出到 std out,若另外處理的話,記得用 output buffer 把資料拉回來,直接在瀏覽器看的話會破版。

備註:PHPUnit 不同版本的路徑不同,類別名稱也會不同。

2018/05/24

PHP 7.1 的 Symmetric Array Destructuring

PHP 7.1 對 list() 語法做了一些調整,讓 list() 操作使用起來更簡便。

原本的 list() 語法:
list($a, $b) = [1, 2];
// $a = 1, $b = 2

在 7.1 以後可以使用陣列來表示:
[$a, $b] = [1, 2];

這個語法稱為 Symmetric Array Destructuring

有了這個 feature 以後,不用另外宣告暫存變數也可以對調變數 (swap):
[$b, $a] = [$a, $b];

2018/05/07

PHP array 新增 element 的方法和效率

新增 element 到 PHP array 的寫法有二種,一種是用 array_push(),一種是使用 $list[] 直接新增。

跑個迴圈塞一千萬筆資料進 array,看看哪一種寫法的速度比較快。

這個是 array_push() 的:
$list = [];

$start = microtime(true);

for ($a = 0; $a < 10000000; $a++ ) {
    array_push($list, 1);
}

$end = microtime(true);

var_dump($end - $start);  // double(7.2292509078979)


同上,若將語法換掉的話:
$list = [];

$start = microtime(true);

for ($a = 0; $a < 10000000; $a++ ) {
    $list[] = 1;
}

$end = microtime(true);

var_dump($end - $start);  // double(2.0703480243683)


小量資料的話,其實二種作法在速度上不會差很多,但資料量變大時,速度差異就會變得很明顯。

2018/03/09

PHP 中 「...」(three dots) 也可以用在 argument provider

看了 PHPUnit 的 source code,發現有個很有趣的寫法
function assertSame($expected, $actual, string $message = ''): void
{
    Assert::assertSame(...\func_get_args());
}

最初以為「...」只能用在定義 variable-length function argument/param,重新看了文件才發現之前文件沒有看完,也可以拿來把陣列分別當作 argument/param 傳入:
function add($a, $b) {
    return $a + $b;
}

add(...[1, 2]);  // $a = 1, $b = 2

// or ...

$args = [1, 2];
add(...$args);

2018/03/08

PHPUnit 中「backupGlobals」的作用

複習 PHPUnit 順手寫了小程式做實驗。

PHPUnit 為了讓各個不同的 test case 不會因為 super globals 變數而互相影響,設計了 「backupGlobals」這個功能。先來看範例程式:
class MyTest extends TestCase
{
    public function testOne()
    {
        $GLOBALS['test'] = 123;

        $this->assertArrayHasKey('test', $GLOBALS);
    }

    public function testTwo()
    {
        $this->assertArrayHasKey('test', $GLOBALS);
    }
}

範例中 testOne() 修改了 $GLOBALS 變數的內容,如果在不使用「backupGlobals」的情況下,在 testTwo() 中的 $GLOBALS 也會保留 testOne() 修改過的內容,使 testTow() 抓得到「test」這個 key:
$ phpunit MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 40 ms, Memory: 4.00MB

OK (2 tests, 2 assertions)

也就表示 testOne() 影響到了 testTwo() 的測試結果,這在測試時是不樂見的,各個測試應該有獨立的測試環境,不應該互相影響。

使用「backupGlobals」功能,PHPUnit 會在 test case 執行之前,使用 serialize() 對 super globals 做備份,並在測試結束以後 unserialize() 復原,所以 testOne() 中做的修改就不會影響到 testTwo() 了:
$ phpunit --globals-backup MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 52 ms, Memory: 4.00MB

There was 1 failure:

1) MyTest::testTwo
Failed asserting that an array has the key 'test'.

/home/johnroyer/tmp/MyTest.php:16

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

重點來了,因為是使用 serialize() 和 unserialize(),所以只能針對 primitive type 進行備份和還原,如果使用了 object 或是 singleton 時,「backupGlobals」就派不上用場了。這個時候要使用殺手鐧「runTestsInSeparateProcesses」:
/**
 * @runTestsInSeparateProcesses
 */
class MyTest extends TestCase
{
    ....
}

使用這個 annotation 會讓 PHPUnit 為每個 test case 分別 fork 出新的 proccess 進行測試,因此不同 test case 就可以在完全獨立的環境下執行不受影響:
$ phpunit MyTest.php 
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 303 ms, Memory: 4.00MB

There was 1 failure:

1) MyTest::testTwo
Failed asserting that an array has the key 'test'.

/home/johnroyer/tmp/MyTest.php:19

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

但使用這個殺手鐧也是要付出一點代價:時間。最前二次執行 PHPUnit 時,跑完二個 test case 只花費了 50ms 左右,而開 proccess 來進行測試則花了 300ms。