{ php }

  • [php] grapheme_strlen vs mb_strlen vs strlen

    | /

    前言

    小時候在寫 PHP 時要看字串長度,常常不懂事的用 strlen 來作判斷,但是在遇到中日韓這種會把 2 bytes 當一個字的長度時,這時候字串的長度又不準了。這時學會了使用 mb_strlen 這個 function,感覺長度這樣用,就準了。但有趣的事是,竟然近期又出現了 grapheme_strlen 。崩潰,不過就是算個長度而已,為什麼有這麼多的方法。

    讓我們來寫個小程式來實驗一下

    [code lang=”php”] <?php $str = “這是中文 english 混合測試”; var_dump($str); var_dump( mb_strlen($str, ‘UTF-8’), grapheme_strlen($str), strlen($str) ); ?> [/code] 結果如下: [code lang=”bash”] $ php /tmp/test.php string(33) “這是中文 english 混合測試” int(17) int(17) int(33) [/code] 看起來很合理啊,用 strlen 結果是 33 個字元,因為他是算 bytes。mb_strlen 17 個字,即使是中英文混合,看起來算字數也是沒問題的!mb_strlen 和 grapheme_strlen 目前看起來結果是一樣的。

    接下來再一個小實驗

    [code lang=”php”] <?php $str = json_decode(‘“e\u0301 = \u00E9”‘); var_dump($str); var_dump( mb_strlen($str, ‘UTF-8’), grapheme_strlen($str), strlen($str) ); ?> [/code] 結果如下: [code lang=”bash”] $ php /tmp/test.php string(8) “é = é” int(6) int(5) int(8) [/code] WTF,竟然有三個不同的數字,畫面上來看,明明就只有五個字,為什麼 mb_strlen 會算出來六個字呢!

    原來是 Grapheme Cluster 在作怪

    Grapheme Cluster 就類似中文一樣,會把二個字當作一個字。所以畫面上看起來是一個字,但實際上在 unicode 是可以用二個字來表示,所以可以看見範例二中顯示的字是一樣,但 unicode 是不同的。而這樣就會造成 mutlibyte 的 function 計算長度有問題。

    結論就是….

    如果有考慮中日韓以外的語系的話,可以使用 grapheme_strlen 。另外必須要注意,如果傳進來的參數不是 utf8 的字的話,他的回傳值是 null ,而這部份與 mb_strlen 的結果不一致。

    參考文章

  • [php] mock 有 reference parameter 的 method

    | /

    前言

    雖然盡量不要在 php 裡面寫 call by reference 的參數,但是有一些舊有的code 還是有人這樣寫。如果這時又想要mock 他就麻煩了,因為值並不會隨著自已想要的內容作改變。

    實作

    原先的 class 設計如下

    [code lang=”php”] <?php class NeedToMockClass { function testReference(&$str) { // 也許會有很多很複雜的計算,不過這邊簡化成一行就好 $str = “test String”; return; } } class testClass{ function test($str) { $t = new NeedToMockClass(); $t->testReference($str); return __METHOD__ .’->’ . $str; } } // $t = new testClass(); // $str = $t->test(‘not a test string’); // var_dump($str); // string(28) “testClass::test->test String” [/code]

    How To Test?

    如果這時我要測 testClass 我該如何測?因為我單純的只想測 testClass 裡的邏輯對不對,我不想管 NeedToMockClass 的邏輯,所以這時我該把 class mock 掉,並且讓他可以自定 $str 的 output。 首先,會先改寫 testClass 讓他可以從外面傳 NeedToMockClass 的 object 進去。 [code lang=”php”] <?php class testClass{ private $_mockClass = null; // 由 construct 傳進來,讓他可以取代掉預設的 mockClass function __construct($mockClass == null) { if ($mockClass = null) { $mockClass = new NeedToMockClass(); } $this->_mockClass = $mockClass; } function test($str) { // 接著用mock 後的class 來執行 $this->_mockClass->testReference($str); return __METHOD__ .’->’ . $str; } } [/code] 最後測試程式會用下面這個方法寫,主要是用 returnCallback 的方式來 mock: [code lang=”php”] <?php require_once ‘./sample.php’; class testTest extends PHPUnit_Framework_TestCase { function mockTestReference(&$str) { $str = ‘3345678’; } function testTestClass() { $mockClass = $this->getMock(‘NeedToMockClass’, array(), array(), “” ,false); // 這裡是用 returnCallback 的方式呼叫 testTest::mockTestReference 這樣就可以改 $str 的內容了 $mockClass->expects($this->any()) ->method(‘testReference’) ->will($this->returnCallback(array(‘testTest’, ‘mockTestReference’))); $t = new testClass($mockClass); $str = ‘one test’; $ret = $t->test($str); // 最後會如預期的產生 testClass::test->3345678 的字樣 $this->assertEquals(‘testClass::test->3345678’, $ret); } } [/code] 順利達成目標,其結果如下: [code lang=”bash”] $ phpunit testTest.php PHPUnit 3.7.35 by Sebastian Bergmann. . Time: 22 ms, Memory: 3.50Mb OK (1 test, 2 assertions) [/code]

    參考文件

  • 使用 Codeigniter 的 Pagination 且帶入不定長度查詢參數的方法

    | /

    在官方的Pagination Class 中, 用 page 的參數來控制本頁目前的起始行數。但範例裡面使用的方法如下:

    1
    2
    3
    4
    5
    $this->load->library('pagination'); 
    $config['base_url'] = 'http://example.com/index.php/test/page/';
    $config['total_rows'] = 200;
    $config['per_page'] = 20;
    $this->pagination->initialize($config);

    所以原始的方法所產生的 url 就是 http://example.com/index.php/test/page/{row number} 這種格式。在參數是固定長度下,你可以輕易的知道在第幾個位置是分頁的依據,即可以使用 $config[‘uri_segment’] 來指定位置。

    但如果參數為不固定數量,哪怎麼辦?使用 uri_segment 就沒有作用了,因為你也不知道第幾個位置是起始行數,而且起始行數只會加在 url 的最後面。

    下面提供一個方法可以 override 掉原先的目前所抓取的起始行數的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $this->load->library('pagination'); // 中間可以加很多的東西,但 page 一定要放在最後面
    $config['base_url'] = 'http://example.com/index.php/test/'.blalbala.'/page/';
    $config['total_rows'] = 200;
    $config['per_page'] = 20;
    // 新增下面的程式。
    $config['uri_segment'] = 0;
    // 不指定 row 的 url 位置
    $q = $this->uri->uri\_to\_assoc(3);
    // 把目前的參數轉成array
    $config['cur_page'] = $q['page'];
    // override 目前的 row
    $this->pagination->initialize($config);
    echo $this->pagination->create\_links();

    這樣你的參數的長度就不需要固定了。這對於帶入參數的 search ,或是 list 的頁面,在使用上較為方便,也不用作什麼 dirty hack。

    actos price