PHP源码阅读笔记二十七:PHP对构造方法的识别

PHP源码阅读笔记二十七:PHP对构造方法的识别
众所周知,由于历史原因,PHP之前是使用类名作为构造函数,在PHP5中引入的新的构造函数__construct。为了实现向后兼容性,如果 PHP 5 在类中找不到 __construct() 函数,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。因此唯一会产生兼容性问题的情况是:类中已有一个名为 __construct() 的方法,但它却又不是构造函数。
有如下一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Foo {
 
    public function Foo() {
 
    }
 
    private function __construct() {
 
    }
}
 
new Foo();
die();

此时,输出为:
Fatal error: Call to private Foo::__construct() from invalid context
此时,PHP识别出来的构造函数是__construct,因为是private,于是在外部调用出错。

好吧,我们从PHP的C源码中查找一下原因吧。
从spl的扩展类中直接查找类的定义开始:

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
spl_iterators.c 3228行 REGISTER_SPL_STD_CLASS_EX(IteratorIterator, spl_dual_it_new, spl_funcs_IteratorIterator);
///spl_functions.h 31行
#define REGISTER_SPL_STD_CLASS_EX(class_name, obj_ctor, funcs) \
	spl_register_std_class(&spl_ce_ ## class_name, # class_name, obj_ctor, funcs TSRMLS_CC);
//spl_functions.c 41行
PHPAPI void spl_register_std_class(zend_class_entry ** ppce, char * class_name, void * obj_ctor, const zend_function_entry * function_list TSRMLS_DC)
 
//spl_functions.c 2235行
ZEND_API zend_class_entry *zend_register_internal_class(zend_class_entry *orig_class_entry TSRMLS_DC) /* {{{ */
//调用do_register_internal_class函数
 
//zend_API.c 2169行
static zend_class_entry *do_register_internal_class(zend_class_entry *orig_class_entry, zend_uint ce_flags TSRMLS_DC) /* {{{ */
//调用
zend_register_functions(class_entry, class_entry->builtin_functions, &class_entry->function_table, MODULE_PERSISTENT TSRMLS_CC);
 
//zend_API.c 1795行
/* Look for ctor, dtor, clone
* If it's an old-style constructor, store it only if we don't have
* a constructor already.
*/
if ((fname_len == class_name_len) && !memcmp(lowercase_name, lc_class_name, class_name_len+1) && !ctor) {
	ctor = reg_function;
} else if ((fname_len == sizeof(ZEND_CONSTRUCTOR_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_CONSTRUCTOR_FUNC_NAME, sizeof(ZEND_CONSTRUCTOR_FUNC_NAME))) {
	ctor = reg_function;
} 
 
scope->constructor = ctor;	//	在1961行 确认构造函数

以上代码为php5.3.0版本
从以上跟踪流程来看,程序在注册所有函数时,如果存在__construct(即ZEND_CONSTRUCTOR_FUNC_NAME)时,会覆盖class_name(类名)的构造函数,使其作为常规的成员函数存在。如下所示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Foo {
 
    public function Foo() {
        echo 'Foo';
    }
 
    public function __construct() {
        echo '__construct';
    }
}
 
$foo = new Foo();
$foo->Foo();

对于在前面的示例中的报错,我们可以在
zend/zend_object_handlers.c 1057行
ZEND_API union _zend_function *zend_std_get_constructor(zval *object TSRMLS_DC)
找到出处。

–EOF-

PHP中的urlencode,rawurlencode和JS中的encodeURI,encodeURIComponent

PHP中的urlencode,rawurlencode和JS中的encodeURI,encodeURIComponent

【PHP中的urlencode和rawurlencode】
urlencode之前有看过其源码实现PHP 源码阅读笔记二十三 :urlencode函数
二都的区别仅在” “空格上,rawurlencode()会把空格编码为%20,而urlencode会把空格编码为+

【JS中的encodeURI和encodeURIComponent】
encodeURI 方法不会对下列字符进行编码:”:”、”/”、”;” 和 “?”,而encodeURIComponent会编码这些字符

【urlencode与encodeURI】
首先,我们看下这4种编码方式针对ASCII的127个字符编码后的差别,显示代码如下:

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
<?php
/**
 * 生成urlencode,rawurlencode,encodeURI,encodeURIComponent的编码结果 2010-10-29 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
header("Content-type:text/html;charset=utf-8");
 
echo <<<STYLE
<style type="text/css">
    table {
cursor:default;
font-family:Verdana,Helvetica,sans-serif;
font-size:8pt;
}
td {
background:none repeat scroll 0 0 #EFEFEF;
text-align:center;
width:100px;
}
</style>
STYLE;
echo '<table >';
echo _tr(_td("ASCII") . _td("urlenocde") . _td("rawurlencode") . _td("encodeURI") . _td("encodeURIComponent"));
for ($i = 0; $i < 128; $i++) {
    $ch = chr($i);
    $td = _td($ch) . _td(urlencode($ch)) . _td(rawurlencode($ch));
    $td .= _td(_encodeURI($ch)) . _td(_encodeURIComponent($ch));
 
    echo _tr($td);
}
echo "</table>";

对比urlencode和encodeURI的不同,可以看到#$&+,/:;=?@这些符号编码结果不同,
于是对于需要在PHP中编码后,给js的encodeURI使用的操作可以使用如下函数:

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
<?php
/**
 * urlencode适用于js版本 2010-10-29 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
header("Content-type:text/html;charset=utf-8");
 
function urlencode_js($str) {
    $str_len = strlen($str);
 
    $new = array();
    for ($i = 0; $i < $str_len; $i++) {
        $ch = $str[$i];
        if (strpos("#$&+,/:;=?@", $ch) !== FALSE) {
            $new[] = $ch;
        } else {
            $new[] = urlencode($ch);
        }
    }
 
    return implode("", $new);
}
 
$encode_str = urlencode_js("a汉bc中文 章+aa#$&+,/:;=?@a汉bc中文 章+aa");
 
echo <<<HTML
<script type="text/javascript">
    document.write(decodeURI("$encode_str") + "<br />");
 </script>
HTML;
die();

【urlencode和urldecode的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
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
 
<?php
/**
 * urlencode和urldecode的PHP版本 2010-10-29 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
header("Content-type:text/html;charset=utf-8");
 
$str = "a汉bc中文 章+aa:/;?()'!-.*_~";
 
/**
 * urlencode的PHP实现
 * 纯属折腾 其C实现请参照PHP源码  url.c文件中php_url_encode函数
 * @param <type> $str
 * @return <type>
 */
function myurlencode($str) {
    $len = strlen($str);
 
    $rs = array();
    for ($i = 0; $i < $len; $i++) {
        $ch = $str[$i];
        if ($ch == ' ') {
            $rs[] = '+';
        } else if (!encodecheck($ch)) {
            $rs[] = strtoupper('%' . dechex(ord($ch) >> 4) . dechex(ord($ch) & 15));
        } else {
            $rs[] = $ch;
        }
    }
 
    return implode("", $rs);
}
 
/**
 * 判断是否为字符和字线以及_-.
 * 相当于c中的!isalnum(c) && strchr("_-.", c) == NULL(PHP源码)
 * @param <type> $ch
 * @return <type>
 */
function encodecheck($ch) {
    $pattern = "/[a-zA-Z0-9_\-\.]/";
    return preg_match($pattern, strval($ch));
}
 
/**
 * 判断是否为16进制数
 * @param <type> $ch
 * @return <type>
 */
function checkhex($ch) {
    $hexstr = "0123456789ABCDEF";
    return strpos($hexstr, strval($ch)) === FALSE ? FALSE : TRUE;
}
 
/**
 * urldecode的PHP实现
 * 纯属折腾
 * @param <type> $str
 * @return <type>
 */
function myurldecode($str) {
    $len = strlen($str);
 
    $rs = array();
    for ($i = 0; $i < $len; $i++) {
        $ch = $str[$i];
        if ($ch == '+') {
            $rs[] = ' ';
        } else if ($ch == '%' && isset($str[$i + 1]) && checkhex($str[$i + 1]) && isset($str[$i + 2]) && checkhex($str[$i + 2])) {
            $rs[] = chr(hexdec($str[$i + 1] . $str[$i + 2]));
            $i += 2;
        } else {
            $rs[] = $ch;
        }
    }
 
    return implode("", $rs);
}
 
/* 测试 */
echo $str, '<br />';
echo urldecode(myurlencode($str)), '<br />';
echo myurldecode(urlencode($str)), '<br />';
die();

以上算是对urlencode和urldecode实现的一次复习吧。

–EOF–

PHP手册拾遗四:变量函数

PHP手册拾遗:变量函数
1、isset()函数
若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。
对于一个数组的元素,如果该元素的值为NULL,使用isset()函数将返回FALSE,此时需要使用array_key_exists函数。如下所示代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
$array = array('t1' => NULL);
if (isset($array['t1'])) {                                                               
    echo 'Yes';
}else{
    echo 'No';
}
echo '<br />';
 
if (array_key_exists('t1', $array)) {
    echo 'Yes';
}else{
    echo 'No';
}

2、empty()函数
这是一个语言结构而非函数
empty() 只检测变量,检测任何非变量的东西都将导致解析错误。换句话说,后边的语句将不会起作用: empty(addslashes($name))。
如下示例:

1
2
3
$str = "phppan";
if (empty(1));  //Parse error: syntax error, unexpected T_LNUMBER
if (empty(addslashes($str)));   //  Fatal error: Can't use function return value in write context

3、floatval()函数
float floatval ( mixed var )
var 可以是任何标量类型。你不能将 floatval() 用于数组或对象。

4、import_request_variables函数
将 GET/POST/Cookie 变量导入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局变量,那么此函数就很有用。
很少用到此函数,一般都是直接使用$_GET/$_POST/$_COOKIE
如果要将其它变量导入到全局变量中,可以考虑使用extract()。

5、serialize()与unserialize()
serialize会存储数组/对象中的引用。因此可以通过序列化和反序列化实现深拷贝。
当序列化对象时,PHP 将试图在序列动作之前调用该对象的成员函数 __sleep()。这样就允许对象在被序列化之前做任何清除操作。类似的,当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数。

6、unset — 释放给定的变量
如果在函数中 unset() 一个全局变量,则只是局部变量被销毁,而在调用环境中的变量将保持调用 unset() 之前一样的值。
如下所示代码:

1
2
3
4
5
6
7
8
9
$a = 10;
function unset_global_var() {
    global $a;
    unset($a);
    echo $a, '<br />';
}
unset_global_var();
echo $a, '<br />';
die();

如果在函数中 unset() 一个通过引用传递的变量,则只是局部变量被销毁,而在调用环境中的变量将保持调用 unset() 之前一样的值。
如果在函数中 unset() 一个静态变量,则 unset() 将销毁此变量及其所有的引用。
新版本的手册中的示例很能说明上面的这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
    static $bar;
    $bar++;
    echo "Before unset: $bar, ";
    unset($bar);
    $bar = 23;
    echo "after unset: $bar<br />";
}
 
foo();
foo();
foo();

输出:
Before unset: 1, after unset: 23
Before unset: 2, after unset: 23
Before unset: 3, after unset: 23

如果在函数中 unset() 一个全局变量,可使用 $GLOBALS 数组来实现: