Magic method : __call($fun, $args)

呼叫物件未定義的方法時觸發,$fun是呼叫的方法名,$args是參數陣列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php

class Human {
    function __call($fun, $args) {
        print_r([$fun, $args]);
    }
}

$human = new Human();
$human->test(1, 2, 'a', 'b');

The above example will output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Array
(
    [0] => test
    [1] => Array
        (
            [0] => 1
            [1] => 2
            [2] => a
            [3] => b
        )

)

如果要實現像Java 多載(overloading)的方法(使用相同function名稱)
相對來說會比較複雜一些

Java的多載 :

1
2
static int demo(byte[] a, byte key);
static int demo(byte[] a, int fromIndex, int toIndex, byte key);

PHP的多載 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
class Demo
{
    function __call($fun, $args)
    {
        switch($fun) {
        case 'add':
            if (count($args)===2) {
                if (is_numeric($args[0]) && is_numeric($args[1])) {
                    return $args[0]+$args[1];
                }
                if (is_string($args[0]) && is_string($args[1])) {
                    return $args[0].$args[1];
                }
            }
        default:
            throw new Exception("[warning] b::$name method not found.\n");
        }
    }
}

$demo = new Demo();
echo $demo->add(1, 2);  // 3
echo "\n";
echo $demo->add('a', 'b');  // ab
echo "\n";
echo $demo->add('a', 2);  // get error : method not found
echo "\n";

callback : call_user_func(), call_user_func_array()

callback (回呼函數) :
指的是回傳某個函數的指標,呼叫者便可透過這個函數指標直接執行函數
底下直接展示範例(從官網整理修改然後加一些有的沒的)
參考1 : Callbacks / Callables, 參考2 : call_user_func_array

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
// An example callback function
function simple_callback_function()
{
    echo 'simple callback function' . "\n";
}
  
// An example callback method
class MyClass
{
    function myCallbackMethod()
    {
        echo 'class callback function' . "\n";
    }

    function myCallbackMethod2($arg, $arg2)
    {
        echo "Hello $arg $arg2" . "\n";
    }
}

class MyClass2
{
    static function demo()
    {
        echo 'Hello demo' . "\n";
    }
}

class C {
    public function __invoke($name) {
        echo 'Hello ' . $name . "\n";
    }
}

// 1. Simple callback 直接回呼,注意函式並非在類別中,直接使用函式名即可,省略函式本身的小括號
call_user_func('simple_callback_function'); 

// 2. Call function from class  回呼類別中的函式
call_user_func(array('MyClass', 'myCallbackMethod')); 

// 3. Object method call 以物件的方式回呼,要先宣告該物件
$myclass = new MyClass();
call_user_func(array($myclass, 'myCallbackMethod'));

// 4. Static class method call (As of PHP 5.2.3) 同樣使用靜態方式回呼,建議寫法
call_user_func('MyClass::myCallbackMethod');

// 5. 使用類別靜態方法, 範圍解析操作符(::)
MyClass2::demo();

// 6. invoke : 將物件當做函數來使用
$c = new C();
$c('123');  // 一般方式
call_user_func($c, 'PHP!'); // callable方式

// 7. Call the $myclass->myCallbackMethod2() method with 2 arguments => 4種方式
$myclass->myCallbackMethod2('aaa', 'bbb');
call_user_func_array([$myclass, 'myCallbackMethod2'], ['aaa', 'bbb']);
call_user_func_array(['MyClass', 'myCallbackMethod2'], ['aaa', 'bbb']);
call_user_func_array('MyClass::myCallbackMethod2', ['aaa', 'bbb']);

實際遇到的應用場景

最近在CodeIgniter上遇到CRUD的需求
研究了CI_Model相關資料後
決定寫MY_Model來作為自定義的Model class
之後比如我有個demo table就可以直接繼承MY_Model class

MY_Model在還沒有使用__call()之前
使用demo model物件裡的方法時,必須加上db(因為CI_Model)
比如要select demo全部資料 :

1
2
$this->load->model('demo_model');
$this->demo_model->db->get('demo');   // CI_Model get()方法

若要去除擾人的db,並且還可以使用原生CI_Model方法和覆寫方法
此時就需要動用__call()call_user_func_array()
實作參考如下

CodeIgniter : Active Record 類別

application/core/MY_Model.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

class MY_Model extends CI_Model
{
    public function __construct()
    {
        parent::__construct();
        $this->load->database();
    }
    
    public function __call($fun, $args)
    {
        // $this->db 是config/database.php上的db connection settings
        // 此時db是一個物件
        call_user_func_array([$this->db, $fun], $args);
        return $this;
    }

    // overwrite get()
    public function get(bool $isFields = false)
    {
        if ($isFields) {
            $header = (clone $this->db)->from($this->table)->get()->list_fields();
        }

        $result = $this->db->from($this->table)->get()->result_array();

        if ($isFields) {
            array_unshift($result, $header);
        }

        return $result;
    }
}

application/models/Demo_model.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php

class Demo_model extends MY_Model
{
    protected $table = 'demo';

    public function __construct()
    {
        parent::__construct();
    }
    
}

application/controllers/Demo.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

class Events extends CI_Controller
{
    ...

    public function index()
    {
        $this->load->model('demo_model');
        $this->demo_model->where('tag', 'aaa')->get();  
    }
}

Summary

因為之前有先學習Laravel的原因,在了解CI Model Class後
跟Laravel Model相比差蠻多的
Laravel寫法相對比較漂亮且直覺
我做了一個自訂class MY_Model for CodeIgniter
為了要做得像Laravel Model
過程中當然也遇到一些問題
不過還算順利
做出了一個蠻像Laravel的Model Class
上面範例中的get()便是其中之一
日後有機會再來分享我如何自訂MY_Model裡的東西吧

Reference