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

2020年2月13日 星期四

[PHP] 網頁顯示 UTF-8 轉 BIG5

UTF-8 有很多字元是 BIG5 無法支援,但是有時候又需要使用 BIG5,就需要特別轉成 HTML Entities。

mb_substitute_character('entity');
echo mb_convert_encoding('許功蓋開飛機♥⬅️', 'BIG5', 'UTF-8');
// 許功蓋開飛機♥⬅️

2018年5月28日 星期一

[PHP] unique values & resort keys

function calc($t0, $t1)
{
    $v0 = array_sum(explode(' ', $t0));
    $v1 = array_sum(explode(' ', $t1));
    $sub = $v1 - $v0;
    echo "$v1 - $v0 = $sub\n";
    return $sub;
}

$a = [116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,116,117,116,117,116,116,116,116,116,119];

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_values(array_unique($a));
}
$t1 = microtime();
$method1 = calc($t0, $t1);     // output: 1527477436.7125 - 1527477409.434 = 27.278562068939

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_keys(array_flip($a));
}
$t1 = microtime();
$method2 = calc($t0, $t1);     // output: 1527477439.6214 - 1527477436.7126 = 2.908814907074

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    array_merge(array_flip(array_flip($a)));
}
$t1 = microtime();
$method3 = calc($t0, $t1);     // output: 1527477443.3256 - 1527477439.6215 = 3.7041912078857

有時候直覺的方式會比較慢。

參考資料:http://php.net/manual/en/function.array-unique.php#70786

PHP 5.6.36 (cli) (built: May  9 2018 20:31:47)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Xdebug v2.3.3, Copyright (c) 2002-2015, by Derick Rethans

2016年11月23日 星期三

[PHP] SQLSTATE[HY000]: General error: 2006 MySQL server has gone away

近日遇到稍微罕見的案例:MySQL server has gone away

PHP Warning:  PDO::exec(): MySQL server has gone away in /.../A.inc on line 104
PHP Warning:  PDO::exec(): Error reading result set's header in /.../A.inc on line 104
PHP Fatal error:  Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error: 2006 MySQL server has gone away' in /.../A.inc:104
Stack trace:
#0 /.../A.inc(104): PDO->exec('SET NAMES utf8')
...

由於這是在執行排程的時候遇到,查詢之後還是決定從 PHP 著手,避免影響 APIs 效能。

下面是處理方式;拿掉註解則可以測試狀況。

$pdo = $this->getPdo();
try {
   // $pdo->exec('SET wait_timeout=1');
   // sleep(3);
   $pdo->exec('SET NAMES utf8');
} catch (PDOException $e) {
   $errInfo = $pdo->errorInfo();
   if ($errInfo[0] === 'HY000' && $errInfo[1] === 2006) {
      // SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
      $this->resetPdo();
      $pdo = $this->getPdo();
      $pdo->exec('SET NAMES utf8');
      return $pdo;
   }
   throw $e;
}
return $pdo;

參考資料:

2016年6月7日 星期二

[PHP] empty() / isset() on String Offsets

一般判斷陣列是否有鍵約莫就是 array_key_exists()、empty()、isset()。 最近遇到的問題是,如果需要判斷多於兩層的鍵存不存在,會出現 Fatal error:

$a = '';
var_dump(empty($a['1']['2'])); // bool(true)
var_dump(isset($a['1']['2'])); // bool(false)
var_dump(empty($a['1']['2']['3'])); // PHP Fatal error:  Cannot use string offset as an array
var_dump(isset($a['1']['2']['3'])); // PHP Fatal error:  Cannot use string offset as an array

$a['1'] = '';
var_dump(empty($a['1']['2']['3'])); // bool(true)
var_dump(isset($a['1']['2']['3'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4'])); // PHP Fatal error:  Cannot use string offset as an array
var_dump(isset($a['1']['2']['3']['4'])); // PHP Fatal error:  Cannot use string offset as an array

$a['1']['2'] = '';
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4']['5'])); // PHP Fatal error:  Cannot use string offset as an array
var_dump(isset($a['1']['2']['3']['4']['5'])); // PHP Fatal error:  Cannot use string offset as an array

但是如果有一個鍵不匹配,就不會發生問題:

$a['x'] = '';
var_dump(empty($a['1']['2']['3'])); // bool(true)
var_dump(isset($a['1']['2']['3'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)

$a['1']['y'] = '';
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4']['5'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4']['5'])); // bool(false)

$a['x']['2'] = '';
var_dump(empty($a['1']['2']['3']['4'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4'])); // bool(false)
var_dump(empty($a['1']['2']['3']['4']['5'])); // bool(true)
var_dump(isset($a['1']['2']['3']['4']['5'])); // bool(false)

2015年10月13日 星期二

[PHP] 換行造成的災難

關於 ?> end tag,官網有這麼一段話,建議不要寫:

The closing tag of a PHP block at the end of a file is optional, and in some cases omitting it is helpful when using include or require, so unwanted whitespace will not occur at the end of files, and you will still be able to add headers to the response later. It is also handy if you use output buffering, and would not like to see added unwanted whitespace at the end of the parts generated by the included files.

最近遇到一件慘案。

伺服器A要呼叫伺服器B的一隻 API,因為早期開發比較沒有固定做法,所以把整個資料進行加密回傳給伺服器A。 然後有一天突然說解密錯誤。

測試的時候可以看到,伺服器A拿到的資料會換行,但是伺服器B傳出的時候並沒有換行。示意如下:

// 伺服器A
[Tue Oct 13 11:27:39 2015] [error] [client 127.0.0.1] 
4fCfBODGX4qDIcLjcc0NqbY-

// 伺服器B
[Tue Oct 13 11:27:39 2015] [error] [client 127.0.0.1] 4fCfBODGX4qDIcLjcc0NqbY-

// handler.php
<?php
require_once 'XYZ.php';
// ...

header('Content-Type: application/octet-stream');
header('HTTP/1.0 200');

echo encrypt_data($output);
?>
檢查伺服器B,看起來沒什麼改動。找了很久之後才發現,有人在某隻依賴的 PHP 檔案的 end tag 後面加了一行:
// ABC.php
<?php
// ...
?>

一個小疏忽會累死別人。

2015年1月9日 星期五

[PHP] is_array() vs. identical (===)

function calc($t0, $t1)
{
    $v0 = array_sum(explode(' ', $t0));
    $v1 = array_sum(explode(' ', $t1));
    $sub = $v1 - $v0;
    echo "$v1 - $v0 = $sub\n";
    return $sub;
}

$a = array();
$b = array(1);
$c = '';

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    is_array($a);
    is_array($b);
    is_array($c);
}
$t1 = microtime();
$method1 = calc($t0, $t1);     // output: 1420785145.2006 - 1420785142.9861 = 2.2144329547882

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    $a === (array) $a;
    $b === (array) $b;
    $c === (array) $c;
}
$t1 = microtime();
$method2 = calc($t0, $t1);     // output: 1420785142.986 - 1420785142.2001 = 0.78591012954712

echo ($method1 - $method2) / $method1; // output: 0.64509644428486

從測試中可以知道 is_array() 比較慢。

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2014年12月1日 星期一

[PHP] parent::__construct()

class A {}
class B extends A {
    public function __construct() {
        parent::__construct(); // PHP Fatal error:  Cannot call constructor in test.php on line 5
    }
}
new B();

執行上面的程式會發生錯誤。

在 Java 這樣寫是合法的,由此可知 PHP 把 __construct() 當成一種特殊方法使用, 因此既有方法的功能,有又建構子的功能。

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2014年7月8日 星期二

[PHP] 轉換 HTML entity 在字串中

在處理 BIG5 轉換 UTF-8 的過程中,字串還存在一些 HTML entity 需要額外處理。 以下還順便處理缺省分號造成沒有轉換的問題。

function entity2utf8($s)
{
   return preg_replace_callback('|&#\d+;?|', function ($matches) {
         $match = $matches[0];
         if (mb_strpos($match, ';') === false) {
             $match .= ';';
         }
         return mb_convert_encoding($match, 'UTF-8', 'HTML-ENTITIES');
      }, $s);
}

$s ='英國皇冠♔';
echo entity2utf8($s); // output: 英國皇冠♔
PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2014年5月28日 星期三

[PHP] stream resource output

最近看到有一段代碼是要輸出 CSV 檔案:

$rows = array();
$rows[] = array(1,2,3,"四");

ob_start();
$fh = fopen("php://output", "w");
foreach ($rows as $r) {
   fputcsv($fh, $r);
}
fclose($fh);
$csv = ob_get_clean();

var_dump($csv);

從上述代碼看到,轉換的方式是使用 Output Control Functions。

其實這麼寫並不妥當。整段代碼被 ob 給限制住了,如果還要做一些錯誤處理,會使得代碼變得相當複雜。 而且個人相當排斥使用 ob。

有寫過 Java 應該都會先想到 java.io 提供的 InputStream。 所以試著查詢 PHP Manual,找到相似的方法,重新改寫一下:

$rows = array();
$rows[] = array(1,2,3,"四");

$fh = fopen("php://temp", "w+");
foreach ($rows as $r) {
   fputcsv($fh, $r);
}
rewind($fh);
$csv = stream_get_contents($fh);
fclose($fh);

var_dump($csv);

去掉 ob 之後,整個資料流就都在 stream resource 處理,看起來會比較適當。 日後有需要修改或除錯也都變得比較容易多。

參考資料:http://www.php.net/manual/en/function.stream-get-contents.php

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2014年5月8日 星期四

[PHP] usort()

這次要來討論 usort()。

我們假設以下情況依照水果名稱排序:

$fruits = array(
   array('id' => 1, 'name' => 'apple'),
   array('id' => 2, 'name' => 'orange'),
   array('id' => 3, 'name' => 'banana'),
   array('id' => 4, 'name' => 'banana'));

usort($fruits, function ($a, $b) {
   if ($a['name'] === $b['name']) { return 0; }
   return $a['name'] < $b['name'] ? -1 : 1;
});

/**
 * Output:
 * $fruits = array(
 *    array('id' => 1, 'name' => 'apple'),
 *    array('id' => 4, 'name' => 'banana'),
 *    array('id' => 3, 'name' => 'banana'),
 *    array('id' => 2, 'name' => 'orange'));
 */
結果似乎有點不符預期,banana (id=3) 不是出現在 banana (id=4) 前面。

再測試一次,這次改為回傳總是 0,即左右等值,所以預期順序應該不會有任何變化。 但是做完 usort() 之後,結果卻是幫我們進行一次反向排序。

usort($fruits, function ($a, $b) { return 0; });

/**
 * Expect:
 * $fruits = array(
 *    array('id' => 1, 'name' => 'apple'),
 *    array('id' => 2, 'name' => 'orange'),
 *    array('id' => 3, 'name' => 'banana'),
 *    array('id' => 4, 'name' => 'banana'));
 * Acutal:
 * $fruits = array(
 *    array('id' => 4, 'name' => 'banana'),
 *    array('id' => 3, 'name' => 'banana'),
 *    array('id' => 2, 'name' => 'orange'),
 *    array('id' => 1, 'name' => 'apple'));
 */

查閱 PHP Manual 之後, 發現到這段說明:

Note: If two members compare as equal, their relative order in the sorted array is undefined.
原來等值並沒有規定怎麼處理。

由於等值並沒有規範,所以我們需要再做一些判斷,才會是我們預期的結果。

usort($fruits, function ($a, $b) {
   if ($a['name'] === $b['name']) { return $a['id'] > $b['id']; }
   return $a['name'] < $b['name'] ? -1 : 1;
});

/**
 * Output:
 * $fruits = array(
 *    array('id' => 1, 'name' => 'apple'),
 *    array('id' => 3, 'name' => 'banana'),
 *    array('id' => 4, 'name' => 'banana'),
 *    array('id' => 2, 'name' => 'orange'));
 */

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2014年5月2日 星期五

[PHP] 轉換資料 Ⅱ:for-loop vs. array_reduce()

/**
 * Origin:
 *  array(
 *    array('id' => 'x', 'name' => 'apple'),
 *    array('id' => 'y', 'name' => 'banana'),
 *    array('id' => 'z', 'name' => 'orange'),
 *  );
 * Expect:
 *  array(
 *    'x' => 'apple',
 *    'y' => 'banana',
 *    'z' => 'orange',
 *  );
 */
function calc($t0, $t1)
{
   $v0 = array_sum(explode(' ', $t0));
   $v1 = array_sum(explode(' ', $t1));
   $sub = $v1 - $v0;
   echo "$v1 - $v0 = $sub\n";
   return $sub;
}

$origin = array(
   array('id' => 'x', 'name' => 'apple'),
   array('id' => 'y', 'name' => 'banana'),
   array('id' => 'z', 'name' => 'orange'));

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
   $expect = array();
   foreach ($origin as $o) {
      $expect[$o['id']] = $o['name'];
   }
}
$t1 = microtime();
$method1 = calc($t0, $t1);     // output: 1420770579.8306 - 1420770578.0456 = 1.785040140152

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
   $expect = array_reduce($origin, function ($result, $item) {
      $result[$item['id']] = $item['name']; return $result; });
}
$t1 = microtime();
$method2 = calc($t0, $t1);     // output: 1420770588.2167 - 1420770579.8308 = 8.3859748840332

echo ($method2 - $method1) / $method2; // output: 0.78713981798936

從測試中可以知道,使用 array_reduce() 比之 for-loop 較慢,雖然看起來比較簡潔。

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2014年4月3日 星期四

[PHP] 轉換資料:for-loop vs. array_map()

/**
 * Origin:
 *  array(
 *    array('id' => 'x'),
 *    array('id' => 'y'),
 *    array('id' => 'z'),
 *  );
 * Expect:
 *  array('x', 'y', 'z');
 */
function calc($t0, $t1)
{
   $v0 = array_sum(explode(' ', $t0));
   $v1 = array_sum(explode(' ', $t1));
   $sub = $v1 - $v0;
   echo "$v1 - $v0 = $sub\n";
   return $sub;
}

$origin = array(array('id' => 'x'), array('id' => 'y'), array('id' => 'z'));

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
   $expect = array();
   foreach ($origin as $o) {
      $expect[] = $o['id'];
   }
}
$t1 = microtime();
$method1 = calc($t0, $t1);     // output: 1420770195.046 - 1420770193.6821 = 1.3638761043549

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
   $expect = array_map(function ($o) { return $o['id']; }, $origin);
}
$t1 = microtime();
$method2 = calc($t0, $t1);     // output: 1420770202.2368 - 1420770195.0461 = 7.1907830238342

echo ($method2 - $method1) / $method2; // output: 0.81032995991755

從測試中可以知道 array_map() 比 for-loop 慢。不過 array_map() 寫法比較簡潔。

補充,引用外部參數:

   $key = 'id';
   $expect = array_map(function ($o) use ($key) { return $o[$key]; }, $origin);
PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2014年1月21日 星期二

[PHP] __DIR__ vs. dirname(__FILE__)

PHP 5.3 開始支援 __DIR__,從測試中可以知道 __DIR__ 略快于 dirname(__FILE__)。

function calc($t0, $t1)
{
   $v0 = array_sum(explode(' ', $t0));
   $v1 = array_sum(explode(' ', $t1));
   $sub = $v1 - $v0;
   echo "$v1 - $v0 = $sub\n";
   return $sub;
}
$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) require_once __DIR__.'/a.php';
$t1 = microtime();
$new = calc($t0, $t1);     // output: 1420769613.0535 - 1420769612.1561 = 0.89736700057983

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) require_once dirname(__FILE__).'/a.php';
$t1 = microtime();
$old = calc($t0, $t1);     // output: 1420769614.9498 - 1420769613.0548 = 1.8950188159943

echo ($old - $new) / $old; // output: 0.52646011057731

參考資料:http://www.php.net/manual/en/language.constants.predefined.php

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2013年12月9日 星期一

[PHP] dollar sign ($) 的陷阱

// == case 1 ==
$foo = 'foo';$
$bar = 'bar';
// expect: $foo = 'foo', $bar = 'bar'
// actual: $foo = 'foo', $bar = NULL
// hint:   PHP Notice: undefined variable: bar in test.php on line 4

// == case 2 ==
$foo = 'foo';$
bar  = 'bar';
// actual: PHP Parse error:  syntax error, unexpected T_STRING, expecting T_VARIABLE or '$' in test.php on line 3

// == case 3 ==
$foo = 'foo';$
// actual: PHP Parse error:  syntax error, unexpected $end, expecting T_VARIABLE or '$' in test.php on line 2

可以發現,在一些特定情形下,PHP 允許換行使用錢號 ($),但是不容易發現。 所以任何 PHP 的訊息應該都要注意。

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

2013年11月19日 星期二

[PHP] Call By Reference 的陷阱

class ClassA
{
   private $fields;

   public function test1()
   {
      $this->fields = array(1,2,3);
      foreach ($this->fields as &$v) {
          $v += 1;
      }
                
      $localFields = array('x','y','z');
      foreach ($localFields as $v) {
      }
      echo json_encode($this->fields); // expect: [2,3,4], output: [2,3,"z"]
   }
                
   public function test2()
   {
      $this->fields = array(1,2,3);
      foreach ($this->fields as &$v) {
          $v += 1;
      }

      $v = 'abc';
      echo json_encode($this->fields); // expect: [2,3,4], output: [2,3,"abc"]
   }
                
   public function test3()
   {
      $this->fields = array(1,2,3);
      foreach ($this->fields as &$v) {
          $v += 1;
      }

      $this->inner();
   }

   private function inner()
   {
      $v = '***';
      echo json_encode($this->fields); // output: [2,3,4]
   }
}

$obj = new ClassA();
$obj->test1();
$obj->test2();
$obj->test3();

上面可以看到,雖然第一次使用 &$v 是在迴圈中,第二次使用 $v是在另一個迴圈。 原本預想應該是互不相關,但是顯然 Call By Reference 影響到第一次使用的欄位,造成出乎意料的狀況。


解決的方法:使用完 &$v 之後就馬上 unset($v); 缺點是可能會看不懂為什麼這樣做。

比較好的作法還是改用索引存取:

foreach ($this->fields as $k => $v) {
    $this->fields[$k] = $v + 1;
}

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

[Java] Invalid HTTP method: PATCH

最近系統需要使用 Netty4,所以把衝突的 Netty3 拆掉,然後就出現了例外。 pom.xml <dependency> <groupId>com.ning</groupId> <artifactId>as...