最近在CI專案上遇到改寫CI_Model的問題,主要是要覆寫(overwrite)和多載(overload)相關的需求,後來找到解法是需要__call()與callback相關知識與技術,這塊對我來說比較陌生,不過之前常看到別人寫比較厲害的套件時都會看到,趁這個機會好好學一下。
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