顯示具有 PHP 標籤的文章。 顯示所有文章
顯示具有 PHP 標籤的文章。 顯示所有文章
2021-10-15 17:26

全域的 Exception 處理

大部分具有 Exception 機制的程式語言都有提供全域的 Exception 處理,如果你已經有用 log 去記錄錯誤的習慣的話,與其在程式裡佈滿了 try catch,不如在全域處裡中去記錄沒有被 catch 的 Exception,這樣程式就可以更乾淨了,而且程式如果發生異常的關閉時也會進入全域處裡,可以確保 Exception 妥善地被記錄。


Console, WinForm 程式

using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Forms;
using NLog;

namespace AppThreadException
{
    static class Program
    {
        private static readonly ILogger _log = LogManager.GetCurrentClassLogger();


        [STAThread]
        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
        public static void Main(string[] args)
        {
            /* ThreadException 用來攔截 UI 錯誤 */
            Application.ThreadException += threadExceptionHandler;

            /* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;

            Application.Run(new MyForm());
        }
        

        /// <summary>攔截 UI 錯誤</summary>
        private static void threadExceptionHandler(object sender, ThreadExceptionEventArgs e)
        {
            _log.Fatal(e.Exception, "操作錯誤");
            MessageBox.Show(e.Exception.Message, "操作錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
        }
        

        /// <summary>攔截不可挽回的錯誤,不能阻止程式關閉</summary>
        private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
        {
            Exception ex = (Exception)e.ExceptionObject;

            _log.Fatal(ex, "執行錯誤");
            MessageBox.Show(ex.Message, "執行錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
        }
    }
}

Web 程式

在 Global.asax.cs 中可以設定 Application_Error 就可以攔截未處裡的 Exception,除了用這個方法記錄錯誤,還可以用現有的套件(elmah)幫我們完成。

using System;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using NLog;

namespace MvcReport
{
    public class MvcApplication : System.Web.HttpApplication
    {
        private static readonly ILogger _log = LogManager.GetCurrentClassLogger();

        //...

        protected void Application_Error(object sender, EventArgs e)
        {
            Exception ex = Server.GetLastError();
            _log.Fatal(ex, ex.Message);
        }

        //...
    }
}

Net core 程式

Net core 的程式都是相同的方式,Web Request 的要另外配置

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using EverTop.Api;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using NLog;


namespace MyWebApp
{
    public class Program
    {
        private static readonly ILogger _log = LogManager.GetCurrentClassLogger();


        public static void Main(string[] args)
        {
            /* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
            AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;

            /* 用來攔截 Task 錯誤 */
            TaskScheduler.UnobservedTaskException += unobservedTaskException;

            var host = Host.CreateDefaultBuilder(args)
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder
                    .UseStartup<Startup>()
                )
                .Build();

            host.Run(); /* 啟動網站 */
        }


        private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
        {
            Exception ex = (Exception)e.ExceptionObject;
            _log.Fatal(ex, "執行錯誤");
        }

        private static void unobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            _log.Fatal(e.Exception, "執行錯誤");
            e.SetObserved();
        }
    }
}

Net core 3.1 Web 程式

在 Startup.cs 中配置 middleware 進行 Exception 攔截

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (_env.IsProduction())
    {
        app.UseExceptionHandler("/Error");
    }
    else
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseStatusCodePagesWithReExecute("/Error");


    /* 利用 middleware 進行 Exception 攔截 */
    /* 這裡的順序很重要,不然會被前面 ExceptionHandler 處理掉就拿不到 Exception */
    app.Use(async (context, next) =>
    {
        try
        {
            await next();
        }
        catch (Exception ex)
        {
            _log.Fatal(ex, "執行錯誤");
            throw; /* 把 Exception 再丟出去給別人處理 */
        }
    });
}

週期性 Thread 的處裡方式

將主要邏輯寫在另外 method 裡,這樣可以專注在 Exception 上。

private bool _runFlag = false;

public void Start()
{
    if (_runFlag) { return; }
    _runFlag = true;

    var thread = new Thread(() =>
    {
        while (_runFlag)
        {
            try
            {
                cycleHandler();
            }
            catch (Exception ex)
            {
                _log.Fatal(ex, "執行錯誤");
            }
            Thread.Sleep(1000);
        }
    });

    thread.Start();
}

public void Stop()
{
    _runFlag = false;
}

private void cycleHandler()
{
    // 主要的邏輯程式寫在這裡
}

PHP 錯誤處理

Ref: set_error_handler, set_exception_handler

<?php
function error_handler($errno, $errstr, $errfile, $errline) {
    if(error_reporting() === 0){ return; }
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('error_handler', E_ALL^E_NOTICE);


function exception_handler($exception) {
    // 在這記錄 log
    throw $exception;
}
set_exception_handler('exception_handler');


throw new Exception('Uncaught Exception');
//$a = 1 / 0;

2015-10-23 16:52

[PHP] 從 RGB 轉換到 HSB

HSB(HSV)通過色相/飽和度/亮度三要素來表達顏色。
  • H(Hue):表示颜色的類型 (例如红色,绿色或者黄色),取值範圍為 0 ~ 360,其中每一个值代表一種顏色
  • S(Saturation):顏色的飽和度從 0 到 1,有时候也稱為纯度 (0 表示灰度圖,1 表示纯的顏色)
  • B(Brightness or Value):顏色的明亮程度從 0 到 1 (0 表示黑色,1 表示特定飽和度的顏色)

function rgb2hsb($rgb) {
    $red   = max(0, min(255, $rgb['red']));
    $green = max(0, min(255, $rgb['green']));
    $blue  = max(0, min(255, $rgb['blue']));

    $max = max($red, $green, $blue);
    $min = min($red, $green, $blue);
    $delta = $max - $min;

    $b = $max / 255;
    $s = $max == 0 ? 0 : $delta / $max;

    $h = 0;
    if ($delta == 0) {
        $h = 0;
    } else if ($max == $red && $green >= $blue) {
        $h = 0   + 60 * ($green - $blue) / $delta;
    } else if ($max == $red && $green < $blue) {
        $h = 360 + 60 * ($green - $blue) / $delta;
    } else if ($max == $green) {
        $h = 120 + 60 * ($blue - $red) / $delta;
    } else if ($max == $blue) {
        $h = 240 + 60 * ($red - $green) / $delta;
    }

    return array('hue' => fmod($h, 360), 'saturation' => $s, 'brightness' => $b);
}



function hsb2rgb($hsb) {
    $h = $hsb['hue'];
    $s = max(0, min(1, $hsb['saturation']));
    $b = max(0, min(1, $hsb['brightness']));

    $h = fmod($h, 360);
    if($h < 0){ $h += 360; }

    $i = ($h / 60) % 6;
    $f = ($h / 60) - $i;
    $p = $b * (1 - $s);
    $q = $b * (1 - $f * $s);
    $t = $b * (1 - (1 - $f) * $s);

    $p = intval(round(255 * $p));  
    $q = intval(round(255 * $q));
    $t = intval(round(255 * $t));
    $b = intval(round(255 * $bri));
    
    switch ($i) {
        case 0: return array('red' => $b, 'green' => $t, 'blue' => $p);
        case 1: return array('red' => $q, 'green' => $b, 'blue' => $p);
        case 2: return array('red' => $p, 'green' => $b, 'blue' => $t);
        case 3: return array('red' => $p, 'green' => $q, 'blue' => $b);
        case 4: return array('red' => $t, 'green' => $p, 'blue' => $b);
        case 5: return array('red' => $b, 'green' => $p, 'blue' => $q);
        default: return array('red' => 0, 'green' => 0, 'blue' => 0);
    }
}
2015-10-23 16:44

[PHP] 從 Hex 轉換到 RGB

function hex2rgb($hex) {
    list($r, $g, $b) = sscanf($hex, "%02x%02x%02x");
    return array('red' => $r, 'green' => $g, 'blue' => $b);
}

function rgb2hex($rgb) {
    return sprintf("%02x%02x%02x", $rgb['red'], $rgb['green'], $rgb['blue']);
}
2015-02-26 14:40

[PHP][Java][C#] 用 XSD 驗證 XML

menu_config.xml 要驗證的 XML
<?xml version="1.0" encoding="utf-8"?>
<menu_config>
    <menu title="文章管理" url="~/Article" target="" allow="">
        <submenu title="列表" url="~/Article/list" target="" allow=""/>
        <submenu/>
        <submenu title="新增" url="~/Article/add" target="" allow=""/>
    </menu>
    <menu/>
    <menu title="帳號管理" url="~/Admin"/>
</menu_config>


menu_config.xsd 結構定義
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

    <xs:element name="menu_config">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="menu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="menuType">
        <xs:sequence>
            <xs:element name="submenu" type="menuType" minOccurs="0" maxOccurs="unbounded" />
        </xs:sequence>
        <xs:attribute name="title" type="xs:string"/>
        <xs:attribute name="url" type="xs:string"/>
        <xs:attribute name="target" type="xs:string"/>
        <xs:attribute name="allow" type="xs:string"/>
    </xs:complexType>

</xs:schema>
element minOccurs
最少的出現次數,不設置為至少出現 1 次,設置 0 為可有可無。
element maxOccurs
最大的出現次數,設置 unbounded 為無上限。
attribute type
型別必須為 QName,常用的有 xs:string, xs:date, xs:int, xs:integer, xs:decimal, xs:boolean, xs:double, xs:float。


PHP
$xmlFile = 'menu_config.xml';
$xsdFile = 'menu_config.xsd';

/* 啟用自行錯誤處裡 */
libxml_use_internal_errors(true);

$xml = new DOMDocument();
$xml->load($xmlFile);

if (!$xml->schemaValidate($xsdFile)) {
    /* 顯示錯誤訊息 */
    print_r(libxml_get_errors());
    libxml_clear_errors();
}


C#
var menuConfig = XDocument.Load("menu_config.xml");
schemas.Add("", XmlReader.Create("menu_config.xsd"));

menuConfig.Validate(schemas, (o, e) => {
    e.Message.Dump();
});


Java
import java.io.File;
import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.SAXException;


public class TestXsd {

    public static void main(String[] args) {
        File xsd = new File("menu_config.xsd");
        File xml = new File("menu_config.xml");

        try {
            SchemaFactory factory = SchemaFactory.newInstance(
                XMLConstants.W3C_XML_SCHEMA_NS_URI
            );
            Schema schema = factory.newSchema(xsd);
            Validator validator = schema.newValidator();

            validator.validate(new StreamSource(xml));
            System.out.println("xml is valid");
        }
        catch (SAXException|IOException e) {
            System.out.println("Reason: " + e.getLocalizedMessage());
        }
    }
}



參考資料:
XML 結構描述項目 - MSDN
XML Schema Tutorial W3Schools
2015-01-31 21:02

[轉載] PHP 配置open_basedir,让各虚拟站点独立运行

轉載自:PHP 配置open_basedir,让各虚拟站点独立运行 - canbeing - 博客园

open_basedir 可将用户访问文件的活动范围限制在指定的区域,通常是其家目录的路径,也可用符号"."来代表当前目录。open_basedir 也可以同时设置多个目录, 在 Windows 中用分号(;)分隔目录,在任何其它系统中用冒号(:)分隔目录。当其作用于 Apache 模块时,父目录中的 open_basedir 路径自动被继承。

以下以 Linux 系统下的配置为例:

方法一:在 php.ini 里配置
open_basedir = .:/tmp/

方法二:在 Apache 配置的 VirtualHost 里设置
php_admin_value open_basedir .:/tmp/

方法三:在 Apache 配置的 Direcotry .htaccess 里设置
php_admin_value open_basedir .:/tmp/


关于三个配置方法的解释:
  1. 方法二的优先级高于方法一,也就是说方法二会覆盖方法一
    方法三的优先级高于方法二,也就是说方法三会覆盖方法二
  2. 配置目录里加了 /tmp/ 是因为php默认的临时文件(如上传的文件、session 等)会放在该目录,所以一般需要添加该目录,否则部分功能将无法使用
  3. 配置目录里加了 . 是指运行php文件的当前目录,这样做可以避免每个站点一个一个设置
  4. 如果站点还使用了站点目录外的文件,需要单独在对应 VirtualHost 设置该目录
2014-03-05 23:38

利用 HTTP Status Codes 傳遞 Ajax 成功失敗的狀態

一般處理 Ajax 回應時會傳送的資訊種類有:資料、成功訊息、錯誤訊息、失敗訊息以及處理狀態,傳遞的資訊種類並不一致,再加上除了資料之外,通常還希望能傳遞處理狀態,這種情況大部分會選擇是以 JSON 的方式傳遞這兩個訊息,以下是常見的幾種格式:

{ code: 1, msg: "OK" }
{ success: true, result: "data", errorMsg: "" }
{ status: 'success', result: [], errorMsg: "" }
//...

但以執行狀態跟操作行為作一個歸納,可以區分以下幾種回傳結果:
資料操作 HTTP Method 成功 錯誤/失敗
檢視(Read) GET 資料 錯誤/失敗訊息
新增(Create)
修改(Update)
刪除(Delete)
POST 成功訊息 錯誤/失敗訊息

從上面的歸納可以看出規律性,接著只要有方法可以傳送處理的狀態,以及能夠區分資料的種類,其實就單純很多,而 HTTP Status Codes 就是用來傳遞 HTTP 的處理狀態,如果利用這個方式來傳遞自訂的處理狀態,這樣 HTTP Content 就可以很單純傳遞資料,讓資料格式不受限於 JSON,還可以使用其他格式(text, xml, html),而且 XMLHttpRequest 本身就有處理 HTTP Status Codes 的能力,而 jQuery.ajax 也有提供 error status 的處理,所以可以利用這個來定義狀態的處理,在 HTTP Status Codes 有幾個已經定義狀態,很適合用來回傳處理狀態的資訊:

400 Bad Request 錯誤的請求 適用在表單內容的錯誤,如必填欄位未填、Email 格式錯誤
403 Forbidden 沒有權限,被禁止的 適用在沒有登入或權限不足
500 Internal Server Error 內部服務器錯誤 適用在程式的錯誤


jQuery 接收資訊的範例
$.ajax({
    type: "POST",
    url: document.location,
    success: function (data, textStatus, jqXHR) {
        alert(data);
    },
    error: function (jqXHR, textStatus, errorThrown) {
        alert(jqXHR.responseText);
    }
});


PHP 傳遞錯誤訊息的範例
if (php_sapi_name() == 'cgi'){
    header("Status: 400 Bad Request");
}else{
    header("HTTP/1.0 400 Bad Request");
}
exit("儲存失敗!!");


C# MVC 傳遞錯誤訊息的範例
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 400;
return Content("儲存失敗!!");
2014-03-05 14:44

使用 Stream 讀取 cUrl 下載結果

使用 stream 的好處就是用多少拿多少,不會因為資料大小而占用大量的記憶體。

$url = 'http://www.google.com.tw';

/* 建立接收的 Temp File Stream */
$tmpfile = tmpfile();


$curl = curl_init();

/* 指定下載的的 URL */
curl_setopt($curl, CURLOPT_URL, $url); 

/* 指定存放的 File Stream */
curl_setopt($curl, CURLOPT_FILE, $tmpfile);

/* 執行並取得狀態 */
$status = curl_exec($curl);
curl_close($curl);

if(!$status){ 
    fclose($tmpfile); 
    exit('error'); 
}


/* Temp File Stream 指標歸零 */
fseek($tmpfile, 0);

/*一次讀取一行*/
while (($line = fgets($tmpfile)) !== false) {
    var_dump($line);
}

/* 關閉 Stream */
fclose($tmpfile);
2014-03-05 14:30

使用 stream 讀取指令列結果

$cmd = "find ./ -path './*/*/*'";

/* 執行指令,並取得 stream */
$fp = popen($cmd, "r");

/*一次讀取一行*/
while (($filePath = fgets($fp)) !== false) {
    $filePath = trim($filePath);
    var_dump($filePath);
}

/* 關閉 stream */
pclose($fp);
2013-08-17 14:55

[PHP] url, base64, sprite 三種格式的 icons.css 產生器

先做一個假設,如果 icon 的檔名就是 css 的 class 樣式名稱,那麼我們只要掃瞄資料夾的 Icon 圖檔,然後產生對應的 CSS 檔案,這樣就可以省去製作 Sprite 圖檔跟維護 CSS 對應的問題。

第一種 url 格式只是取得路徑的問題。

第二種 base64 格式可以透過 base64_encode(file_get_contents($path)); 就簡單的達成。

第三種 sprite 格式則使用 Imagick 去處理,會比較快樂。


接著以下就是如何達成的程式片段:

<?php

/*把目錄改變到當前文件下*/
chdir(dirname(__FILE__));

/*Sprite 圖與圖的間距*/
$spriteGap = 30;


/*=[ 取得圖檔資訊 ]=*/
$maxWidth = 0;
$maxHeight = 0;
$nextTop = 0;
$imageList = array();

foreach ( glob('icons/*.{png,jpg,gif}',GLOB_BRACE) as $path )
{
    $image = new Imagick($path);
    $name = pathinfo($path,PATHINFO_FILENAME);

    if(isset($imageList[$name])){ 
        throw new Exception("圖片名稱重複 [ $name ]"); 
    }

    $info = array(
        '{top}' => $nextTop,
        '{image}' => $image,
        '{width}' => $image->getImageWidth(),
        '{height}' => $image->getImageHeight(),
        '{name}' => $name,
        '{path}' => $path,
        '{isAnimate}' => false
    );

    $header = '';
    switch($image->getImageFormat()){
        case "PNG":
            $header = 'data:image/png;base64,'; break;
        case "JPEG":
            $header = 'data:image/jpeg;base64,'; break;
        case "GIF":
            $header = 'data:image/gif;base64,'; break;
        default: break;
    }

    $info['{uri}'] = $header.base64_encode(file_get_contents($path));

    $maxWidth = max($maxWidth, $info['{width}']);
    $maxHeight = max($maxHeight, $info['{height}']);

    
    /*檢查圖片是否為動畫*/
    $frameNum = 0;
    foreach($image->deconstructImages() as $i) {
        $frameNum++;
        if ($frameNum > 1) {
            $info['{isAnimate}'] = true;
            break;
        }
    }
    
    if(!$info['{isAnimate}']){
        $nextTop += $info['{height}'] + $spriteGap;
    }


    $imageList[$name] = $info;
}


/*=[ 製作 CSS Sprite 圖檔 ]=*/
$spriteImage = new Imagick();
$spriteImage->newImage($maxWidth, $nextTop, new ImagickPixel());
$spriteImage->setImageFormat('png');
$spriteImage->paintTransparentImage(new ImagickPixel(), 0.0, 0);

foreach ($imageList as $name => $info)
{
    if($info['{isAnimate}']){ continue; } /* 忽略 GIF 動畫 */

    /* 複製 Icon 圖檔到 Sprite */
    $spriteImage->compositeImage(
        $info['{image}'],
        $info['{image}']->getImageCompose(),
        0,
        $info['{top}']
    );

    $info['{image}']->destroy();
    unset($imageList[$name]['{image}']);
}

$spriteImage->writeImage('icons.sprite.png');
$spriteImage->destroy();
$spriteImage = null;


下載完整程式: php_make_icons_css.zip
2013-06-04 23:14

[PHP] 分配上傳檔案的路徑

<?php

/*數字方式分配路徑*/
function allotPath($id, $extend='jpg') {
    $folders = str_split(sprintf("%012s", $id),3);
    $folders[3] = $id;

    return  '/'. join('/', $folders).'.'.$extend;
}

/*雜湊方式分配路徑*/
function allotHashPath($id, $extend='jpg') {
    $folders = array_slice( str_split(md5($id),2), 0, 4);
    $folders[] = $id;

    return  '/'. join('/', $folders).'.'.$extend;
}

var_dump(allotPath(122333));
// string(23) "/000/000/122/122333.jpg"

var_dump(allotHashPath(122333));
// string(23) "/9c/7c/c2/cd/122333.jpg"

2013-05-30 22:21

[PHP] 從網頁執行 SVN 更新

想說寫一個透過網頁就可以執行 SVN 更新的程式,結果並不是我想得那樣簡單,有一些眉角需要注意的說。

先以 Apache 的使用者帳號執行 SVN checkout,這樣 Apache 才有 SVN 的連結權力,才可以透過網頁執行 SVN update

su -s /bin/bash www-data
cd /var/www
svn checkout http://www.xxx.com/svn/my_site 


  1. 在用 PHP 執行 shell 指令前要加上 export LANG=C.UTF-8 的環境宣告,不然 SVN update 時遇到中文會出現 error,Ubuntu 的 Apache 預設是 LANG=C
  2. 接著要為 SVN 補上 --accept theirs-full 的參數,這是當衝突發生時,都以 SVN Server 的檔案版本為主
  3. 最後再加上 2>&1,讓 PHP 可以取得包含錯誤的所有訊息
<?php
putenv('LANG=C.UTF-8');
$result = shell_exec('svn update --accept theirs-full /var/www/my_site 2>&1');
echo nl2br($result);
2013-05-29 20:47

[轉載][PHP] Pattern Modifiers - 正規表示式的修飾符

下面是當前規則表達式裡可用的修飾. 括號內的名字是那些修飾符的內部 PCRE 名字.

i (PCRE_CASELESS)
如果設置了這個修飾符, 則表達式不區分大小寫.

m (PCRE_MULTILINE)
默認的, PCRE 認為目標字符串值是單行字符串 (即使他確實包含多行). 行開始標記 (^) 只匹配字符串的開始部分, 而行結束標記 ($) 只匹配字符串的尾部,或者一個結束行(除非指定 E 修飾符). 這個和 Perl 裡面一樣.

如果設定了這個修飾符, 行開始和行結束結構分別匹配在目標字符串任何新行的當前位置後面的或者以前的, 和每一個開始和結束一樣. 這個等於 Perl 裡面的 /m 修飾符. 如果目標字符串沒有 "n" 字符, 或者模式裡沒有 ^ 或 $ ,這個修飾符不起作用.

s (PCRE_DOTALL)
如果設置這個修飾符, 模式裡的一個"點"將匹配所有字符, 包括換行. 沒有他, 換行將被排除在外. 這個修飾符等同於 Perl 裡面的 /s 修飾符. 一個相反的類型,例如 [^a] 將總是匹配換行字符,而不管這個修飾符的限制.

x (PCRE_EXTENDED)
如果設置這個修飾符, 模式裡面的空格數句將會被全部忽略,除非用轉義符或者一個字符的內部類型,還有所有字符類型外的未轉義的 # 號之間的也被忽略. 這個等同於 Perl 裡面的 /x 修飾符, 這樣可以複雜的模式裡面加入註釋. 注意,只適用於數據字符. 空格字符將不會在指定的模式字符指定順序中出現。

e
如果設置這個修飾符, preg_replace() 將在替換值裡進行正常的涉及到 \ 的替換, 等同於在 PHP 代碼裡面一樣, 然後用於替換搜索到的字符串.

只在 preg_replace() 裡使用這個修飾符; 其它 PCRE 函數忽略他.

A (PCRE_ANCHORED)
如果設置這個修飾符, 模式被強制為錨(anchored), 也就是說, 他將值匹配搜索字符串的開始. 這個效果可以通過恰當的模式結構自身來實現,那是在 Perl 裡面的唯一途徑.

D (PCRE_DOLLAR_ENDONLY)
如果設置這個修飾符,則模式裡的 $ 修飾符將僅匹配目標字符串裡的尾部. 沒有這個修飾符, $ 字符也匹配新行的尾部 (但是不再新行的前面). 如果設置了 m 修飾符則忽略這個修飾符. 在 Perl 裡面沒有類似的.

S
如果一個模式將被使用多次, 使用長些時間分析他來來提高匹配的速度. 如果使用這個修飾符,則進行額外的分析. 目前, 研究模式僅用於非錨模式,沒有一個固定的開始字符.

U (PCRE_UNGREEDY)
這個修飾符翻轉數量的 "greediness" ,使得默認不被 greedy,但是如果你緊跟問號(?),則可以 greedy. 這個和 Perl 不兼容. 這個也可以通過在模式裡面的(?U) 修飾符得到.

X (PCRE_EXTRA)
這個修飾符打開額外的功能,這些和 Perl 不兼容. 任何模式裡面的後面帶字符但沒有特殊意義的反斜槓將引起錯誤, 從而儲備這些聯合用於將來的擴充. 默認的, 在 Perl 裡面, 反斜槓後面有無意義的字符被當成正常的 literal. 目前還沒有其他的控制特徵
2013-04-04 22:05

PDO fetch 模式的回傳結果

PDO 的 fetch 模式功能實在是太方便了,但每次要產生想要的結果都要試太麻煩了,這裡列出可能的組合。

<?php
$dbAdapter = new PDO("mysql:host=localhost;dbname=test", "root", "1234");
$dbAdapter->exec("SET NAMES 'utf8';");




$data = $dbAdapter->query("
    SELECT id, name, method FROM category
")->fetchAll(PDO::FETCH_ASSOC);

//var_dump($data);
/*
array(
    array(
        'id' => '1',
        'name' => 'HBO',
        'method' => 'service',
    ),
    array(
        'id' => '2',
        'name' => '本週新片',
        'method' => 'movie',
    ),
    array(
        'id' => '3',
        'name' => '熱映中',
        'method' => 'movie',
    ),
)
*/



$data = $dbAdapter->query("
    SELECT name, method FROM category
")->fetchAll(PDO::FETCH_COLUMN);

//var_dump($data);
/*
array(
    'HBO',
    '本週新片',
    '熱映中',
)
*/



$data = $dbAdapter->query("
    SELECT id, name, method FROM category
")->fetchAll(PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC);

//var_dump($data);
/*
array(
    '1' => array(
        'name' => 'HBO',
        'method' => 'service',
    ),
    '2' => array(
        'name' => '本週新片',
        'method' => 'movie',
    ),
    '3' => array(
        'name' => '熱映中',
        'method' => 'movie',
    ),
)
*/



$data = $dbAdapter->query("
    SELECT method, id, name FROM category
")->fetchAll(PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC);

//var_dump($data);
/*
array(
    'service' => array(
        'id' => '1',
        'name' => 'HBO',
    ),
    'movie' => array(
        'id' => '3',
        'name' => '熱映中',
    ),
)
*/



$data = $dbAdapter->query("
    SELECT id, name, method FROM category
")->fetchAll(PDO::FETCH_UNIQUE | PDO::FETCH_COLUMN);

//var_dump($data);
/*
array(
    '1' => 'HBO',
    '2' => '本週新片',
    '3' => '熱映中',
)
*/



$data = $dbAdapter->query("
    SELECT method, name, id FROM category
")->fetchAll(PDO::FETCH_UNIQUE | PDO::FETCH_COLUMN);

//var_dump($data);
/*
array(
    'service' => 'HBO',
    'movie' => '熱映中',
)
*/




$data = $dbAdapter->query("
    SELECT method, id, name FROM category
")->fetchAll( PDO::FETCH_ASSOC | PDO::FETCH_GROUP);

//var_dump($data);
/*
array(
    'service' => array(
        array(
            'id' => '1'
            'name' => 'HBO'
        ),
    )
    'movie' => array(
        array(
          'id' => '2'
          'name' => '本週新片'
        ),
        array(
          'id' => '3'
          'name' => '熱映中'
        ),
    )
)
*/




$data = $dbAdapter->query("
    SELECT method, name, id FROM category
")->fetchAll(PDO::FETCH_GROUP | PDO::FETCH_COLUMN);

//var_dump($data);
/*
array(
    'service' => array(
        'HBO'
    ),
    'movie' => array(
        '本週新片'
        '熱映中'
    ),
)
*/





$data = $dbAdapter->query("
    SELECT id, name, method FROM category
")->fetchAll(PDO::FETCH_OBJ);

//var_dump($data);
/*
array(
    stdClass{
        public $id = '1';
        public $name = 'HBO';
        public $method = 'service';
    },
    stdClass{
        public $id = '2';
        public $name = '本週新片';
        public $method = 'movie';
    },
    stdClass{
        public $id = '3';
        public $name = '熱映中';
        public $method = 'movie';
    },
)
*/







class Category_1 {}

$data = $dbAdapter->query("
    SELECT id, name, method FROM category
")->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, "Category_1");

//var_dump($data);
/*
array(
    Category_1{
        public $id = '1';
        public $name = 'HBO';
        public $method = 'service';
    },
    Category_1{
        public $id = '2';
        public $name = '本週新片';
        public $method = 'movie';
    },
    Category_1{
        public $id = '3';
        public $name = '熱映中';
        public $method = 'movie';
    },
),
*/





class Category_2 {
    public $name;
    public $method;

    public function __construct() {}
    public function __set($name, $value ){}
}

$data = $dbAdapter->query("
    SELECT id, name, method FROM category
")->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, "Category_2");

//var_dump($data);
/*
array(
    Category_2{
        public $name = 'HBO';
        public $method = 'service';
    },
    Category_2{
        public $name = '本週新片';
        public $method = 'movie';
    },
    Category_2{
        public $name = '熱映中';
        public $method = 'movie';
    },
)
*/
2012-09-30 22:24

[轉載][PHP] preg_replace 效能測試 (將兩個空白字元以上取代成一個)

轉載自:[PHP] preg_replace 效能測試 (將兩個空白字元以上取代成一個) 小惡魔 – 電腦技術 – 工作筆記 – AppleBOY

preg_replace 可以使用正規語法來取代字串任何字元,,今天探討取代空白字元的效能,雖然這是個不起眼的效能評估,一般人不太會這樣去改,不過這是國外 PHP Framework 有人提出來修正的,經過許多人的測試一致同意。功能就是一篇文章內如果有多餘的空白能空取代成一個,一般人都會用 \s+ 正規語法,畢竟大家都知道 \s 代表單一空白或 \r 等符號,但是國外有人提出用 {2,} 方式來取代空白。程式碼如下,大家可以測試看看。

<?php
$nb = 10000;
$str = str_repeat('Hi, I am appleboy  ' . "\n", 10);
$t1 = microtime(true);
for ($i = $nb; $i--; ) {
    preg_replace('/\s+/', ' ', $str);
}
$t2 = microtime(true);
for ($i = $nb; $i--; ) {
    preg_replace('/ {2,}/', ' ', 
        str_replace(array("\r","\n","\t","\x0B","\x0C"),' ',$str)
    );
}
$t3 = microtime(true);

echo $t2 - $t1;
echo "\n";
echo $t3 - $t2;

測試結果(1萬次)

PHP 5.3.3
old: 0.13053798675537
new: 0.058536052703857

PHP 5.3.15
old: 0.11732506752014
new: 0.071418046951294

PHP 5.3.17
old: 0.11612010002136
new: 0.07065486907959

PHP 5.4.5
old: 0.1185781955719
new: 0.066012859344482

PHP 5.4.7
old: 0.11343121528625
new: 0.066931962966919

結論至少快蠻多的,如果整體資料量再大一點,我想差別會更大,那至於要不要用呢,就看個人了 XD。
2012-05-05 01:53

[轉載] 让PHP更快的提供文件下载

作者:Laruence
本文地址:http://www.laruence.com/2012/05/02/2613.html

一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件.

但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们采用让PHP来做转发, 为用户提供文件下载.

<?php
    $file = "/tmp/dummy.tar.gz";
    header("Content-type: application/octet-stream");
    header('Content-Disposition: attachment; filename="' 
        . basename($file) . '"');
    header("Content-Length: ". filesize($file));
    readfile($file);

但是这个有一个问题, 就是如果文件是中文名的话, 有的用户可能下载后的文件名是乱码.

于是, 我们做一下修改(参考: :

<?php
    $file = "/tmp/中文名.tar.gz";
 
    $filename = basename($file);
 
    header("Content-type: application/octet-stream");
 
    //处理中文文件名
    $ua = $_SERVER["HTTP_USER_AGENT"];
    $encoded_filename = urlencode($filename);
    $encoded_filename = str_replace("+", "%20", $encoded_filename);
    if (preg_match("/MSIE/", $ua)) {
        header('Content-Disposition: attachment; filename="'
            . $encoded_filename . '"');
    } else if (preg_match("/Firefox/", $ua)) {
        header("Content-Disposition: attachment; filename*=\"utf8''"
            . $filename . '"');
    } else {
        header('Content-Disposition: attachment; filename="'
            . $filename . '"');
    }
 
    header('Content-Disposition: attachment; filename="'
        . $filename . '"');
    header("Content-Length: ". filesize($file));
    readfile($file);

恩, 现在看起来好多了, 不过还有一个问题, 那就是readfile, 虽然PHP的readfile尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出.

输出的时候, 如果是Apache + PHP mod, 那么还需要发送到Apache的输出缓冲区. 最后才发送给用户. 而对于Nginx + fpm如果他们分开部署的话, 那还会带来额外的网络IO.

那么, 能不能不经过PHP这层, 直接让Webserver直接把文件发送给用户呢?

今天, 我看到了一个有意思的文章: How I PHP: X-SendFile.

我们可以使用Apache的module mod_xsendfile, 让Apache直接发送这个文件给用户:

<?php
    $file = "/tmp/中文名.tar.gz";
 
    $filename = basename($file);
 
    header("Content-type: application/octet-stream");
 
    //处理中文文件名
    $ua = $_SERVER["HTTP_USER_AGENT"];
    $encoded_filename = urlencode($filename);
    $encoded_filename = str_replace("+", "%20", $encoded_filename);
    if (preg_match("/MSIE/", $ua)) {
        header('Content-Disposition: attachment; filename="'
            . $encoded_filename . '"');
    } else if (preg_match("/Firefox/", $ua)) {
        header("Content-Disposition: attachment; filename*=\"utf8''"
            . $filename . '"');
    } else {
        header('Content-Disposition: attachment; filename="'
            . $filename . '"');
    }
 
    header('Content-Disposition: attachment; filename="'
            . basename($file) . '"');
 
    //让Xsendfile发送文件
    header("X-Sendfile: $file");

X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.

Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看


mod-xsendfile 在 Ubuntu 的安裝方法:
sudo apt-get install libapache2-mod-xsendfile
2012-05-05 01:27

[PHP] 使用 php5-ffmpeg 擷取影片圖片

前幾天在玩 FFmpeg 的時後,突然發現 Ubuntu 上多了 php5-ffmpeg 這個擴充套件,就想來玩玩看,看好不好用,有兩個結論:
  1. 讀取影片取決於 FFmpeg 的支援性,如果想要什麼格式都支援的話,建議自己重新編譯 FFmpeg。
  2. 效率並沒有我想像中的快,兩分鐘的影片取十張圖,大約 30 秒。
安裝方法:
sudo apt-get install ffmpeg php5-ffmpeg php5-gd

擷圖測試範例:
<?php
$page = 10;
$prefix = 'screencap';

$mov = new ffmpeg_movie('gt.avi');
$count = $mov->getFrameCount();
$range = (int)round($count/($page+1));

for($i=1; $i<=$page; $i++){
    $frameNum = $range*$i;
    $imgFile = $prefix.'_'.$i.'.png';

    $frame = $mov->getFrame($frameNum);
    if(!$frame){ continue; }

    $gdImage = $frame->toGDImage();
    if(!$gdImage){ continue; }

    imagepng($gdImage, $imgFile);
    imagedestroy($gdImage);

    echo '<img src="'.$imgFile.'" border="1" /><br />';
}

參考文件:
ffmpeg_movie object methods
FFmpeg and PHP
2012-04-29 21:16

[Linux] 檢查 SAMBA 與 NFS Server 是否存在

通常會透過 /etc/fstab 來處理掛載的設定,然後再使用 mount -a 來重新確認掛載,最好在排程的程序用到掛載目錄時也執行一次 mount -a,掛載目錄在斷線後是不會自動回復的,mount -a 的 Timeout 其實還蠻久的,尤其是 Server 不存在的時候,所以最好還是用對應的 client 先確認 server 是否存在。

而檢查 NFS 的 client 可以用 showmount 來處理,在 Ubuntu 上的安裝方式如下:
sudo aptitude install nfs-common

而 SAMBA 的 client 則是用 smbclient,在 Ubuntu 上的安裝方式如下:
sudo aptitude install smbclient


檢查 NFS Server 是否存在的流程

以 Shell 的方式檢查
# 先以 client 確認 server 是否存在
/sbin/showmount 192.168.0.6 >/dev/null 2>&1
if [ "j$?" != "j0" ]; then  
    echo "NFS Server is not exist"
    exit 1
fi

# 重新確認掛載 
mount -a >/dev/null 2>&1
if [ "j$?" != "j0" ]; then
    echo "NFS Server mount failed"
    exit 1;
fi

以 PHP 的方式檢查
/*先以 client 確認 server 是否存在*/ 
$state = shell_exec('/sbin/showmount 192.168.0.6 >/dev/null 2>&1; echo $?');
if(trim($state)!='0'){
    echo "NFS Server is not exist";
    exit;
}

/*重新確認掛載*/ 
if(shell_exec('mount -a 2>&1')){
    echo "NFS Server mount failed"
    exit;
}



檢查 SAMBA Server 是否存在的流程

以 Shell 的方式檢查
# 先以 client 確認 server 是否存在
smbclient -NL //192.168.0.6 >/dev/null 2>&1
if [ "j$?" != "j0" ]; then  
    echo "SAMBA Server is not exist"
    exit 1
fi

# 重新確認掛載 
mount -a >/dev/null 2>&1
if [ "j$?" != "j0" ]; then
    echo "SAMBA Server mount failed"
    exit 1;
fi

以 PHP 的方式檢查
/*先以 client 確認 server 是否存在*/ 
$state = shell_exec('smbclient -NL //192.168.0.6 >/dev/null 2>&1; echo $?');
if(trim($state)!='0'){
    echo "SAMBA Server is not exist";
    exit;
}

/*重新確認掛載*/ 
if(shell_exec('mount -a 2>&1')){
    echo "SAMBA Server mount failed"
    exit;
}
2012-04-23 16:47

[PHP] 簡易的圖片相似度比較

由於相似图片搜索的php实现的 API 不怎麼符合我的用途,所以我重新定義 API 的架構,改寫成比較簡單的函數方式,雖然還是用物件的方式包裝。

<?php
/**
 * 圖片相似度比較
 *
 * @version     $Id: ImageHash.php 4429 2012-04-17 13:20:31Z jax $
 * @author      jax.hu
 *
 * <code>
 *  //Sample_1
 *  $aHash = ImageHash::hashImageFile('wsz.11.jpg');
 *  $bHash = ImageHash::hashImageFile('wsz.12.jpg');
 *  var_dump(ImageHash::isHashSimilar($aHash, $bHash));
 *
 *  //Sample_2
 *  var_dump(ImageHash::isImageFileSimilar('wsz.11.jpg', 'wsz.12.jpg'));
 * </code>
 */

class ImageHash {

    /**取樣倍率 1~10
     * @access public
     * @staticvar int
     * */
    public static $rate = 2;

    /**相似度允許值 0~64
     * @access public
     * @staticvar int
     * */
    public static $similarity = 80;

    /**圖片類型對應的開啟函數
     * @access private
     * @staticvar string
     * */
    private static $_createFunc = array(
        IMAGETYPE_GIF   =>'imageCreateFromGIF',
        IMAGETYPE_JPEG  =>'imageCreateFromJPEG',
        IMAGETYPE_PNG   =>'imageCreateFromPNG',
        IMAGETYPE_BMP   =>'imageCreateFromBMP',
        IMAGETYPE_WBMP  =>'imageCreateFromWBMP',
        IMAGETYPE_XBM   =>'imageCreateFromXBM',
    );


    /**從檔案建立圖片
     * @param string $filePath 檔案位址路徑
     * @return resource 當成功開啟圖片則回傳圖片 resource ID,失敗則是 false
     * */
    public static function createImage($filePath){
        if(!file_exists($filePath)){ return false; }

        /*判斷檔案類型是否可以開啟*/
        $type = exif_imagetype($filePath);
        if(!array_key_exists($type,self::$_createFunc)){ return false; }

        $func = self::$_createFunc[$type];
        if(!function_exists($func)){ return false; }

        return $func($filePath);
    }


    /**hash 圖片
     * @param resource $src 圖片 resource ID
     * @return string 圖片 hash 值,失敗則是 false
     * */
    public static function hashImage($src){
        if(!$src){ return false; }

        /*缩小圖片尺寸*/
        $delta = 8 * self::$rate;
        $img = imageCreateTrueColor($delta,$delta);
        imageCopyResized($img,$src, 0,0,0,0, $delta,$delta,imagesX($src),imagesY($src));

        /*計算圖片灰階值*/
        $grayArray = array();
        for ($y=0; $y<$delta; $y++){
            for ($x=0; $x<$delta; $x++){
                $rgb = imagecolorat($img,$x,$y);
                $col = imagecolorsforindex($img, $rgb);
                $gray = intval(($col['red']+$col['green']+$col['blue'])/3)& 0xFF;

                $grayArray[] = $gray;
            }
        }
        imagedestroy($img);

        /*計算所有像素的灰階平均值*/
        $average = array_sum($grayArray)/count($grayArray);

        /*計算 hash 值*/
        $hashStr = '';
        foreach ($grayArray as $gray){
            $hashStr .= ($gray>=$average) ? '1' : '0';
        }

        return $hashStr;
    }


    /**hash 圖片檔案
     * @param string $filePath 檔案位址路徑
     * @return string 圖片 hash 值,失敗則是 false
     * */
    public static function hashImageFile($filePath){
        $src = self::createImage($filePath);
        $hashStr = self::hashImage($src);
        imagedestroy($src);

        return $hashStr;
    }


    /**比較兩個 hash 值,是不是相似
     * @param string $aHash A圖片的 hash 值
     * @param string $bHash B圖片的 hash 值
     * @return bool 當圖片相似則回傳 true,否則是 false
     * */
    public static function isHashSimilar($aHash, $bHash){
        $aL = strlen($aHash); $bL = strlen($bHash);
        if ($aL !== $bL){ return false; }

        /*計算容許落差的數量*/
        $allowGap = $aL*(100-self::$similarity)/100;

        /*計算兩個 hash 值的漢明距離*/
        $distance = 0;
        for($i=0; $i<$aL; $i++){
            if ($aHash{$i} !== $bHash{$i}){ $distance++; }
        }

        return ($distance<=$allowGap) ? true : false;
    }


    /**比較兩個圖片檔案,是不是相似
     * @param string $aHash A圖片的路徑
     * @param string $bHash B圖片的路徑
     * @return bool 當圖片相似則回傳 true,否則是 false
     * */
    public static function isImageFileSimilar($aPath, $bPath){
        $aHash = ImageHash::hashImageFile($aPath);
        $bHash = ImageHash::hashImageFile($bPath);
        return ImageHash::isHashSimilar($aHash, $bHash);
    }

}
2012-03-16 22:55

[PHP] 檢查 XML 文件結構

利用 SimpleXML 去檢查 XML 結構是否符合規格,為了讓這個程式可以多用途,採用了一個基準文件的作為結構準則,依據裡面定義的節點跟屬性,去檢查文件是否符合基本要求的格式。

<?php

/**檢查 XML 文件結構
 * @param string $baseFilePath 基準結構文件
 * @param string $checkFilePath 待檢查文件
 * @return bool 當結構與基準文件相符合時則回傳 true,否則是 false
 * */
function checkXmlFileStructure($baseFilePath,$checkFilePath){
    /*開啟 Base File*/
    if(!file_exists($baseFilePath)){ return false; }
    $base = simplexml_load_file($baseFilePath);
    if($base===false){ return false; }

    /*開啟 Check File*/
    if(!file_exists($checkFilePath)){ return false; }
    $check = simplexml_load_file($checkFilePath);
    if($check===false){ return false; }

    /*比較起始點的名稱*/
    if($base->getName() != $check->getName()){ return false; }

    /*比較結構*/
    return checkXmlStructure($base,$check);
}

/**檢查 XML 結構
 * @param SimpleXMLElement $base 基準結構物件
 * @param SimpleXMLElement $check 待檢查 XML 物件
 * @return bool 當結構與基準物件相符合時則回傳 true,否則是 false
 * */
function checkXmlStructure($base,$check){
    /*檢查屬性*/
    foreach ($base->attributes() as $name => $baseAttr){
        /*必要的屬性不存在*/
        if(!isset($check->attributes()->$name)){ return false; }
    }

    /*當沒有子節點時,則檢查對象也不能有子節點*/
    if(count($base->children())==0){
        return (count($check->children())==0);
    }

    /*將檢查對象的子節點分群*/
    $checkChilds = array();
    foreach($check->children() as $name => $child){
        $checkChilds[$name][] = $child;
    }

    /*檢查子節點*/
    $checked = array();
    foreach($base->children() as $name => $baseChild){
        /*跳過已經檢查的子節點*/
        if(in_array($name, $checked)){ continue; }
        $checked[] = $name;

        /*檢查必要的子節點是否存在*/
        if(empty($checkChilds[$name])){ return false; }

        foreach ($checkChilds[$name] as $child){
            /*遞迴檢查子節點*/
            if( !checkXmlStructure($baseChild, $child) ){ return false; }
        }
    }

    return true;
}


/*==============================================================================*/

if(isset($_SERVER['argv'])){
    parse_str(preg_replace('/&[\-]+/','&',join('&',$_SERVER['argv'])), $_GET);

    if(empty($_GET['base_file']) || empty($_GET['check_file'])){
        echo "Run: ".basename(__FILE__)." base_file=base.xml check_file=check.xml\n"; exit(1);
    }

    exit( checkXmlFileStructure($_GET['base_file'],$_GET['check_file']) ? 0 : 1);

}else{
    if(empty($_GET['base_file']) || empty($_GET['check_file'])){
        echo "Run: ".basename(__FILE__)."?base_file=base.xml&check_file=check.xml<br />"; exit;
    }

    echo( checkXmlFileStructure($_GET['base_file'],$_GET['check_file']) ? '1' : '0');
}


使用方式(shell)
php check_xml_file_structure.php base_file=base.xml check_file=check.xml

if [ "j$?" != "j0" ]; then
    echo "Run Error"
fi


測試範例 1
base_1.xml
<?xml version="1.0" encoding="UTF-8"?>
<items>
    <item>
        <Category>Category文字</Category>
        <Title>Title文字</Title>
    </item>
</items>

check_1.xml
<?xml version="1.0" encoding="UTF-8"?>
<items>
    <item>
        <Category>Category文字</Category>
        <Title>Title文字</Title>
    </item>
    <item>
        <Category>Category文字</Category>
        <Title>Title文字</Title>
        <Description>Description文字</Description>
    </item>
</items>


測試範例 2
base_2.xml
<?xml version="1.0" encoding="UTF-8"?>
<items>
    <item category="Category文字" Title="Title文字"/>
</items>

check_2.xml
<?xml version="1.0" encoding="UTF-8"?>
<items>
    <item category="Category文字" Title="Title文字" Description="Description文字" />
    <item category="Category文字" Title="Title文字" />
    <item category="Category文字" Title="Title文字" Description="Description文字" />
</items>
2012-03-07 14:06

[Ubuntu] 安裝 Oracle Client 與 PDO_OCI

Oracle Database Instant Client 下載 Client/SDK
(Version 10.2.0.4 Instant Client Package - Basic, Instant Client Package - SDK)
oracle-instantclient-basic-10.2.0.4-1.i386.rpm
oracle-instantclient-devel-10.2.0.4-1.i386.rpm

# 安裝套件轉換器
apt-get install alien 

# 轉換 rpm 套件到 deb,並安裝
alien -i oracle-instantclient-basic*.rpm
alien -i oracle-instantclient-devel*.rpm

# 安裝 Apache2,PHP,MySQL
apt-get install apache2 php5 mysql-server php5-mysql libapache2-mod-php5

# 安裝 PEAR 
apt-get install php-pear php5-dev dh-make-php make re2c

# 下載 PDO_OCI 原始檔 
pecl download pdo
pecl download pdo_oci
tar zxvf PDO-1.0.3.tgz
tar zxvf PDO_OCI-1.0.tgz

mkdir -p PDO_OCI-1.0/include/php/ext/
mv PDO-1.0.3 PDO_OCI-1.0/include/php/ext/pdo
cd PDO_OCI-1.0/
phpize
./configure --with-pdo-oci=instantclient,/usr,10.2.0.4

make -j$(grep processor /proc/cpuinfo |wc -l)
make install  # /usr/lib/php5/20xxxxxx+lfs/pdo_oci.so

vim /etc/php5/conf.d/pdo_oci.ini # 建立 pdo_oci.ini, 內容如下:
extension=pdo_oci.so

vim /etc/apache2/envvars # 在最後面加入環境變數, 內容如下: 
export NLS_LANG="TRADITIONAL CHINESE_TAIWAN.UTF8"
export NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS"

# 重新啟動 Apache 
service apache2 restart

參考來源:
Debian 安裝設定 PHP 連 Oracle extension 使用 PDO(PDO_OCI)
Oracle Instant Client