月度归档:2010年12月

思考PHP语言二:面向对象

思考PHP语言二:面向对象
【概述】
PHP是一门支持面向过程,也支持面向对象的动态语言。从PHP5开始,PHP对于面向对象的支持好了很多。
面向对象程序设计是抽象数据类型的抽象原理的一种应用。当我们使用类时,我们应该了解这是一种抽象数据类型,对象是属于所对应类的类型的。
面向对象的三个特征:封装,继承,多态。这三个特征也可以用:抽象数据类型,继承和方法调用与方法间的动态绑定来说明。抽象是我们与程序复杂性抗争的一种方式,通过这种方式,我们可以关注并且仅关注主要的属性而忽略次要的属性。从语法的角度来说,抽象数据类型是一种封装,它不仅仅包括对于数据的封装,也包括其提供方法的封装。通过访问控制,从而实现数据或方法的隐藏和暴露,这就是我们经常所说的类,它的实例我们称之为对象。

【PHP的抽象数据类型】
PHP以类的形式实现封装,类中所定义的数据称为成员变量;而类中所定义的函数称为成员函数或成员方法。成员函数和成员变量又分别有类属性和实例属性。
类属性的成员函数和成员变量
PHP中的类属性成员都以静态的方式存在,为所有的实例共享(包括成员函数和成员变量),可以通过类名直接调用。但是其仅在一个PHP的生命周期内有效,这与java等有较大的差别。
实例属性的成员函数和成员变量
对于实例属性的成员函数和成员变量,一个类的所有实例共享一套成员函数,而每个实例都有属于自己的一套成员变量,这也是我们在做面向对象的程序结构设计时将数据尽量向下移,将共用的方法尽量向上移的原因。
信息隐藏
PHP中的信息隐藏通过访问控制实现,对于需要隐藏的数据将其以私有成员定义,在其声明前添加private关键字,对于需要公开的数据将其以公有成员定义,在其定义前添加public关键字,也可以不加任何关键字,因为PHP默认情况下是public。PHP的私有和公有访问权限与java相同,

命名封装
在构建大型系统时,为解决不同团队对于函数或类等的命名冲突,我们需要引入命名封装,命名封装是一些逻辑上的封装。在PHP中使用命名空间实现,只是这在PHP5.3版本后才有相关支持。
在PHP中,类,函数和常量这三种类型受命名空间的影响,命名空间通过关键字namespace 来声明,对于层次化的命名空间以反斜杠隔开,如:namespace com\toll\level;

【PHP的继承】
继承是软件复用的一种形式,它不仅可以让程序人员以一个已有类为基础,设计一个修改了的后代类型,还可以定义相关的程序结构和关系。这种结构和关系在一定程度上反映了这些实体的上下代关系。从这里就产生了父类,子类等概念,并且在访问控制中也产生了protected,使用protected时,它的最大访问范围为其派生类。在子类中我们可以定义新的成员变量和新的成员函数,并且可以重载父类的成员函数,但是无法重载父类的构造函数,如果在实例化时需要调用父类的构造函数,我们需要手动调用父类的构造函数,PHP本身不会自动调用。
另外,关于PHP的构造函数的一个历史遗留问题:以类名为构造函数和以__construct为构造函数,在之前写过一篇文章介绍二者在调用时的选择方式。PHP源码阅读笔记二十七:PHP对构造方法的识别
PHP没有多继承,只支持单继承,但是他拥有接口,它提供了一种多继承的方式。这点和java一样。
接口的定义与类的定义类似,只是关键字变成了interface,接口可以包含常量和方法声明。它的作用仅仅是定义类的说明。类可以实现多个接口,但是类也必须实现接口中的所有方法。接口可以模拟实现多继承,也提供了另一种形式的多态,但是这对于PHP来说没有多大的意义,因为PHP的变量本身就是多态的。

继承是作为增加复用可能性的一种手段,但是它在继承层次中产生的类之间的相互依赖性,产生了一些强耦合。而这与之前的封装的优点刚好相反,封装就是为了保持类之间的相互独立性。所以在作设计时,我们如果使用继承就需要考虑是否真的需要,是否真的是is-a的关系。更多的时候我们会选择使用委托来实现。
【PHP的动态绑定】
PHP本身就是动态绑定的。

思考PHP语言一:控制结构

思考PHP语言一:控制结构
【概述】
命令式程序设计语言的实质是赋值语句占主导地位。 赋值语句的目的是改变变量的值,因此在所有的命令式程序设计语言中,其共同部分就是不停的变幻变量的值,最后达到我们的目的。
然而我们的程序并不是仅仅是由赋值语句组成的。至少还需要两种额外的语言机制:
1、控制路径选择的方法。
2、控制某些语句重复执行的方法。
这些方法或语言机制我们称之为控制语句。
大量的控制语句可以提高程序语言的可写性,但是同时又会降低程序语言的可读性,为此,经常需要在这二者之间进行权衡。
【PHP中的选择语句】
1、双向选择语句
示例:

1
2
3
4
5
if (expr) {
	statement
}else{
	statement
}

PHP选择语句中的逻辑表达式可以是布尔表达式,也可以是结果为数字的表达式,但是在PHP内部只有一种变量zval,于是这些表达式结果类型对于PHP来说没有多大的意义

在PHP中,then子句和else子句可以是简单语句也可以是复合语句,如果是复合语句需要使用大括号将所有的复合语句括起来。

关于else的归属问题,以就近原则为准,如果需要在一个then子句中包含一个if语句,建议将其以复合语句处理,使用大括号将if语句括起来。如下所示代码:

1
2
3
4
5
6
7
if (expr) {
	if (expr2) {
		statement
	}
}else{
	statement
}

2、多向选择语句
多向选择语句允许在任意数目的语句或语句组中进行一种选择。多向选择器与双向选择器可以互相构造,都可以实现对方的功能。
示例:

1
2
3
4
5
6
7
8
9
10
11
12
switch(expr) {
case 常量表达式1:
	statement 1;
	break;
......
 
case 常量表达式n:
	statement n;
	break;
[default: statement n + 1;]
 
}

PHP的switch语句使用了C的switch语句的语法,但是却有一些差别,PHP的case 表达式可以是数字,字符串或浮点数这些简单类型。
与双向选择语句一样,多向选择语句在选择的路径上可以是简单语句,也可以是复合语句,只是这里我们不需要使用大括号,此时我们可以叫它语句序列。
当所有的情况都不满足时,执行default路径,如果没有所谓的default情况,请将default子句也写上,并且报错吧。
但是,当出现了switch选择语句时,此时可以想想我们的代码是否可以重构了。
当某个case下没有 break语句时,请写下注释说明原因。

【PHP中的循环语句】
循环语句的作用是将一条或一组语句执行n(n>=0)的一种语句
在函数式程序设计语言中一般是通过递归完成循环语句的功能
1、计数器循环控制语句
这种形式的循环控制语句一般包括初始值,终值,步长,这些统称为循环参数
PHP的计数器循环控制语句是类似于C语言的。
示例如下:

1
2
3
for (表达式1; 表达式2;表达式3) {
	循环体
}

这就是我们所说的for语句,只是在PHP中,for语句的使用较少,用得最多的还是在后面会提到的基于数据结构的循环—foreach
for语句中的三个表达式都是可选的,如果缺少第二个表达式,则认为其结果为真,如果在循环体中没有退出操作的话,这将是一个死循环。并且每个表达式都是可以由多条语句组成,语句与语句之间用逗号隔开,此时表达式的值为最后一条语句的值。
虽然for语句属于计数器循环控制语句,但是它并没有明显的循环变量或循环参数,可以在循环体中改变所有的所有变量,这就导致了for语句的灵活性,同时,如果用得不好也将导致程序逻辑的混乱
2、逻辑控制循环语句
逻辑控制循环语句是基于布尔表达式的,所有的计数循环都可以使用逻辑循环来实现,反过来则不一定。
示例:

1
2
3
4
5
6
7
while (控制表达式) {
    循环体
}
 
do {
   循环体
}while(控制表达式);

逻辑控制语句分为先测试和后测试两种情况,这个在上面的示例中有所体现。与for语句一样,PHP的逻辑循环控制语句也是类似于C语言的实现。
对于while语句,当控制表达式的条件为真时执行循环体,对于do-while语句,先执行循环体,再判断控制表达式是否为真。while语句总会比do-while语句少执行一次。
3、基于数据的循环语句
顾名思义,基于数据的循环语句的循环是由数据结构中的元素的数目来控制的。一般来说,基于数据的循环语句会使用一种称之为迭代器的函数来实现元素的遍历。
PHP中可以使用预定义的一些函数来实现对数组的迭代访问,如current,next,reset等等。
然而我们使用得最多的还是foreach语句,foreach可以直接迭代访问数组,如果用户自己定义的对象需要使用此语句进行迭代访问,必须实现SPL的迭代器。
之前写过两篇关于迭代器的文章:
PHP中迭代器的简单实现及Yii框架中的迭代器实现
PHP源码阅读笔记二十四 :iterator实现中当值为false时无法完成迭代的原因分析

PHP源码阅读笔记三十八:base64_encode实现

PHP源码阅读笔记三十八:base64_encode实现
【什么是base64编码】
Base64是一种使用64基的位置计数法。它使用2的最大次方来代表仅可打印的ASCII 字符。这使它可用来作为电子邮件的传输编码。在Base64中的变量使用字符A-Z、a-z和0-9 ,这样共有62个字符,用来作为开始的64个数字,最后两个用来作为数字的符号在不同的系统中而不同。一些如uuencode的其他编码方法,和之后binhex的版本使用不同的64字符集来代表6个二进制数字,但是它们不叫Base64。

【base64编码产生的历史原因】
在Email的传送过程中,由于历史原因,Email只被允许传送ASCII字符,即一个8位字节的低7位。因此,如果您发送了一封带有非ASCII字符(即字节的最高位是1)的Email通过有”历史问题“的网关时就可能会出现问题。网关可能会把最高位置为0!由于以上原因,产生了Base64编码。

【base64_encode的PHP内部实现】
base64_encode是PHP的标准函数,它存在于标准扩展中,在ext/standard/base64.c 210行,以标准的PHP_FUNCTION(base64_encode)实现。如下所示代码:

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/* {{{ proto string base64_encode(string str)
   Encodes string using MIME base64 algorithm */
PHP_FUNCTION(base64_encode)
{
	char *str;
	unsigned char *result;
	int str_len, ret_length;
 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &str_len) == FAILURE) {
		return;
	}
	result = php_base64_encode((unsigned char*)str, str_len, &ret_length);
	if (result != NULL) {
		RETVAL_STRINGL((char*)result, ret_length, 0);
	} else {
		RETURN_FALSE;
	}
}
/* }}} */

第218行 函数的参数输入,base64_encode仅有一个参数,字符串数据类型;
第221行 调用php_base64_encode函数实现base64编码;
第222~226行 返回编码后的值,如果编码成功,返回编码后的字符串,如果失败返回FALSE

[PHP_FUNCTION(base64_encode) -> php_base64_encode()]

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
PHPAPI unsigned char *php_base64_encode(const unsigned char *str, int length, int *ret_length) /* {{{ */
{
	const unsigned char *current = str;
	unsigned char *p;
	unsigned char *result;
 
	if ((length + 2) < 0 || ((length + 2) / 3) >= (1 << (sizeof(int) * 8 - 2))) {
		if (ret_length != NULL) {
			*ret_length = 0;
		}
		return NULL;
	}
 
	result = (unsigned char *)safe_emalloc(((length + 2) / 3) * 4, sizeof(char), 1);
	p = result;
 
	while (length > 2) { /* keep going until we have less than 24 bits */
		*p++ = base64_table[current[0] >> 2];
		*p++ = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
		*p++ = base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)];
		*p++ = base64_table[current[2] & 0x3f];
 
		current += 3;
		length -= 3; /* we just handle 3 octets of data */
	}
 
	/* now deal with the tail end of things */
	if (length != 0) {
		*p++ = base64_table[current[0] >> 2];
		if (length > 1) {
			*p++ = base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];
			*p++ = base64_table[(current[1] & 0x0f) << 2];
			*p++ = base64_pad;
		} else {
			*p++ = base64_table[(current[0] & 0x03) << 4];
			*p++ = base64_pad;
			*p++ = base64_pad;
		}
	}
	if (ret_length != NULL) {
		*ret_length = (int)(p - result);
	}
	*p = '\0';
	return result;
}
/* }}} */

第62~67行 输入判断,程序的健壮性处理,如果长度小于-2,或者长度大于2的(机器的int型长度 * 8 – 2)次方
第69行 按需分配内存
第72~80行 处理所给字符串的能被3整除的部分,在这里需要说明的是Base64编码要求把3个8位字节(3*8=24)转化为4个6位的字节(4*6=24),之后在6位的前面补两个0,形成8位一个字节的形式。
第83~94行 处理以3个字节划分后剩余的数据,分成只有一个字节,和两个字节的情况,分别在其后面添加一个或两个base64_pad,在这里base64_pad定义为:static const char base64_pad = ‘=’;
最后是添加’\0′,返回结果

【base64的URL应用】
Base64编码可用于在HTTP环境下传递较长的标识信息。例如,在Java持久化系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码不仅比较简短,同时也具有不可读性,即所编码的数据不会被人用肉眼所直接看到。
然而,标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的「/」和「+」字符变为形如「%XX」的形式,而这些「%」号在存入数据库时还需要再进行转换,因为ANSI SQL中已将「%」号用作通配符。
为解决此问题,可采用一种用于URL的改进Base64编码,它不在末尾填充’=’号,并将标准Base64中的「+」和「/」分别改成了「*」和「-」,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。
另有一种用于正则表达式的改进Base64变种,它将「+」和「/」改成了「!」和「-」,因为「+」,「*」以及前面在IRCu中用到的「[」和「]」在正则表达式中都可能具有特殊含义。
此外还有一些变种,它们将「+/」改为「_-」或「._」(用作编程语言中的标识符名称)或「.-」(用于XML中的Nmtoken)甚至「_:」(用于XML中的Name)。
以上部分来源于维基百科http://zh.wikipedia.org/zh/Base64