tag:blogger.com,1999:blog-59465307047421309702024-03-06T16:20:07.273+08:00Jax 的工作紀錄除了在整理學習上的經驗,同時也能幫助其他需要的人Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.comBlogger69125tag:blogger.com,1999:blog-5946530704742130970.post-75816354379789685932021-10-15T17:26:00.004+08:002023-02-25T18:17:08.463+08:00全域的 Exception 處理<p>大部分具有 Exception 機制的程式語言都有提供全域的 Exception 處理,如果你已經有用 log 去記錄錯誤的習慣的話,與其在程式裡佈滿了 try catch,不如在全域處裡中去記錄沒有被 catch 的 Exception,這樣程式就可以更乾淨了,而且程式如果發生異常的關閉時也會進入全域處裡,可以確保 Exception 妥善地被記錄。</p>
<br/>
<h3>Console, WinForm 程式</h3>
<pre class="cs" name="code">
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);
}
}
}
</pre>
<br/>
<h3>Web 程式</h3>
<p>在 Global.asax.cs 中可以設定 Application_Error 就可以攔截未處裡的 Exception,除了用這個方法記錄錯誤,還可以用現有的套件(<a href="https://elmah.github.io/a/mvc/" target="_blank">elmah</a>)幫我們完成。</p>
<pre class="cs" name="code">
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);
}
//...
}
}
</pre>
<br/>
<h3>Net core 程式</h3>
<p>Net core 的程式都是相同的方式,Web Request 的要另外配置</p>
<pre class="cs" name="code">
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();
}
}
}
</pre>
<br/>
<h3>Net core 3.1 Web 程式</h3>
<p>在 Startup.cs 中配置 middleware 進行 Exception 攔截</p>
<pre class="cs" name="code">
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 再丟出去給別人處理 */
}
});
}
</pre>
<br/>
<h3>週期性 Thread 的處裡方式</h3>
<p>將主要邏輯寫在另外 method 裡,這樣可以專注在 Exception 上。</p>
<pre class="cs" name="code">
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()
{
// 主要的邏輯程式寫在這裡
}
</pre>
<br/>
<h3>PHP 錯誤處理</h3>
<p>Ref: <a href="https://www.php.net/manual/zh/function.set-error-handler.php" target="_blank">set_error_handler</a>, <a href="https://www.php.net/manual/zh/function.set-exception-handler.php" target="_blank">set_exception_handler</a></p>
<pre class="php" name="code">
<?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;
</pre>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-25907685581100966402015-10-23T16:52:00.001+08:002015-10-25T22:41:52.836+08:00[PHP] 從 RGB 轉換到 HSBHSB(HSV)通過色相/飽和度/亮度三要素來表達顏色。<br />
<ul><li>H(Hue):表示颜色的類型 (例如红色,绿色或者黄色),取值範圍為 0 ~ 360,其中每一个值代表一種顏色</li>
<li>S(Saturation):顏色的飽和度從 0 到 1,有时候也稱為纯度 (0 表示灰度圖,1 表示纯的顏色)</li>
<li>B(Brightness or Value):顏色的明亮程度從 0 到 1 (0 表示黑色,1 表示特定飽和度的顏色)</li>
</ul><br />
<pre class="php" name="code">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);
}
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-52799284520576543602015-10-23T16:44:00.002+08:002015-10-23T16:44:08.805+08:00[PHP] 從 Hex 轉換到 RGB<pre class="php" name="code">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']);
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-61888603785399785712015-02-26T14:40:00.001+08:002023-02-25T21:30:47.901+08:00[PHP][Java][C#] 用 XSD 驗證 XML<strong>menu_config.xml</strong> 要驗證的 XML<br />
<pre class="xml:nocontrols" name="code"><?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>
</pre><br />
<br />
<strong>menu_config.xsd</strong> 結構定義<br />
<pre class="xml:nocontrols" name="code"><?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>
</pre><dl><dt>element minOccurs</dt>
<dd>最少的出現次數,不設置為至少出現 1 次,設置 0 為可有可無。</dd>
<dt>element maxOccurs</dt>
<dd>最大的出現次數,設置 unbounded 為無上限。</dd>
<dt>attribute type</dt>
<dd>型別必須為 QName,常用的有 xs:string, xs:date, xs:int, xs:integer, xs:decimal, xs:boolean, xs:double, xs:float。</dd> </dl><br />
<br />
<strong>PHP</strong><br />
<pre class="php:nocontrols" name="code">$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();
}
</pre><br />
<br />
<strong>C#</strong><br />
<pre class="cs:nocontrols" name="code">var menuConfig = XDocument.Load("menu_config.xml");
schemas.Add("", XmlReader.Create("menu_config.xsd"));
menuConfig.Validate(schemas, (o, e) => {
e.Message.Dump();
});
</pre><br />
<br />
<strong>Java</strong><br />
<pre class="java:nocontrols" name="code">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());
}
}
}
</pre><br />
<br />
<br />
參考資料:<br />
<a href="https://msdn.microsoft.com/zh-tw/library/ms256142(v=vs.110).aspx" target="_blank">XML 結構描述項目 - MSDN</a><br />
<a href="http://www.w3schools.com/schema/default.asp" target="_blank">XML Schema Tutorial W3Schools</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-85072958064986502822015-01-31T21:02:00.001+08:002015-01-31T21:36:21.047+08:00[轉載] PHP 配置open_basedir,让各虚拟站点独立运行轉載自:<a href="http://www.cnblogs.com/flashlm/archive/2009/11/11/php_open_basedir_seprate_site.html" target="_blank">PHP 配置open_basedir,让各虚拟站点独立运行 - canbeing - 博客园</a><br />
<br />
open_basedir 可将用户访问文件的活动范围限制在指定的区域,通常是其家目录的路径,也可用符号"."来代表当前目录。open_basedir 也可以同时设置多个目录, 在 Windows 中用分号(;)分隔目录,在任何其它系统中用冒号(:)分隔目录。当其作用于 Apache 模块时,父目录中的 open_basedir 路径自动被继承。<br />
<br />
以下以 Linux 系统下的配置为例:<br />
<br />
<strong>方法一:在 php.ini 里配置</strong><br />
<code>open_basedir = .:/tmp/</code><br />
<br />
<strong>方法二:在 Apache 配置的 VirtualHost 里设置</strong><br />
<code>php_admin_value open_basedir .:/tmp/</code><br />
<br />
<strong>方法三:在 Apache 配置的 Direcotry .htaccess 里设置</strong><br />
<code>php_admin_value open_basedir .:/tmp/</code><br />
<br />
<br />
<strong>关于三个配置方法的解释:</strong><br />
<ol><li>方法二的优先级高于方法一,也就是说方法二会覆盖方法一<br/>方法三的优先级高于方法二,也就是说方法三会覆盖方法二</li>
<li>配置目录里加了 <code>/tmp/</code> 是因为php默认的临时文件(如上传的文件、session 等)会放在该目录,所以一般需要添加该目录,否则部分功能将无法使用</li>
<li>配置目录里加了 <code>.</code> 是指运行php文件的当前目录,这样做可以避免每个站点一个一个设置</li>
<li>如果站点还使用了站点目录外的文件,需要单独在对应 VirtualHost 设置该目录</li>
</ol>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-33163877699696296622014-03-05T23:38:00.001+08:002023-02-25T21:32:20.715+08:00利用 HTTP Status Codes 傳遞 Ajax 成功失敗的狀態一般處理 Ajax 回應時會傳送的資訊種類有:資料、成功訊息、錯誤訊息、失敗訊息以及處理狀態,傳遞的資訊種類並不一致,再加上除了資料之外,通常還希望能傳遞處理狀態,這種情況大部分會選擇是以 JSON 的方式傳遞這兩個訊息,以下是常見的幾種格式:<br />
<br />
<pre class="js" name="code">{ code: 1, msg: "OK" }
{ success: true, result: "data", errorMsg: "" }
{ status: 'success', result: [], errorMsg: "" }
//...
</pre><br />
但以執行狀態跟操作行為作一個歸納,可以區分以下幾種回傳結果:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr class="header">
<th>資料操作</th>
<th>HTTP Method</th>
<th>成功</th>
<th>錯誤/失敗</th>
</tr>
<tr>
<td>檢視(Read)</td>
<td>GET</td>
<td>資料</td>
<td>錯誤/失敗訊息</td>
</tr>
<tr>
<td>新增(Create)<br />
修改(Update)<br />
刪除(Delete)</td>
<td>POST</td>
<td>成功訊息</td>
<td>錯誤/失敗訊息</td>
</tr>
</table><br />
從上面的歸納可以看出規律性,接著只要有方法可以傳送處理的狀態,以及能夠區分資料的種類,其實就單純很多,而 HTTP Status Codes 就是用來傳遞 HTTP 的處理狀態,如果利用這個方式來傳遞自訂的處理狀態,這樣 HTTP Content 就可以很單純傳遞資料,讓資料格式不受限於 JSON,還可以使用其他格式(text, xml, html),而且 XMLHttpRequest 本身就有處理 HTTP Status Codes 的能力,而 jQuery.ajax 也有提供 error status 的處理,所以可以利用這個來定義狀態的處理,在 HTTP Status Codes 有幾個已經定義狀態,很適合用來回傳處理狀態的資訊:<br />
<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr>
<th>400</th>
<td>Bad Request</td>
<td>錯誤的請求</td>
<td>適用在表單內容的錯誤,如必填欄位未填、Email 格式錯誤</td>
</tr>
<tr>
<th>403</th>
<td>Forbidden</td>
<td>沒有權限,被禁止的</td>
<td>適用在沒有登入或權限不足</td>
</tr>
<tr>
<th>500</th>
<td>Internal Server Error</td>
<td>內部服務器錯誤</td>
<td>適用在程式的錯誤</td>
</tr>
</table><br />
<br />
jQuery 接收資訊的範例<br />
<pre class="js" name="code">$.ajax({
type: "POST",
url: document.location,
success: function (data, textStatus, jqXHR) {
alert(data);
},
error: function (jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText);
}
});
</pre> <br />
<br />
PHP 傳遞錯誤訊息的範例 <br />
<pre class="php" name="code">if (php_sapi_name() == 'cgi'){
header("Status: 400 Bad Request");
}else{
header("HTTP/1.0 400 Bad Request");
}
exit("儲存失敗!!");
</pre><br />
<br />
C# MVC 傳遞錯誤訊息的範例 <br />
<pre class="cs" name="code">Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 400;
return Content("儲存失敗!!");
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-52031273633087622812014-03-05T14:44:00.001+08:002014-03-05T14:44:59.489+08:00使用 Stream 讀取 cUrl 下載結果使用 stream 的好處就是用多少拿多少,不會因為資料大小而占用大量的記憶體。<br />
<br />
<pre class="php" name="code">$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);
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-37192443240748414832014-03-05T14:30:00.000+08:002014-03-05T14:30:54.770+08:00使用 stream 讀取指令列結果 <pre class="php" name="code">$cmd = "find ./ -path './*/*/*'";
/* 執行指令,並取得 stream */
$fp = popen($cmd, "r");
/*一次讀取一行*/
while (($filePath = fgets($fp)) !== false) {
$filePath = trim($filePath);
var_dump($filePath);
}
/* 關閉 stream */
pclose($fp);
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-17281822509853364672013-08-17T14:55:00.001+08:002013-08-17T15:01:57.490+08:00[PHP] url, base64, sprite 三種格式的 icons.css 產生器先做一個假設,如果 icon 的檔名就是 css 的 class 樣式名稱,那麼我們只要掃瞄資料夾的 Icon 圖檔,然後產生對應的 CSS 檔案,這樣就可以省去製作 Sprite 圖檔跟維護 CSS 對應的問題。<br />
<br />
第一種 url 格式只是取得路徑的問題。<br />
<br />
第二種 base64 格式可以透過 <code>base64_encode(file_get_contents($path));</code> 就簡單的達成。<br />
<br />
第三種 sprite 格式則使用 <a target="_blank" href="http://php.net/manual/en/book.imagick.php">Imagick</a> 去處理,會比較快樂。<br />
<br />
<br />
接著以下就是如何達成的程式片段:<br />
<br />
<pre class="php" name="code"><?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;
</pre><br />
<br />
下載完整程式: <a target="_blank" href="https://dl.dropboxusercontent.com/u/16784943/code-demo/php_make_icons_css.zip">php_make_icons_css.zip</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-18671039125404125602013-06-04T23:14:00.000+08:002013-06-04T23:14:12.272+08:00[PHP] 分配上傳檔案的路徑<pre class="php" name="code"><?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"
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-49095090587741562782013-05-30T22:21:00.000+08:002013-07-18T12:32:50.903+08:00[PHP] 從網頁執行 SVN 更新想說寫一個透過網頁就可以執行 SVN 更新的程式,結果並不是我想得那樣簡單,有一些眉角需要注意的說。<br />
<br />
先以 Apache 的使用者帳號執行 SVN checkout,這樣 Apache 才有 SVN 的連結權力,才可以透過網頁執行 SVN update<br />
<br />
<pre class="sh" name="code">su -s /bin/bash www-data
cd /var/www
svn checkout http://www.xxx.com/svn/my_site
</pre><br />
<br />
<ol><li>在用 PHP 執行 shell 指令前要加上 <strong>export LANG=C.UTF-8</strong> 的環境宣告,不然 SVN update 時遇到中文會出現 error,Ubuntu 的 Apache 預設是 LANG=C</li>
<li>接著要為 SVN 補上 <strong>--accept theirs-full</strong> 的參數,這是當衝突發生時,都以 SVN Server 的檔案版本為主</li>
<li>最後再加上 <strong>2>&1</strong>,讓 PHP 可以取得包含錯誤的所有訊息</li>
</ol><pre class="php" name="code"><?php
putenv('LANG=C.UTF-8');
$result = shell_exec('svn update --accept theirs-full /var/www/my_site 2>&1');
echo nl2br($result);
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-70829214216860995762013-05-29T20:47:00.000+08:002013-05-29T21:04:54.280+08:00[轉載][PHP] Pattern Modifiers - 正規表示式的修飾符下面是當前規則表達式裡可用的修飾. 括號內的名字是那些修飾符的內部 PCRE 名字.<br />
<br />
<strong>i (PCRE_CASELESS)</strong><br />
如果設置了這個修飾符, 則表達式不區分大小寫.<br />
<br />
<strong>m (PCRE_MULTILINE)</strong><br />
默認的, PCRE 認為目標字符串值是單行字符串 (即使他確實包含多行). 行開始標記 (^) 只匹配字符串的開始部分, 而行結束標記 ($) 只匹配字符串的尾部,或者一個結束行(除非指定 E 修飾符). 這個和 Perl 裡面一樣.<br />
<br />
如果設定了這個修飾符, 行開始和行結束結構分別匹配在目標字符串任何新行的當前位置後面的或者以前的, 和每一個開始和結束一樣. 這個等於 Perl 裡面的 /m 修飾符. 如果目標字符串沒有 "n" 字符, 或者模式裡沒有 ^ 或 $ ,這個修飾符不起作用.<br />
<br />
<strong>s (PCRE_DOTALL)</strong><br />
如果設置這個修飾符, 模式裡的一個"點"將匹配所有字符, 包括換行. 沒有他, 換行將被排除在外. 這個修飾符等同於 Perl 裡面的 /s 修飾符. 一個相反的類型,例如 [^a] 將總是匹配換行字符,而不管這個修飾符的限制.<br />
<br />
<strong>x (PCRE_EXTENDED)</strong><br />
如果設置這個修飾符, 模式裡面的空格數句將會被全部忽略,除非用轉義符或者一個字符的內部類型,還有所有字符類型外的未轉義的 # 號之間的也被忽略. 這個等同於 Perl 裡面的 /x 修飾符, 這樣可以複雜的模式裡面加入註釋. 注意,只適用於數據字符. 空格字符將不會在指定的模式字符指定順序中出現。<br />
<br />
<strong>e</strong><br />
如果設置這個修飾符, preg_replace() 將在替換值裡進行正常的涉及到 \ 的替換, 等同於在 PHP 代碼裡面一樣, 然後用於替換搜索到的字符串.<br />
<br />
只在 preg_replace() 裡使用這個修飾符; 其它 PCRE 函數忽略他.<br />
<br />
<strong>A (PCRE_ANCHORED)</strong><br />
如果設置這個修飾符, 模式被強制為錨(anchored), 也就是說, 他將值匹配搜索字符串的開始. 這個效果可以通過恰當的模式結構自身來實現,那是在 Perl 裡面的唯一途徑.<br />
<br />
<strong>D (PCRE_DOLLAR_ENDONLY)</strong><br />
如果設置這個修飾符,則模式裡的 $ 修飾符將僅匹配目標字符串裡的尾部. 沒有這個修飾符, $ 字符也匹配新行的尾部 (但是不再新行的前面). 如果設置了 m 修飾符則忽略這個修飾符. 在 Perl 裡面沒有類似的.<br />
<br />
<strong>S</strong><br />
如果一個模式將被使用多次, 使用長些時間分析他來來提高匹配的速度. 如果使用這個修飾符,則進行額外的分析. 目前, 研究模式僅用於非錨模式,沒有一個固定的開始字符.<br />
<br />
<strong>U (PCRE_UNGREEDY)</strong><br />
這個修飾符翻轉數量的 "greediness" ,使得默認不被 greedy,但是如果你緊跟問號(?),則可以 greedy. 這個和 Perl 不兼容. 這個也可以通過在模式裡面的(?U) 修飾符得到.<br />
<br />
<strong>X (PCRE_EXTRA)</strong><br />
這個修飾符打開額外的功能,這些和 Perl 不兼容. 任何模式裡面的後面帶字符但沒有特殊意義的反斜槓將引起錯誤, 從而儲備這些聯合用於將來的擴充. 默認的, 在 Perl 裡面, 反斜槓後面有無意義的字符被當成正常的 literal. 目前還沒有其他的控制特徵 <br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-85221825939894050832013-04-04T22:05:00.000+08:002013-04-04T22:06:05.258+08:00PDO fetch 模式的回傳結果PDO 的 fetch 模式功能實在是太方便了,但每次要產生想要的結果都要試太麻煩了,這裡列出可能的組合。<br />
<br />
<pre class="php" name="code"><?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';
},
)
*/
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-50737341489854991862012-09-30T22:24:00.001+08:002023-02-25T21:36:52.353+08:00[轉載][PHP] preg_replace 效能測試 (將兩個空白字元以上取代成一個)轉載自:<a target="_blank" href="http://blog.wu-boy.com/2012/09/php-preg_replace-performance/">[PHP] preg_replace 效能測試 (將兩個空白字元以上取代成一個) 小惡魔 – 電腦技術 – 工作筆記 – AppleBOY</a> <br />
<br />
<a target="_blank" href="http://php.net/manual/en/function.preg-replace.php" target="_blank">preg_replace</a> 可以使用正規語法來取代字串任何字元,,今天探討取代空白字元的效能,雖然這是個不起眼的效能評估,一般人不太會這樣去改,不過這是國外 PHP Framework 有人提出來修正的,經過許多人的測試一致同意。功能就是一篇文章內如果有多餘的空白能空取代成一個,一般人都會用 <strong>\s+</strong> 正規語法,畢竟大家都知道 \s 代表單一空白或 \r 等符號,但是國外有人提出用 <strong>{2,}</strong> 方式來取代空白。程式碼如下,大家可以測試看看。<br />
<br />
<pre class="php" name="code"><?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;
</pre><br />
測試結果(1萬次)<br />
<br />
<pre class="cfg" name="code">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
</pre><br />
結論至少快蠻多的,如果整體資料量再大一點,我想差別會更大,那至於要不要用呢,就看個人了 XD。Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com1tag:blogger.com,1999:blog-5946530704742130970.post-13295502360347923762012-05-05T01:53:00.000+08:002013-06-10T22:34:10.512+08:00[轉載] 让PHP更快的提供文件下载作者:<a target="_blank" href="http://www.laruence.com/">Laruence</a><br />
本文地址:<a target="_blank" href="http://www.laruence.com/2012/05/02/2613.html">http://www.laruence.com/2012/05/02/2613.html</a><br />
<br />
一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件.<br />
<br />
但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们采用让PHP来做转发, 为用户提供文件下载.<br />
<br />
<pre class="php" name="code"><?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);
</pre><br />
但是这个有一个问题, 就是如果文件是中文名的话, 有的用户可能下载后的文件名是乱码.<br />
<br />
于是, 我们做一下修改(参考: :<br />
<br />
<pre class="php" name="code"><?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);
</pre><br />
恩, 现在看起来好多了, 不过还有一个问题, 那就是readfile, 虽然PHP的readfile尝试实现的尽量高效, 不占用PHP本身的内存, 但是实际上它还是需要采用MMAP(如果支持), 或者是一个固定的buffer去循环读取文件, 直接输出.<br />
<br />
输出的时候, 如果是Apache + PHP mod, 那么还需要发送到Apache的输出缓冲区. 最后才发送给用户. 而对于Nginx + fpm如果他们分开部署的话, 那还会带来额外的网络IO.<br />
<br />
那么, 能不能不经过PHP这层, 直接让Webserver直接把文件发送给用户呢?<br />
<br />
今天, 我看到了一个有意思的文章: <a target="_blank" href="http://www.jasny.net/articles/how-i-php-x-sendfile/">How I PHP: X-SendFile</a>.<br />
<br />
我们可以使用Apache的module <a target="_blank" href="https://tn123.org/mod_xsendfile/">mod_xsendfile</a>, 让Apache直接发送这个文件给用户:<br />
<br />
<pre class="php" name="code"><?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");
</pre><br />
X-Sendfile头将被Apache处理, 并且把响应的文件直接发送给Client.<br />
<br />
Lighttpd和Nginx也有类似的模块, 大家有兴趣的可以去找找看<br />
<br />
<hr />mod-xsendfile 在 Ubuntu 的安裝方法:<br />
<strong>sudo apt-get install libapache2-mod-xsendfile</strong>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-57973715503495346402012-05-05T01:27:00.001+08:002013-06-10T22:33:47.762+08:00[PHP] 使用 php5-ffmpeg 擷取影片圖片前幾天在玩 FFmpeg 的時後,突然發現 Ubuntu 上多了 php5-ffmpeg 這個擴充套件,就想來玩玩看,看好不好用,有兩個結論:<ol><li>讀取影片取決於 FFmpeg 的支援性,如果想要什麼格式都支援的話,建議自己重新編譯 FFmpeg。</li>
<li>效率並沒有我想像中的快,兩分鐘的影片取十張圖,大約 30 秒。</li>
</ol>安裝方法:<br />
<strong>sudo apt-get install ffmpeg php5-ffmpeg php5-gd</strong><br />
<br />
擷圖測試範例:<br />
<pre class="php" name="code"><?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 />';
}
</pre><br />
參考文件:<br />
<a target="_blank" href="http://ffmpeg-php.sourceforge.net/doc/api/ffmpeg_movie.php">ffmpeg_movie object methods</a><br />
<a target="_blank" href="http://www.sitepoint.com/forums/printthread.php?t=292670">FFmpeg and PHP</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-6086723317997082992012-04-29T21:16:00.001+08:002012-04-29T21:16:51.090+08:00[Linux] 檢查 SAMBA 與 NFS Server 是否存在通常會透過 <strong>/etc/fstab</strong> 來處理掛載的設定,然後再使用 <strong>mount -a</strong> 來重新確認掛載,最好在排程的程序用到掛載目錄時也執行一次 mount -a,掛載目錄在斷線後是不會自動回復的,mount -a 的 Timeout 其實還蠻久的,尤其是 Server 不存在的時候,所以最好還是用對應的 client 先確認 server 是否存在。 <br />
<br />
而檢查 NFS 的 client 可以用 showmount 來處理,在 Ubuntu 上的安裝方式如下: <br />
<strong>sudo aptitude install nfs-common</strong><br />
<br />
而 SAMBA 的 client 則是用 smbclient,在 Ubuntu 上的安裝方式如下:<br />
<strong>sudo aptitude install smbclient</strong><br />
<br />
<br />
<strong>檢查 NFS Server 是否存在的流程</strong><br />
<br />
以 Shell 的方式檢查<br />
<pre class="sh" name="code"># 先以 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
</pre><br />
以 PHP 的方式檢查<br />
<pre class="php" name="code">/*先以 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;
}
</pre><br />
<br />
<br />
<strong>檢查 SAMBA Server 是否存在的流程</strong><br />
<br />
以 Shell 的方式檢查<br />
<pre class="sh" name="code"># 先以 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
</pre><br />
以 PHP 的方式檢查<br />
<pre class="php" name="code">/*先以 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;
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-18254329373343038092012-04-23T16:47:00.000+08:002013-06-10T22:38:42.754+08:00[PHP] 簡易的圖片相似度比較由於<a target="_blank" href="http://www.welefen.com/similar-image-search-in-php.html">相似图片搜索的php实现</a>的 API 不怎麼符合我的用途,所以我重新定義 API 的架構,改寫成比較簡單的函數方式,雖然還是用物件的方式包裝。<br />
<br />
<pre class="php" name="code"><?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);
}
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com2tag:blogger.com,1999:blog-5946530704742130970.post-17682424429005087692012-03-16T22:55:00.001+08:002012-03-16T22:59:04.829+08:00[PHP] 檢查 XML 文件結構利用 SimpleXML 去檢查 XML 結構是否符合規格,為了讓這個程式可以多用途,採用了一個基準文件的作為結構準則,依據裡面定義的節點跟屬性,去檢查文件是否符合基本要求的格式。<br />
<br />
<pre class="php" name="code"><?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');
}
</pre><br />
<br />
<strong>使用方式(shell)</strong><br />
<pre class="sh" name="code">php check_xml_file_structure.php base_file=base.xml check_file=check.xml
if [ "j$?" != "j0" ]; then
echo "Run Error"
fi
</pre><br />
<br />
<strong>測試範例 1</strong><br />
base_1.xml<br />
<pre class="xml" name="code"><?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<Category>Category文字</Category>
<Title>Title文字</Title>
</item>
</items>
</pre><br />
check_1.xml<br />
<pre class="xml" name="code"><?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>
</pre><br />
<br />
<strong>測試範例 2</strong><br />
base_2.xml<br />
<pre class="xml" name="code"><?xml version="1.0" encoding="UTF-8"?>
<items>
<item category="Category文字" Title="Title文字"/>
</items>
</pre><br />
check_2.xml<br />
<pre class="xml" name="code"><?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>
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-7912197072555984552012-03-07T14:06:00.002+08:002013-06-10T22:40:22.436+08:00[Ubuntu] 安裝 Oracle Client 與 PDO_OCI於 <a target="_blank" href="http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html">Oracle Database Instant Client</a> 下載 Client/SDK <br />
(Version 10.2.0.4 Instant Client Package - Basic, Instant Client Package - SDK)<br />
<strong>oracle-instantclient-basic-10.2.0.4-1.i386.rpm</strong><br />
<strong>oracle-instantclient-devel-10.2.0.4-1.i386.rpm</strong><br />
<br />
<pre class="sh" name="code"># 安裝套件轉換器
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
</pre><br />
參考來源:<br />
<a target="_blank" href="http://blog.longwin.com.tw/2008/06/debian_php_oracle_ext_pdo_oci_2008/">Debian 安裝設定 PHP 連 Oracle extension 使用 PDO(PDO_OCI)</a><br />
<a target="_blank" href="https://help.ubuntu.com/community/Oracle%20Instant%20Client">Oracle Instant Client</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-39865778807319570452012-02-01T14:05:00.003+08:002013-06-10T22:41:55.222+08:00[PHP] 用 SoapServer 建立 Microsoft Office Research Service這裡我用 <a target="_blank" href="http://www.jool.nl/jool-tech-zone/webservice-helper/download/">Webservice Helper</a> 來處理 WSDL 的問題,所以範例也是 Base 在 <a target="_blank" href="http://www.jool.nl/jool-tech-zone/webservice-helper/download/">Webservice Helper</a> 上面,在開始前請檢查是否有 <b>php_soap</b> 與 <b>php_xsl</b> 這兩個套件。<br />
<br />
<br />
為了相容於 Research Service 的名稱空間,在輸出入的定義上需要在包一層資料類型,所以需要以下的類型定義:<br />
StatusResponse.class.php<br />
<pre class="php" name="code"><?php
/**
* Return object to Status method
*/
class StatusResponse {
/** @var string */
public $StatusResult;
}
</pre><br />
Registration.class.php<br />
<pre class="php" name="code"><?php
/**
* Input object to Registration method
*/
class Registration {
/** @var string */
public $registrationXml;
}
</pre><br />
RegistrationResponse.class.php<br />
<pre class="php" name="code"><?php
/**
* Return object to Registration method
*/
class RegistrationResponse {
/** @var string */
public $RegistrationResult;
}
</pre><br />
Query.class.php<br />
<pre class="php" name="code"><?php
/**
* Input object to Query method
*/
class Query {
/** @var string */
public $queryXml;
}
</pre><br />
QueryResponse.class.php<br />
<pre class="php" name="code"><?php
/**
* Return object to Query method
*/
class QueryResponse {
/** @var string */
public $QueryResult;
}
</pre><br />
<br />
接著是建立 Web Service 的 Method,主要定義 Registration 跟 Query 這兩個 Method 就可以了,Registration 是用在新增 Research Service 到 Research Pane 時會呼叫的 Method,主要是提供 Research Pane 所需要的 Service 資訊。<br />
而 Query 則是真正再處理資料查詢的 Method,而 QueryResponse 中的 domain 及 QueryId 必需與 QueryXml 中所給的相同,不然 Client 端會無法辨識回傳結果。 <br />
<pre class="php" name="code"><?php
/**
* Microsoft Office Research Service
*
*/
class MsOfficeResearch {
/**
* Entry point to test if server is alive. Will return 'ONLINE' or 'OFFLINE'
* @param void
* @return StatusResponse
*/
function Status() {
$result = new StatusResponse;
$result->StatusResult = 'ONLINE';
return $result;
}
/**
* Basic registration entry point
* @param Registration $registrationXml
* @return RegistrationResponse
*/
public function Registration($registrationXml) {
// debugDump('registrationXml: '.$request->registrationXml);
$dom = new DOMDocument();
$proUpdate = $dom->createElementNS("urn:Microsoft.Search.Registration.Response",'ProviderUpdate');
$proUpdate->appendChild( new DOMElement('Status',"SUCCESS") );
$providers = $proUpdate->appendChild( new DOMElement('Providers') );
$provider = $providers->appendChild( new DOMElement('Provider') );
$provider->appendChild( new DOMElement('Id',"{62E1D68D-E1C4-4CC5-9D0C-D4B7999C4B77}") );
$provider->appendChild( new DOMElement('Name',"Research Service") );
$provider->appendChild( new DOMElement('QueryPath',"http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']) );
$provider->appendChild( new DOMElement('RegistrationPath',"http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']) );
$provider->appendChild( new DOMElement('Type',"SOAP") );
$provider->appendChild( new DOMElement('Revision',"1") );
$services = $provider->appendChild( new DOMElement('Services') );
$service = $services->appendChild( new DOMElement('Service') );
$service->appendChild( new DOMElement('Id',"{0297CD20-047F-4256-0104-000004040001}") );
$service->appendChild( new DOMElement('Name',"Research Service Name") );
$service->appendChild( new DOMElement('Description',"Research Service Description") );
$service->appendChild( new DOMElement('Copyright',"") );
$service->appendChild( new DOMElement('Display',"On") );
$service->appendChild( new DOMElement('Category',"RESEARCH_GENERAL") );
$response = new RegistrationResponse;
$response->RegistrationResult = $dom->saveXML($proUpdate);
return $response;
}
/**
* Basic entrypoint for Query
* @param Query $queryXml
* @return QueryResponse
*/
function Query($queryXml) {
if(is_object($queryXml)){ $queryXml = $queryXml->queryXml; }
// debugDump('queryXml: '.$queryXml);
/*解析請求的 XML*/
$dom = new DOMDocument();
$dom->loadXML($queryXml);
$domain = $dom->getElementsByTagName('Query')->item(0)->getAttribute('domain');
$queryId = $dom->getElementsByTagName('QueryId')->item(0)->nodeValue;
/*建立返回的結構*/
$packet = $dom->createElementNS("urn:Microsoft.Search.Response",'ResponsePacket');
$packet->setAttribute('revision',"1");
$response = $packet->appendChild( new DOMElement('Response') );
$response->setAttribute('domain', $domain );
$response->appendChild( new DOMElement('QueryId', $queryId) );
$range = $response->appendChild( new DOMElement('Range') );
$results = $range->appendChild( new DOMElement('Results') );
$content = $results->appendChild( new DOMElement('Content',"","urn:Microsoft.Search.Response.Content") );
/*請求查詢*/
$status = "ERROR_NO_RESULTS_FOUND";
$queryText = trim( $dom->getElementsByTagName('QueryText')->item(0)->nodeValue );
if(!empty($queryText)){
// debugDump($queryText);
$line = $content->appendChild( new DOMElement('P') );
$line->nodeValue = htmlspecialchars($queryText, ENT_QUOTES);
$status = "SUCCESS";
}
$response->appendChild( new DOMElement('Status',$status) );
/*設定回傳結構*/
$response = new QueryResponse;
$response->QueryResult = $dom->saveXML($packet);
return $response;
}
}
</pre><br />
<br />
關於資料交換的 XML 格式如下:<br />
Registration Response XML<br />
<pre class="xml" name="code"><ProviderUpdate xmlns='urn:Microsoft.Search.Registration.Response'>
<Status>SUCCESS</Status>
<Providers><Provider>
<Id>{88686849-2DD9-474d-9300-778E3336FA77}</Id>
<Name>EpgTools</Name>
<QueryPath>http://localhost/service.php</QueryPath>
<RegistrationPath>http://localhost/service.php</RegistrationPath>
<Type>SOAP</Type>
<Revision>1</Revision>
<Services><Service>
<Id>63d351db-d12e-448b-8541-9f794e1ec977</Id>
<Name>Research Service Name</Name>
<Data>1031/1033/4</Data>
<Description>Research Service Description</Description>
<AboutPath>helpId:553713956</AboutPath>
<Copyright>All content Copyright (c) 2003.</Copyright>
<Display>On</Display>
<Category>RESEARCH_GENERAL</Category>
<OptionsPath></OptionsPath>
<Parental>Unsupported</Parental>
</Service></Services>
</Provider></Providers>
</ProviderUpdate>
</pre><br />
Query XML<br />
<pre class="xml" name="code"><QueryPacket xmlns="urn:Microsoft.Search.Query" revision="1" build="(11.0.5606)">
<Query domain="{6E3B8AA1-5131-403E-AEF3-E7AFC2E88557}">
<QueryId>{5A4FD162-DB71-45BC-8721-F059D28947B3}</QueryId>
<OriginatorId>{F6FF7BE0-F39C-4ddc-A7D0-09A4C6C647A5}</OriginatorId>
<SupportedFormats>
<Format revision="1">urn:Microsoft.Search.Response.Document:Document</Format>
<Format revision="1">urn:Microsoft.Search.Response.Content:Content</Format>
<Format revision="1">urn:Microsoft.Search.Response.Form:Form</Format>
</SupportedFormats>
<Context>
<QueryText type="STRING" language="zh-tw">test</QueryText>
<LanguagePreference>zh-tw</LanguagePreference>
<Requery></Requery>
</Context>
<Range id="result"></Range>
<OfficeContext xmlns="urn:Microsoft.Search.Query.Office.Context" revision="1">
<UserPreferences>
<ParentalControl>false</ParentalControl>
</UserPreferences>
<ServiceData>EWATW</ServiceData>
<ApplicationContext>
<Name>Microsoft Office</Name>
<Version>(11.0.5606)</Version>
<SystemInformation>
<SkuLanguage>zh-tw</SkuLanguage>
<LanguagePack>zh-tw</LanguagePack>
<InterfaceLanguage>zh-tw</InterfaceLanguage>
<Location>TW</Location>
</SystemInformation>
</ApplicationContext>
<QueryLanguage>zh-tw</QueryLanguage>
<KeyboardLanguage>zh-tw</KeyboardLanguage>
</OfficeContext>
<Keywords xmlns="urn:Microsoft.Search.Query.Office.Keywords" revision="1">
<QueryText>test</QueryText>
<Keyword>
<Word>test</Word>
</Keyword>
</Keywords>
</Query>
</QueryPacket>
</pre><br />
Query Response XML<br />
<pre class="xml" name="code"><ResponsePacket xmlns="urn:Microsoft.Search.Response" revision="1">
<Response domain="{6e3b8aa1-5131-403e-aef3-e7afc2e88557}">
<QueryId>{5A4FD162-DB71-45BC-8721-F059D28947B3}</QueryId>
<Range><Results>
<Content xmlns="urn:Microsoft.Search.Response.Content">
<any />
</Content>
</Results></Range>
<Status>SUCCESS</Status>
</Response>
</ResponsePacket>
</pre><br />
<br />
範例下載:<br />
<a target="_blank" href="http://sites.google.com/site/weskerjax/code-demo/SearchService.zip">SearchService.zip</a><br />
<br />
<br />
參考資料:<br />
<a target="_blank" href="http://blog.touv.fr/2006/08/serveur-soap-en-php-5-pour-office.html">Serveur SOAP en PHP 5 pour "Microsoft Office Research Service" </a><br />
<a target="_blank" href="http://msdn.microsoft.com/en-us/library/bb226691%28v=office.11%29.aspx">The Definitive Hello World Custom Research Service Tutorial</a><br />
<a target="_blank" href="http://msdn.microsoft.com/en-us/library/aa193743%28v=office.11%29.aspx">Microsoft.Search.Response.Content Schema Documentation</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-63520118930951930342011-12-21T14:32:00.000+08:002011-12-21T14:32:32.083+08:00[PHP] 兩種 while 的應用滿足其中一個條件,就再來一次<br />
<pre class="php" name="code"><?php
$a = 'OK';
while(true){
if($a == "OK"){
var_dump('OK');
$a = "NO";
continue;
}elseif($a == "NO"){
var_dump('NO');
$a = "XXX";
continue;
}else{
var_dump('default');
}
break;
}
</pre><br />
<br />
滿足其中一個條件,就跳過之後的判斷,同樣的程式流,也可以在 function 中用 return 做到。<br />
<pre class="php" name="code"><?php
$a = 'OK';
do{
if($a == "OK"){
echo 'OK'; break;
}
if($a == "NO"){
echo 'NO'; break;
}
if($a == "Off"){
echo 'NO'; break;
}
}while(false);
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com3tag:blogger.com,1999:blog-5946530704742130970.post-54697940549134641752011-12-16T06:49:00.002+08:002013-06-10T22:47:27.154+08:00[PHP] 縮圖方式的比較<ul><li><a target="_blank" href="http://php.net/manual/en/function.imagecopyresized.php">imageCopyResized</a> 速度快,但縮圖的品質很粗糙</li>
<li><a target="_blank" href="http://php.net/manual/en/function.imagecopyresampled.php">imageCopyResampled</a> 縮圖的品質好,但花費的時間有點多</li>
</ul>這兩種的缺點是,都會根據圖片的像素大小,而佔用PHP的記憶體,會造成 <strong>Fatal error: Out of memory</strong> 的錯誤出現,有一種狀況是一個 3MB 大小的 JPEG 實際的像素大小卻是 128MB,再來 GD 支援的圖片類型有限,大約就是四五種常用類型。<br />
<br />
優點是指需要安裝 GD 套件,這個套件不管是在 Windows 或 Linux 上很容易找到跟安裝,在處理圖片的類型明確跟尺寸不大的情況下,使用這兩個函數是不錯的。<br />
<br />
為了改善 <strong>imageCopyResampled</strong> 效率,可以利用 <strong>imageCopyResized</strong> 做預先縮圖,例如要縮圖的大小為 100*100 時,可以先將圖片縮小成四倍或八倍,如 400*400 或 800*800,可改善超大圖造成的效率不好。<br />
<br />
<ul><li><a target="_blank" href="http://www.imagemagick.org/script/index.php">ImageMagick</a> + <a target="_blank" href="http://php.net/manual/en/book.imagick.php">Imagick</a> 使用外部的程式來處理圖片,使用指令的方式或透過 Imagick 套件來處理縮圖。</li>
</ul>缺點是透過指令的方式很不友善,且容易受到系統權限的限制,而 Imagick 套件在 Windows 上不容易找到合適的 DLL。<br />
<br />
優點是支援下面多種格式,效率快且沒有記憶體錯誤的問題。<br />
<blockquote>3FR, A, AI, ART, ARW, AVI, AVS, B, BGR, BGRA, BMP, BMP2, BMP3, BRF, BRG, C, CAL, CALS, CAPTION, CIN, CIP, CLIP, CMYK, CMYKA, CR2, CRW, CUR, CUT, DCM, DCR, DCX, DDS, DFONT, DJVU, DNG, DOT, DPX, EPDF, EPI, EPS, EPS2, EPS3, EPSF, EPSI, EPT, EPT2, EPT3, ERF, EXR, FAX, FITS, FRACTAL, FTS, G, G3, GBR, GIF, GIF87, GRADIENT, GRAY, GRB, GROUP4, HALD, HISTOGRAM, HRZ, HTM, HTML, ICB, ICO, ICON, INFO, INLINE, IPL, ISOBRL, J2C, JNG, JP2, JPC, JPEG, JPG, JPX, K, K25, KDC, LABEL, M, M2V, M4V, MAP, MAT, MATTE, MIFF, MNG, MONO, MOV, MP4, MPC, MPEG, MPG, MRW, MSL, MSVG, MTV, MVG, NEF, NULL, O, ORF, OTB, OTF, PAL, PALM, PAM, PATTERN, PBM, PCD, PCDS, PCL, PCT, PCX, PDB, PDF, PDFA, PEF, PES, PFA, PFB, PFM, PGM, PGX, PICON, PICT, PIX, PJPEG, PLASMA, PNG, PNG24, PNG32, PNG8, PNM, PPM, PREVIEW, PS, PS2, PS3, PSB, PSD, PTIF, PWP, R, RADIAL-GRADIENT, RAF, RAS, RBG, RGB, RGBA, RGBO, RLA, RLE, SCR, SCT, SFW, SGI, SHTML, SR2, SRF, STEGANO, SUN, SVG, SVGZ, TEXT, TGA, THUMBNAIL, TIFF, TIFF64, TILE, TIM, TTC, TTF, TXT, UBRL, UIL, UYVY, VDA, VICAR, VID, VIFF, VST, WBMP, WMF, WMV, WMZ, WPG, X, X3F, XBM, XC, XCF, XPM, XPS, XV, XWD, Y, YCbCr, YCbCrA, YUV</blockquote>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-51173276919070396272011-12-15T18:51:00.001+08:002015-03-13T14:40:27.903+08:00[PHP] Documentor (phpdoc) 備忘<strong>Linux 安裝指令</strong><br />
sudo pear install -o PhpDocumentor<br />
<br />
<br />
<strong>Windows 安裝指令</strong><br />
C:\wamp\bin\php\php5.2.4\pear.bat install -o PhpDocumentor <br />
<br />
<br />
<strong>使用方式</strong><br />
<pre class="sh" name="code">phpdoc --parseprivate \
--output HTML:frames:earthli \
--ignore Smarty/ \
--directory /var/www/lib,/var/www/data_mod,/var/www/etc \
--target /var/www/docs
</pre><br />
<br />
<strong>phpdoc 參數說明</strong><br />
<table><tr valign="top"><td>-f</td><td>--filename</td><td>要解析的檔案名稱,可使用 ‘,’分隔多個檔案 “file1,file2”,可以包含完整路徑和使用 * ? 萬用符號。</td></tr>
<tr valign="top"><td>-d</td><td>--directory</td><td>要解析的目錄路徑,可使用 ‘,’分隔多個目錄路徑 “directory1,directory2”。</td></tr>
<tr valign="top"><td>-t</td><td>--target</td><td>指定要輸出的目錄。</td></tr>
<tr valign="top"><td>-i</td><td>--ignore</td><td>要忽略的檔案名稱,可使用 ‘,’分隔多個檔案 “file1,file2”,可以使用 * ? 萬用符號。</td></tr>
<tr valign="top"><td>-is</td><td>--ignoresymlinks</td><td>忽略系統連結的檔案或目錄,預設是關閉的。</td></tr>
<tr valign="top"><td>-it</td><td>--ignore-tags</td><td>忽略解析的標籤。 @package, @subpackage, @access, @ignore 可能是無法忽視。</td></tr>
<tr valign="top"><td>-q</td><td>--quiet</td><td>不顯示解析/轉換的訊息,在 cron 排程時可以選擇開啟,預設是關閉的。</td></tr>
<tr valign="top"><td>-ti</td><td>--title</td><td>產生出的文件的標題,預設為 ‘Generated Documentation’。</td></tr>
<tr valign="top"><td>-h</td><td>--help</td><td>顯示幫助訊息。</td></tr>
<tr valign="top"><td>-pp</td><td>--parseprivate</td><td>將私有(private)成員函式或私有變數也都加入程式文件裡。不然產生出的文件裡只會有公開(public)和保護(protected)的成員函式和變數。</td></tr>
<tr valign="top"><td>-o</td><td>--output</td><td>設置輸出文件的格式</td></tr>
<tr valign="top"><td colspan="3"><ul><li>HTML:frames:* - 包含iframe的HTML格式</li>
<li>HTML:frames:default – Javadoc風格的文檔模板,很少有格式</li>
<li>HTML:frames:earthli – 漂亮的模板(作者:Marco von Ballmoos)</li>
<li>HTML:frames:l0l33t – 流行模板</li>
<li>HTML:frames:phpdoc.de – 類似於phpdoc.de的PHPDoc輸出</li>
<li>HTML:frames:phphtmllib – 非常棒的用戶貢獻模板</li>
<li>HTML:frames:phpedit – 基於PHPEdit Help Generator的文檔</li>
<li>HTML:Smarty:* - 不使用iframe的HTML格式</li>
<li>HTML:Smarty:default – 使用css控制的黑體模板</li>
<li>HTML:Smarty:HandS – 基於PHP的格式,但是經過優化,帶有logo圖片</li>
<li>HTML:Smarty:PHP – 風格接近PHP官網</li>
<li>CHM:default:* - 輸出CHM幫助文檔</li>
<li>CHM:default:default – windows幫助文檔,基於HTML:frames:l0l33t</li>
<li>PDF:default:* - PDF格式</li>
<li>PDF:default:default – 標準純文本PDF格式</li>
<li>XML:DocBook:* - 以DocBook格式輸出的XML</li>
<li>XML:DocBook/peardoc2:default – 可以被編譯成peardoc的文檔</li>
</ul></td></tr>
<tr valign="top"><td>-j</td><td>--javadocdesc</td><td>相容 JavaDoc 的格式,預設是關閉的</td></tr>
</table><br />
<br />
<strong>中文亂碼的問題</strong><br />
新版的 phpdoc 輸出的格式已經是 UTF-8 了,所以只要在目錄下增加一個 .htaccess 文件,然後內容為:<br />
<code>AddCharset UTF-8 .html</code><br />
<br />
<br />
參考文件:<br />
<a target="_blank" href="http://pkwbim-programming-note.blogspot.com/2008/01/phpdocumentor-0.html">多采多姿的程式筆記: phpDocumentor筆記 - 0 立即體驗</a><br />
<a target="_blank" href="http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_phpDocumentor.howto.pkg.html">phpDocumentor Tutorial</a><br />
<a target="_blank" href="http://luchuan.iteye.com/blog/954147">phpDocumentor学习记录</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-59298353335312559392011-12-14T16:22:00.000+08:002013-06-10T22:50:12.930+08:00[PHP] 取得用戶真實 IP轉載自:<a target="_blank" href="http://www.jaceju.net/blog/archives/1913/">精進你的程式碼 - 從取得用戶端 IP 的函式談起</a><br />
<br />
<pre class="php" name="code"><?php
function get_client_ip(){
foreach (array(
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR'
) as $key) {
if(!array_key_exists($key, $_SERVER)){ continue; }
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip);
if ((bool) filter_var($ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4 |
FILTER_FLAG_NO_PRIV_RANGE |
FILTER_FLAG_NO_RES_RANGE
)){ return $ip; }
}
}
return null;
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0