Jex’s Note

PHP Examples

轉換容量顯示方式 (ex: 15.31GB)

function reable_size($bytes)
{
    if ($bytes == 0)
    {
        return '0 MB';
    }

    $unit = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB');

    $show_size = $bytes / pow(1024, ($i = floor(log($bytes, 1024))));
    if (intval($show_size) == $show_size)
    {
        return $show_size . ' ' . $unit[$i];
    }

    return number_format($show_size, 2) . ' ' . $unit[$i];
}

IP:

function get_ip()
{
    $inspect_type = array(
        "HTTP_CLIENT_IP",
        "HTTP_X_FORWARDED_FOR",
        "HTTP_X_FORWARDED",
        "HTTP_X_CLUSTER_CLIENT_IP",
        "HTTP_FORWARDED_FOR",
        "HTTP_FORWARDED"
    );

    foreach ($inspect_type as $type)
    {
        if (array_key_exists($type, $_SERVER))
        {
            $ips = explode(",", $_SERVER[$type]);
            foreach ($ips as $ip)
            {
                 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))
                 {
                     return $ip;
                 }
            }
        }
    }
    return $_SERVER["REMOTE_ADDR"];
}

產生唯一字串(共32個小寫英文及數字)

echo md5(uniqid(rand(), TRUE));

產生亂數陣列(每個數字唯一,srand指定亂數產生)

php > $a = range(0, 29);
php > srand(333);           <= 隨意指定隨機數字
php > shuffle($a);          <= 按照隨機數字做排序
php > echo print_r($a);
Array
(
    [0] => 26
    [1] => 21
    (..略..)
    [28] => 15
    [29] => 24
)

判斷是否為ajax 傳進來的值(而不是submit傳入)

if (isset($_SERVER['HTTP_X_REQUESTED_WITH']))
{
    if (mb_strtoupper($_SERVER['HTTP_X_REQUESTED_WITH']) === "XMLHTTPREQUEST")
    {
        // do something...
        exit;
    }
}

日期與時間

設定台北時區

date_default_timezone_set("Asia/Taipei");

現在的時間(秒)

time();                                         //13551194547

將日期時間轉換為秒

strtotime('05/29/2013');

獲取目前的日期與時間

date('Y-m-d H:i:s');                            //2012-12-11 02:55:08

日期加1天、加5個月

date('Y-m-d', strtotime('+1 day'));             //加一天
date('Y-m-d', strtotime('+5 month'));           //加五個月

將時間(秒)還原回日期與時間正常格式

strftime('%m/%d/%Y %H:%M:%S', 1283846400);      //09/07/2010 16:00:00

產生時間數字型態(同strtotime)

mktime(0, 0, 0, date('m'), date('d'), date('Y')) + (60 * 60 * 24 * $day_offset);

Timestamp 產生至小數位

microtime(true);    // 1504695682.0988

註: .0000 也不會出錯
1504695796.9996
1504695796.9998
1504695796.9999
1504695797.0001
1504695797.0003
1504695797.0004

1504695867.9995
1504695867.9997
1504695867.9999
1504695868
1504695868.0002
1504695868.0004
1504695868.0005

DateTime 產生至小數位(推薦此方法)

$now = DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
echo $now->format('Y-m-d H:i:s.u')."<br>";

DateTime 產生至小數位 DateTime createFromFormat error: call to a member function format() on boolean

不要用 $now = DateTime::createFromFormat('U.u', microtime(TRUE)) 當0000, $now->format('Y-m-d H:i:s.u') 會得到 fatal error : call to a member function format() on boolean

即使用 if (is_bool($now)) $now = DateTime::createFromFormat('U.u', $aux += 0.001) 在超過 9999 有一段極短的時間日期會是 1970-01-01

    2017-08-25 03:06:59.999900
    2017-08-25 03:06:59.999900
    2017-08-25 03:06:59.999900
    1970-01-01 00:00:00.001000
    1970-01-01 00:00:00.002000
    1970-01-01 00:00:00.003000
    1970-01-01 00:00:00.004000

DateTime 使用 time zone

$timezone = new DateTimeZone("Asia/Taipei");
$time = new DateTime("now", $timezone);
$now = $time->format("Y-m-d H:i:s");

DateTime 使用 time zone 取前一分鐘

$timezone = new DateTimeZone("Asia/Taipei");
$time = new DateTime("1 minutes ago", $timezone);
$timestamp = $time->format("YmdHi");

Convert time and date from one time zone to another

$date = new DateTime('2000-01-01', new DateTimeZone('Pacific/Nauru'));
echo $date->format('Y-m-d H:i:sP') . "\n";

$date->setTimezone(new DateTimeZone('Pacific/Chatham'));
echo $date->format('Y-m-d H:i:sP') . "\n";

刪除test.txt檔再將日期寫入test.txt檔100000次

$ts = array_sum(explode(" ", microtime()));

unlink("./test.txt");
for ($i = 0; $i < 100000; ++$i) {
    file_put_contents("./test.txt", date("Y-m-d H:i:s"), FILE_APPEND);
}
echo array_sum(explode(" ", microtime())) - $ts, "\n";

FILE_APPEND參數可參考php manual

輸出 csv 供下載

產生時間做為檔名,並且開啟檔案,檔案若不存在會自行建立

$file_name = array_sum(explode(" ", microtime())) . '.csv';
$file_path = '/var/www/csv/' . $file_name;
$fp = fopen($file_path, 'w');

將陣列寫入csv檔

fputcsv($fp, array('field1', 'field2', 'field3');
foreach ($record as $row)
{
    fputcsv($fp, array($row[1], $row[2], $row[3]));
}
fclose($fp);

讓瀏覽器可以輸出下載

header('Content-Type: application/csv');
header("Content-Disposition: attachment; filename={$file_name}");
header('Pragma: no-cache');
readfile($file_path);

php 將陣列組回url

EX:

echo urldecode(http_build_query(array('a' => array(1, 2, 3))));

結果 :

a[0]=1&a[1]=2&a[2]=3

uids array :

$uids = ["1","2","4","5"];
echo urldecode(http_build_query(array('uids' => $uids)));

結果 :

uids[0]=1&uids[1]=2&uids[2]=4&uids[3]=5

顯示目錄結構

$dir = dir('/var/www/test');
while ($folder = $dir->read())
{
    if ($folder == '.' || $folder == '..')
    {
        continue;
    }
    echo "{$folder}\n";
}

php two files combine

$file1_path = '/home/jex_lin/test/qq';
$file2 = file_get_contents('/home/jex_lin/test/cc');
file_put_contents($file1_path, $file2, FILE_APPEND);

連接 DB 及測試是否正常

建立測試的 DB 及 Table, 並透過 PDO 執行測試結果

sql :

建立 testDB Database 及 tt Table, 及建立一筆測試資料

php :

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
$stat = $db->prepare("Select * from tt");
$stat->execute();
$result = $stat->fetchAll();
print_r($result);

$stat = $db->prepare("SELECT * FROM users WHERE account = ? AND password = ?");
$stat->execute(array($account, $password));
$result = $stat->fetch(PDO::FETCH_ASSOC);  // 如果沒找到是 false,有的話是 array

Server push by http1.1 chunked (都由 server 推給 client)

view :

<html lang="zh-TW">
<head></head>
<body>
<img src="image server 的連結">
</body>
</html>

image server :

<?php
$boundary = 'thisIsTheBoundary';

header( "Content-Type: multipart/x-mixed-replace; boundary=\"$boundary\"\r\n" );

echo "--$boundary\n" ;

for( $i=1 ; $i<= 10 ; $i++ )
{
        echo 'Content-Type: image/jpeg'."\n\n";

        echo file_get_contents( "images/$i.jpg" );

        echo "\n--$boundary\n" ;

        flush();
        sleep(1);
}
?>

PHP Memory

function convert($size)
{
   $unit=array('b','kb','mb','gb','tb','pb');
   return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];
}
echo $this->convert(memory_get_usage(true)); // 123 kb

費式數列

function fibonacci($n,$first = 0,$second = 1)
{
    $fib = [$first,$second];
    for($i=1;$i<$n;$i++)
    {
        $fib[] = $fib[$i]+$fib[$i-1];
    }
    return $fib;
}

print_r($this->fibonacci(50));

寄信

PHP 原生 mail 函數是使用 sendmail 來寄送, 請先安裝 sendmail

$email = "jxxxlin@gmail.com";
$subject = "Test";
$msg = "Hello world!";
$header = "From: Jex Lin<jxxxlin@gmail.com>\r\n";
$header .= 'Reply-To: jxxxlin@gmail.com' . "\r\n";          // 預設收件者回覆的對像
$header .= 'Cc: jex@example.com' . "\r\n";                  // 副本, email 會出現在信的副件上
$header .= 'Bcc: jex@example.com' . "\r\n";                 // 也是副本, 但 email 不會出現在信的副件上
$header .= "Content-Type: text/html; charset=utf-8\r\n";    // 可使用 HTML Tag
if (mail($email, $subject, $msg, $header))
{
    echo "success";
}
else
{
    echo "fail";
}

mb_substr 問題

之前碰到一個問題, 在 apache 與 cli 下 mb_substr 的結果不一樣,

如果只是將解析出來的 string 在 log 印出來是長一樣的, 但是將它寫進 file 的話結果就不同了

以下兩個環境解析出來字串的差別, 幾乎長一樣, 不一樣的是在最後面的字元

  • apache : % lt<8b>^@~<8f>¡ÅU¼0^Nß|×ÈI
  • cli : % lt<8b>^@~<8f>¡ÅU¼0^Nß|

最後去查官方文件 string mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] )

原來最後一個編碼的參數是抓 PHP 的系統參數, 然候我再把這個參數印出來看

  • apache : UTF-8
  • cli : ISO-8859-1

確認是採用的編碼不同, 造成字串截取的結果不一樣

解決的方法可以設定在系統變數, 或寫在 mb_substr 參數裡或直接設定 mb_internal_encoding('ISO-8859-1');

AES 加解密

  • 密鑰 : AES 使用對稱式密鑰,加解密都要用同一把密鑰
  • IV (Initialization Vector) : 避免加密結果每次都一樣
  • Paadding : 對未加密的資料做填充, 補滿 128 位元, 常見的方式為 PKCS5

example :

$iv_length = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_length, MCRYPT_RAND);
$key = 'jex';
$text = 'secret data';

# 加密
$encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
echo base64_encode($encrypt) . "\n";        // 0gSYCbCcf/u1S78uIVXWZw==

# 解密
$decrypt = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypt, MCRYPT_MODE_CBC, $iv);
echo $decrypt . "\n";                       // secret data

JSONP

protected function _outputJSON($data)
{
    $callback = $this->input->get_post('callback', TRUE);
    if (empty($callback))
    {
        header("Content-type: application/json");
    }
    else
    {
        header('Content-Type: application/javascript');
    }
    $json_str = json_encode($data);
    echo (empty($callback)) ? $json_str : $callback . "(" . $json_str . ")";
    exit;
}

linux 檔案脫逸,支援 windows 支援的符號(也就是除了 \ / : * ? “ < > |)

private function _escape_cmd_argv($path)
{
    $path = str_replace(
        [ ' ', '"',  '`',  '^',  '!',  '$',  '&',  '(',  ')',  '[',  ']',  '{',  '}',  ',',  ';',   "'"],
        ['\ ','\"', '\`', '\^', '\!', '\$', '\&', '\(', '\)', '\[', '\]', '\{', '\}', '\,', '\;', "\\'"],
        $path
    );
    return $path;
}

將 JSON 輸出在 html hidden 欄位裡

php :

$convenience_store = [
    array(
        'value'     => 70,
        'label'     => '7-11',
        'formatted' => '12552 間',
    ),
];

html :

<input type="hidden" id="getData" value=""<?php echo htmlspecialchars($convenience_store, ENT_QUOTES); ?>"/>

注意 htmlspecialcharsENT_QUOTES 參數

js :

var getData = JSON.parse($('#getData').val());

output mp4 - support range

<?php
$file = 'test.mp4';
$fp = @fopen($file, 'rb');
$size   = filesize($file); // File size
$length = $size;           // Content length
$start  = 0;               // Start byte
$end    = $size - 1;       // End byte
header('Content-type: video/mp4');
//header("Accept-Ranges: 0-$length");
header("Accept-Ranges: bytes");
if (isset($_SERVER['HTTP_RANGE'])) {
    $c_start = $start;
    $c_end   = $end;
    list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
    if (strpos($range, ',') !== false) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    if ($range == '-') {
        $c_start = $size - substr($range, 1);
    }else{
        $range  = explode('-', $range);
        $c_start = $range[0];
        $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
    }
    $c_end = ($c_end > $end) ? $end : $c_end;
    if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start-$end/$size");
        exit;
    }
    $start  = $c_start;
    $end    = $c_end;
    $length = $end - $start + 1;
    fseek($fp, $start);
    header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: ".$length);
$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {
    if ($p + $buffer > $end) {
        $buffer = $end - $p + 1;
    }
    set_time_limit(0);
    echo fread($fp, $buffer);
    flush();
}
fclose($fp);
exit();
?>

Ref : http://www.tuxxin.com/php-mp4-streaming/

Send a notification to android device.

define("GOOGLE_API_KEY", "***************************************");
define("GOOGLE_GCM_URL", "https://android.googleapis.com/gcm/send");

function send_push_notification($registatoin_ids, $message)
{
    $fields = array(
            'registration_ids' => $registatoin_ids,
            'data' => array('message'=>$message)
            );

    $headers = array(
            'Authorization: key=' . GOOGLE_API_KEY,
            'Content-Type: application/json'
            );

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, GOOGLE_GCM_URL);
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
    $result = curl_exec($ch);
    if ($result === FALSE) {
        return array(FALSE, 'curl error : ' . curl_error($ch));
    }
    curl_close($ch);
    $result_arr = json_decode($result, TRUE);
    if ( ! $result_arr)
    {
        return array(FALSE, 'json err : ' . $result);
    }
    if ($result_arr['success']=='1')
    {
        return array(TRUE, 'Message successfully delivered');
    }
    else
    {
        return array(FALSE, json_encode($result_arr['results']));
    }
}

/*
$regids = array('*********************************-Cv4vK8p54hH674CHvCfHQq0_dAw749k********************k6cuKsO_O_riIx_EyFGZEgNrHA**********************');
$message = 'hello world ' . date('Y-m-d H:i:s');

echo "start\n";
list($s, $e) = send_push_notification($regids, $message);
var_dump($s);
echo $e . "\n";

*/

Send a notification to ios device.

function send_notify2APNS($deviceToken, $message, $env = 'development', $debug=FALSE)
{

    log_message("INFO", "token is : " . $deviceToken);
        $passphrase = '';

        $fp  = FALSE;
        $ctx = stream_context_create();
        if ($env == 'development')
        {
            $pem_file = '/home/qq/pem/jemco_dev_push.pem';
            stream_context_set_option($ctx, 'ssl', 'local_cert', $pem_file);
            stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
            $fp = stream_socket_client(
                'ssl://gateway.sandbox.push.apple.com:2195', $err,
                $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
        }
        else
        {
            return array(FALSE, 'Env is not development');
        }

        if (!$fp)
        {
            return array(FALSE, "Failed to connect: $err $errstr");
        }
        $body['aps'] = array(
                'alert' => $message,
                'sound' => 'default'
                );
        $payload = json_encode($body);
        $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
        $result = fwrite($fp, $msg, strlen($msg));
        fclose($fp);

        return ($result) ? array(TRUE, 'Message successfully delivered') : array(FALSE, 'Message not delivered');
}
/*
$deviceToken = '6529a573393c*********************************27639b';
$message = file_get_contents('http://whatthecommit.com/index.txt');
list($s, $m) = send_notify2APNS($deviceToken, $message);
echo $m . "\n";
*/

php array 處理

確保 json_encode 後不會有索引

array_unique 做 json_encode 時,會得到索引->值, 當其他強型態語言(e.g. golang)在事先指定只接收 array 的話,在 decode 時會出錯, 可以用此方法做 unique 但 encode 不會有 key

array_keys(array_flip($test_array))

array_map(‘strval’, 將 value 都轉成 string,避免值是 int 造成其他強型態語言錯誤

array_map('strval', array_keys(array_flip($test_array)))

curl 上傳

php 5.6 用 @

'image' => '@' . $_FILES['image']['tmp_name']   // e.g. @/tmp/phpDV7N6h

php 7.0 用 CURLFile

'image' => new CURLFile($_FILES['image']['tmp_name'], 'image/jpeg', $_FILES['image']['name']),

code

$post_data = [
    'image' => new CURLFile($_FILES['image']['tmp_name'], 'image/jpeg', $_FILES['image']['name']),
];
$ch = curl_init();
curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER  => 1,       // return response body
        CURLOPT_TIMEOUT         => 10,
        CURLOPT_URL             => "http://127.0.0.1:8080/user/avatar/upload",
        CURLOPT_POST            => 1,
        CURLOPT_POSTFIELDS      => $post_data,
    ]);
$resp_body = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close ($ch);
if ($httpcode == 200)
{
    // do something
}

Comments