2015年11月28日 星期六

[PHP] PHP Extension 寫 php class 類別

基礎請參考:
PHP 也有 Day #21 從 C 語言到 PHP Extension 錄影

實作一個在 PHP 是這樣的一個類別
Class MyClass {
    protected $name = "";
    public function __construct($name) {
        $this->name = $name;
    }
    public function getName() {
        return $this->name;
    }
    public function setName($name) {
        $this->name = $name;
    }
    public function helloWorld() {
        return "Hello World";
    }
}
在 extension.c 寫完會如下
#include "php_myclass.h"

#if COMPILE_DL_MYCLASS
    ZEND_GET_MODULE(myclass)
#endif
// 宣告一個 global zend_class_entry 變數
zend_class_entry *myclass_ce;

/*
    這裡由原本的 PHP_FE 改用 PHP_ME
    四個參數為
    className:class PHP 呼叫的名字
    methodName:method 呼叫的名字
    arg_info:先帶 NULL,可以設定帶進來的參數值,可參考 http://www.walu.cc/phpbook/preface.md
    flags:有以下參數 ZEND_ACC_PUBLIC, ZEND_ACC_PROTECTED, ZEND_ACC_PRIVATE, ZEND_ACC_STATIC, ZEND_ACC_FINAL, ZEND_ACC_ABSTRACT
           分別表示   public         , protected         , private         , static         , final         , abstract
    如果要寫 abstract method 是使用 PHP_ABSTRACT_ME,就可以不用實作內容

    下述就是定義 MyClass 這個類別
    有 php 的 construct public funciton __construct
    一般 method 有
    public funciton getName
    public funciton setName
    public funciton helloWord
 */
static const zend_function_entry myclass_functions[] = {
    PHP_ME(MyClass, __construct,        NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
    PHP_ME(MyClass, getName,            NULL, ZEND_ACC_PUBLIC)
    PHP_ME(MyClass, setName,            NULL, ZEND_ACC_PUBLIC)
    PHP_ME(MyClass, helloWorld,         NULL, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

/*
    原本這裡的第三個參數改成 NULL,它原本是帶上面這個 zend_function_entry 的變數
    第四個參數改成 PHP_MINIT(myclass)
    class 改成要在 module init 這個周期宣告 class
    Ref:http://www.walu.cc/phpbook/1.3.md
 */
zend_module_entry myclass_module_entry = {
    STANDARD_MODULE_HEADER,
    "myclass",                       // your extension name
    NULL,                            // where you define your functions
    PHP_MINIT(myclass),              // for module initialization
    NULL, // PHP_MSHUTDOWN(myclass), // for module shutdown process
    NULL, // PHP_RINIT(myclass)      // for request initialization
    NULL, // PHP_RSHUTDOWN(myclass)  // for reqeust shutdown process
    NULL, // PHP_MINFO(myclass),     // for providing module information
    "0.1",
    STANDARD_MODULE_PROPERTIES
};

/*
    PHP_MINIT(myclass) 實作內容
    註冊一個叫 MyClass 的類別
    myclass_functions 也在這裡被使用
    全域的 myclass_ce 內容在這裡實際取得
 */
PHP_MINIT_FUNCTION(myclass)
{
    zend_class_entry tmp_ce;
    INIT_CLASS_ENTRY(tmp_ce, "MyClass", myclass_functions);
    myclass_ce = zend_register_internal_class(&tmp_ce TSRMLS_CC);
    return SUCCESS;
}

/*
    實作 __construct
    大致跟之前寫 function 差不多

    不過這裡有用 zend_update_property* 的 function
    這一系列的都是用來設定 class property
    全域的 myclass_ce 也在這裡被使用到,寫進 myclass_ce 的意思

    這裡我用來設定
    Class MyClass {
        protected $name = "";
    }
    這個 name property

    getThis() 是取得這個 class handle

    Ref:http://www.walu.cc/phpbook/11.2.md
 */
PHP_METHOD(MyClass, __construct)
{
    char *name = NULL;
    int name_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|", &name, &name_len) == FAILURE) {
        RETURN_NULL();
    }
    zend_update_property_stringl(myclass_ce, getThis(), "name", sizeof("name") - 1, name, name_len TSRMLS_CC);
}

/*
    實作 getName
    zend_parse_parameters_none 判斷不能傳參數進來
    zend_read_property 取得 name 這個 property
    Z_STRVAL_P 從 zval 取得 string
    RETURN_STRING Return string
*/
PHP_METHOD(MyClass, getName)
{
    zval *name = NULL;
    if (zend_parse_parameters_none() == FAILURE) {
        RETURN_FALSE;
    }
    name = zend_read_property(myclass_ce, getThis(), "name", sizeof("name") - 1, 1 TSRMLS_CC);
    RETURN_STRING(Z_STRVAL_P(name), 1);
}

/*
    實作 setName
*/
PHP_METHOD(MyClass, setName)
{
    char *name = NULL;
    int name_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|", &name, &name_len) == FAILURE) {
        RETURN_NULL();
    }
    zend_update_property_stringl(myclass_ce, getThis(), "name", sizeof("name") - 1, name, name_len TSRMLS_CC);
}

/*
    實作 helloWorld
*/
PHP_METHOD(MyClass, helloWorld)
{
    if (zend_parse_parameters_none() == FAILURE) {
        RETURN_FALSE;
    }

    RETURN_STRING("Hello World", 1);
}




2015年11月20日 星期五

[PHP] PHP 也有 Day #21 - c9s 主講:從 C 語言到 PHP Extension 學習筆記

這是個很硬的題目:從 C 語言到 PHP Extension

萬事起頭難
我覺得因為有 c9s 整理重點、地雷和 skeleton 才會看起來比較簡單,但其實...這很不簡單!!
上次寫 c 大概在 7 年前了~
所以需要沉澱一下...
進入正題

一些觀念

關於變數 (http://php.net/manual/en/internals2.variables.intro.php)

"PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting."

所以有幾個重要的觀念要會
1. copy on write
2. reference counting
3. php 變數是沒有型態的,但實際在 c 語言中是使用 zval 這個 struct
typedef struct _zval_struct {
    zvalue_value value;        /* variable value */
    zend_uint refcount__gc;    /* reference counter */
    zend_uchar type;           /* value type */
    zend_uchar is_ref__gc;     /* reference flag */
} zval;
其中 zvalue_value 是下列 sturct
typedef union _zvalue_value {
    long lval;                 /* long value */
    double dval;               /* double value */
    struct {
        char *val;
        int len;               /* this will always be set for strings */
    } str;                     /* string (always has length) */
    HashTable *ht;             /* an array */
    zend_object_value obj;     /* stores an object store handle, and handlers */
} zvalue_value;

關於檔案結構 (http://php.net/manual/en/internals2.structure.files.php)

config.m4:configure 用的
// 這裡紅字部份就是在 ./configure --enable-foo 是相同的
PHP_ARG_ENABLE(foo, whether to enable foo extension support,
  [--enable-foo Enable foo extension support])

// php_foo.c 是實作的檔案
if test $PHP_FOO != "no"; then
    PHP_NEW_EXTENSION(foo, php_foo.c, $ext_shared)
fi

php_counter.h:自己的 extension header
php_counter.c:自己的 extension 主程式 (說明是寫 counter.c 改成跟 h 同名,檔案多時比較知道是什麼)
config.w32:windows 用的,先忽略

PHP  Life Cycle
https://fntlnz.github.io/php-extensions-development/#/3
http://www.phpxs.com/post/1039


第一個 Hello World

剛好最近在學習 Larvel 直接拿 homested 做環境 (什麼都不用安裝就能編譯)
(是 vagrant,可以參考 http://laravel.tw/docs/5.1/homestead 自行安裝)

1.  ssh 進 homested 後

2. 下載 c9s 整理好的 skeleton
git clone https://github.com/c9s/php-ext-skeleton
3. 改一下程式
vi php_foo.c
改這裡,改成
PHP_FUNCTION(foo_hello) {
    RETURN_STRING("Hello World", 1);
}
4. 編譯
phpize
./configure --enable-foo
make 
make install
編好的會在 php-ext-skeleton/modules 目錄下
install 完的 .so 檔案會在 php default extension 目錄下 (這環境在 /usr/lib/php5/20131226/)
加入 php.ini 加入 extension
例如
echo "extension=foo.so" >  /etc/php5/cli/conf.d/20-foo.ini 
(這裡只有 cli 才會 work,請依自已的環境調整)

5.執行看看
php -r "echo foo_hello();"
應該會顯示 Hello World


寫測試

(https://qa.php.net/write-test.php)
mkdir tests
vi tests/foo.phpt
寫入
--TEST--
Check foo_hello
--FILE--
<?php
echo foo_hello();
--EXPECT--
Hello World
--TEST--
測試名稱
--FILE--
寫 php code
--EXPECT--
比對 echo 的資料
make test
如果有錯誤,在 tests 目錄下會會產生該測試的結果檔 (log, diff, exp, out),可以由這些檔案去看原因

GDB debug
(https://github.com/rcousens/packer-php7-dev/blob/master/doc/02-debug-php-extension.md)

實戰寫一個自己的 extension

(http://php.net/manual/en/internals2.funcs.php)

實作一個 swap function
可以用 c9s 提的指令快速換內容
perl -i -pe 's/foo_hello/swap/g' config.m4 *.h *.c
perl -i -pe 's/foo/swap/g' config.m4 *.h *.c
perl -i -pe 's/Foo/Swap/g' config.m4 *.h *.c
perl -i -pe 's/FOO/SWAP/g' config.m4 *.h *.c
mv foo_hello.c swap.c
mv foo_hello.h swap.h
一般在 php 寫應該像這樣
function swap(&$a, &$b) {
    $tmp = $a;
    $a = $b;
    $b = $tmp;
}
C 應該是像這樣
void swap(int *a, int *b) {
    int tmp=*a;
    *a=*b;
    *b=tmp;
}
開始寫 swap.c 主要是改以下內容
PHP_FUNCTION(swap) {
    zval *za = NULL;
    zval *zb = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &za, &zb) == FAILURE) {
        RETURN_FALSE;
    }
    zval tmp = *za;
    *za = *zb;
    *zb = tmp;
    RETURN_TRUE;
}

1. zval 就是  php 的變數型態
因為要實作任何型態都能 swap 的 function 所以我使用 zval 宣告兩個指標變數 *za、*zb

2. PHP_FUNCTION(swap) 跟在程式開頭寫下面這段有關
static const zend_function_entry swap_functions[] = {
  PHP_FE(swap, NULL)
  PHP_FE(f1, NULL)
  PHP_FE(f2, NULL)
  PHP_FE_END
};
PHP_FE 是註冊在 php 時,call function name
PHP_FUNCTION(swap) 的 swap 就是指在實作 swap 這個 function
如果是實作 f1那就是 PHP_FUNCTION(f1)

3. zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &za, &zb)
這行就是取得 php call function 時的參數
這裡跟 scanf 很像就是用 za 和 zb 接下來
然後 "zz" 就是接幾個參數和該參數型態,這裡用 zval

4. 取得參數後就是 swap 的主邏輯了

完整 Source:https://github.com/ffbli666/php-extension-examples

延伸思考和學習(研究ing)

Reference:


2015年11月7日 星期六

[JavaScript] 使用 css selector 來撰寫爬蟲程式



過去習慣在寫爬蟲程式時都是使用正規表示法來剖析網頁結構取得資料。

最近在寫 Chrome Extension 時 Chrome Extension 可以使用 chrome.tabs.executeScript 在一個 tab 內的內容執行自己寫的 JavaScript,在這個機制可以利用 Css Selector 來做網頁的剖析取得想要的資料,極其方便。

所以一個簡單的想法是,取得網頁資料->執行 JavaScript ->取得想要的資料

就找到 Node.js 有個 Project jsdom 符合此需求

此程式便是使用 jsom 的去 ted 網站取得演講資訊
完整的 Source

var jsdom = require('jsdom');
jsdom.env ( "http://tedxtaipei.com/talks/2013-ping-cheng-yeh/" ,
            ["//code.jquery.com/jquery-1.11.3.min.js"] ,
            function(err, window) {
                var $ = window.jQuery;
                var speech = {
                    title : $('.textover .title').text(),
                    description : $('.content-main .section p').text(),
                    video : $('.player .placeholder').attr('data-embed-code').match(/src="(.*?)"/i)[1],
                    speaker : {
                        name : $('.speaker .name text').text(),
                        anotherName : $('.speaker .another-name text').text(),
                        jobTitle : $('.speaker .job-title').text(),
                        image : $('.speaker .cover .image').attr('style').match(/url\((.*)\)/i)[1],
                        description : $('.speaker .description .content p').text()
                    }
               };
               console.log(speech);
}); 


題外話
php 可以用 DOMDocument

Reference:
jsdom

2015年11月5日 星期四

[心得] 寫程式,利用心智圖來提升第一次交付的完成度

心智圖個人覺得是個對寫程式很好的工具,而也不會對自己增加多少工作上的負擔。
帶來的好處有
  1. 可以整理邏輯,確定要處理的面
  2. 把問題切割成較小的區塊(可以 maping 到要寫的 function)
  3. coding 時因為要專注容易進入一個點的狀態,再回來看心智圖可以看到一個面的狀況
  4. 提高第一次交付功能的完成度
運作方式
  1.  開始動手前,花十分鐘把要做的功能(不要花太多時間),畫一下,想到什麼就先寫上去,這個完成度不用高,只要有 20~30 分即可
  2.  開始動手 coding,把邊寫邊遇到的問題或想到的問題,都先寫進去,小整理一下
  3. coding 完,回頭看一下心智圖整理一下,確定那些沒處理,繼續 coding (這時心智圖大概有 60分)
  4. 重覆 3 ,心智圖會越來越接近 90 分,表示考慮的越詳盡
  5. 丟掉心智圖!!  好吧,其實心智圖幾分不重要,在這個過程你知道自己在做什麼才是最重要的! (不想丟,拿來建立說明文件其實也不錯用)
實務操作
[TODO]