1.入门
关于换行
PHP 会在输出时自动删除其结束符 ?>后的一个换行。该功能主要是针对在一个页面中嵌入多段 PHP 代码或者包含了无实质性输出的 PHP 文件而设计,与此同时也造成了一些疑惑。如果需要在 PHP 结束符 ?> 之后输出换行的话,可以在其后加一个空格,或者在最后的一个 echo/print 语句中加入一个换行。
<?php phpinfo(); ?>
调用函数 phpinfo(),将会看到很多有关自己系统的有用信息,例如预定义变量、已经加载的 PHP 模块和配置信息。
echo $_SERVER[‘HTTP_USER_AGENT‘];
该脚本可能输出是:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36
打印来自表单的数据:从表单提交姓名和年龄。
你好,<?php echo htmlspecialchars($_POST[‘name‘]); ?>。
你 <?php echo (int)$_POST[‘age‘]; ?> 岁了。
htmlspecialchars()使得 HTML 之中的特殊字符被正确的编码,从而不会被使用者在页面注入 HTML 标签或者 Javascript 代码。超全局变量 $_REQUEST,它包含了所有 GET、POST、COOKIE 和 FILE 的数据。
有可能影响到老版本的代码的最重要的两点改动分别是:
- 旧的 $HTTP_*_VARS 数组从 PHP 5.4.0 开始将不再有效。 PHP » 4.1.0版本引入了如下超全局数组变量: $_GET、$_POST、$_COOKIE、 $_SERVER、$_FILES、$_ENV、 $_REQUEST 以及 $_SESSION。
- 外部变量不再被默认注册为全局变量。也就是说,从 PHP » 4.2.0版开始,php.ini 中的设置选项 register_globals默认值变成了 off。建议用以上提到的超全局数组变量来访问这些值。但可能老的脚本、书籍以及教程都可能建立在该设置为 on 的基础上。
<?php 和 ?>:这告诉 PHP 开始和停止解析二者之间的代码。此种解析方式使得 PHP 可以被嵌入到各种不同的文档中去。PHP 也允许使用短标记 <? 和 ?>,但不鼓励使用。只有通过激活 php.ini中的 short_open_tag配置指令或者在编译 PHP 时使用了配置选项 --enable-short-tags时才能使用短标记。
如果文件内容是纯 PHP 代码,最好在文件末尾删除 PHP 结束标记。这可以避免在 PHP 结束标记之后万一意外加入了空格或者换行符,会导致 PHP 开始输出这些空白,而脚本中此时并无输出的意图。
<% echo ‘You may optionally use ASP-style tags‘; %>
<%= $variable; # This is a shortcut for "<% echo . . ." %>
ASP 风格标记仅在通过 php.ini配置文件中的指令 asp_tags 打开后才可用。
自 PHP 5.4 起,短格式的 echo 标记 <?=总会被识别并且合法,而不管 short_open_tag 的设置是什么。
在一个 PHP 代码段中的最后一行可以不用分号结束。如果后面还有新行,则代码段的结束标记包含了行结束。
2.类型
PHP 支持 8 种原始数据类型。
四种标量类型:boolean(布尔型) integer(整型) float(浮点型,也称作 double) string(字符串)
两种复合类型:array(数组) object(对象)
最后是两种特殊类型:resource(资源) NULL(无类型)
伪类型:mixed(混合类型) number(数字类型) callback(回调类型)
以及伪变量 $...
。
如果想查看某个表达式的值和类型,用 var_dump() 函数。如果只是想得到一个易读懂的类型的表达方式用于调试,用 gettype() 函数。要查看某个类型,不要用 gettype(),而用 is_type 函数
如果要将一个变量强制转换为某类型,可以对其使用强制转换或者 settype() 函数。
要指定一个布尔值,使用关键字 TRUE
或 FALSE
。两个都不区分大小写。
当转换为 boolean 时,以下值被认为是 FALSE
:
布尔值 FALSE
本身,整型值 0(零),浮点型值 0.0(零),空字符串,以及字符串 "0"不,包括任何元素的数组,不包括任何成员变量的对象(仅 PHP 4.0 适用),特殊类型 NULL(包括尚未赋值的变量),从空标记生成的 SimpleXML 对象。
所有其它值都被认为是 TRUE
(包括任何资源)。
一个 integer 是集合 ? = {..., -2, -1, 0, 1, 2, ...} 中的一个数。
整型值要使用八进制表达,数字前必须加上 0(零)。要使用十六进制表达,数字前必须加上 0x。要使用二进制表达,数字前必须加上 0b。
如果向八进制数传递了一个非法数字(即 8 或 9),则后面其余数字会被忽略。
var_dump(01090); // 八进制 010 = 十进制 8
整数溢出:
如果给定的一个数超出了 integer 的范围,将会被解释为 float。同样如果执行的运算结果超出了 integer 范围,也会返回 float。
PHP 中没有整除的运算符。1/2 产生出 float0.5。值可以舍弃小数部分强制转换为 integer,或者使用round() 函数可以更好地进行四舍五入。还可以通过函数 intval() 来将一个值转换成整型。从布尔值转换,FALSE
将产生出 0(零),TRUE
将产生出 1(壹)。当从浮点数转换成整数时,将向下取整。决不要将未知的分数强制转换为 integer,这样有时会导致不可预料的结果。
echo (int) ( (0.1+0.7) * 10 ); // 显示 7!
floor((0.1+0.7)*10)通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似7.9999999999999991118...。
所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数。要测试浮点数是否相等,要使用一个仅比该数值大一丁点的最小误差值。
某些数学运算会产生一个由常量 NAN
所代表的结果。此结果代表着一个在浮点数运算中未定义或不可表述的值。任何拿此值与其它任何值进行的松散或严格比较的结果都是 FALSE
。由于 NAN
代表着任何不同值,不应拿 NAN
去和其它值进行比较,包括其自身,应该用 is_nan() 来检查。
string 最大可以达到 2GB。一个字符串可以用 4 种方式表达:
单引号,双引号,heredoc 语法结构,nowdoc 语法结构(自 PHP 5.3.0 起)
要表达一个单引号自身,需在它的前面加个反斜线(\)来转义。要表达一个反斜线自身,则用两个反斜线(\\)。其它任何方式的反斜线都会被当成反斜线本身:也就是说如果想使用其它转义序列例如 \r 或者 \n,并不代表任何特殊含义,就单纯是这两个字符本身。如果字符串是包围在双引号(")中, PHP 将对一些特殊的字符进行解析:
\n:换行;\r回车;\t:水平制表符;\v垂直制表符;\e:Escape;\f:换页;\\:反斜线;\$:美元标记;\":双引号;还有正则表达式。和单引号字符串一样,转义任何其它字符都会导致反斜线被显示出来。用双引号定义的字符串最重要的特征是变量会被解析。heredoc 句法结构:<<<。在该运算符之后要提供一个标识符,然后换行。接下来是字符串 string 本身,最后要用前面定义的标识符作为结束标志。
任何具有 string表达的标量变量,数组单元或对象属性都可使用此语法。只需简单地像在 string 以外的地方那样写出表达式,然后用花括号 { 和 } 把它括起来即可。由于 { 无法被转义,只有 $紧挨着 { 时才会被识别。可以用 {\$ 来表达 {$。
$great = ‘fantastic‘;
// 无效,输出: This is { fantastic}
echo "This is { $great}";
// 有效,输出: This is fantastic
echo "This is {$great}";
echo "This is ${great}";
// 有效
echo "This square is {$square->width}00 centimeters broad.";
// 有效,只有通过花括号语法才能正确解析带引号的键名
echo "This works: {$arr[‘key‘]}";
自 5.4 起可以使用短数组定义语法,用 [] 替代 array()。key 可以是 integer 或者 string。value 可以是任意类型。如果在数组定义中多个单元都使用了同一个键名,则只使用了最后一个,之前的都被覆盖了。PHP 数组可以同时含有 integer 和 string 类型的键名,因为 PHP 实际并不区分索引数组和关联数组。如果对给出的值没有指定键名,则取当前最大的整数索引值,而新的键名将是该值加一。如果指定的键名已经有了值,则该值会被覆盖。还可以只对某些单元指定键名而对其它的空置。数组单元可以通过 array[key] 语法来访问。
$array = [
"foo" => "bar",
"bar" => "foo",
];
此外 key 会有如下的强制转换:
- 包含有合法整型值的字符串会被转换为整型。例如键名 "8" 实际会被储存为 8。但是 "08"则不会强制转换,因为其不是一个合法的十进制数值。
- 浮点数也会被转换为整型,意味着其小数部分会被舍去。例如键名 8.7 实际会被储存为 8。
- 布尔值也会被转换成整型。即键名 true 实际会被储存为 1而键名 false 会被储存为 0。
- Null 会被转换为空字符串,即键名 null 实际会被储存为 ""。
- 数组和对象不能被用为键名。坚持这么做会导致警告:Illegal offset type。
<?php
$array = array(
"foo" => "bar",
42 => 24,
"multi" => array(
"dimensional" => array(
"array" => "foo"
)
)
);
var_dump($array["foo"]);
var_dump($array[42]);
var_dump($array["multi"]["dimensional"]["array"]);
?>
string(3) "bar" int(24) string(3) "foo"
自 PHP 5.4 起可以用数组间接引用函数或方法调用的结果。之前只能通过一个临时变量。自 PHP 5.5 起可以用数组间接引用一个数组原型。
<?php
function getArray() {
return array(1, 2, 3);
}
// on PHP 5.4
$secondElement = getArray()[1];
// previously
$tmp = getArray();
$secondElement = $tmp[1];
// or
list(, $secondElement) = getArray();
?>
仅对部分单元指定键名:
<?php
$array = array(
"a",
"b",
6 => "c",
"d",
);
var_dump($array);
?>
array(4) { [0]=> string(1) "a" [1]=> string(1) "b" [6]=> string(1) "c" [7]=> string(1) "d" }
要修改某个值,通过其键名给该单元赋一个新值。要删除某键值对,对其调用 unset() 函数。
<?php
$arr = array(5 => 1, 12 => 2);
$arr[] = 56; // This is the same as $arr[13] = 56;
$arr["x"] = 42; // This adds a new element to
unset($arr[5]); // This removes the element from the array
unset($arr); // This deletes the whole array
?>
unset() 函数允许删除数组中的某个键。但要注意数组将不会重建索引。如果需要删除后重建索引,可以用array_values() 函数。
对于任意 integer,float,string,boolean和 resource 类型,如果将一个值转换为数组,将得到一个仅有一个元素的数组,其下标为 0,该元素即为此标量的值。换句话说,(array)$scalarValue 与array($scalarValue) 完全一样。
如果一个 object 类型转换为 array,则结果为一个数组,其单元为该对象的属性。键名将为成员变量名,不过有几点例外:整数属性不可访问;私有变量前会加上类名作前缀;保护变量前会加上一个 ‘*‘ 做前缀。这些前缀的前后都各有一个 NULL 字符。这会导致一些不可预知的行为:
<?php
class A {
private $A; // This will become ‘\0A\0A‘
}
class B extends A {
private $A; // This will become ‘\0B\0A‘
public $AA; // This will become ‘AA‘
}
var_dump((array) new B());
?>
上例会有两个键名为 ‘AA‘,不过其中一个实际上是 ‘\0A\0A‘。
数组(Array) 的赋值总是会涉及到值的拷贝。使用引用运算符通过引用来拷贝数组。
<?php
$arr1 = array(2, 3);
$arr2 = $arr1;
$arr2[] = 4; // $arr2 is changed,
// $arr1 is still array(2, 3)
$arr3 = &$arr1;
$arr3[] = 4; // now $arr1 and $arr3 are the same
?>
要创建一个新的对象 object,使用 new 语句实例化一个类。如果将一个对象转换成对象,它将不会有任何变化。如果其它任何类型的值被转换成对象,将会创建一个内置类 stdClass 的实例。如果该值为 NULL
,则新的实例为空。数组转换成对象将使键名成为属性名并具有相对应的值。对于任何其它的值,名为 scalar 的成员变量将包含该值。
<?php
$obj = (object) ‘ciao‘;
echo $obj->scalar; // outputs ‘ciao‘
?>
资源 resource是一种特殊变量,保存了到外部资源的一个引用。资源是通过专门的函数来建立和使用的。
由于资源类型变量保存有为打开文件、数据库连接、图形画布区域等的特殊句柄,因此将其它类型的值转换为资源没有意义。
特殊的 NULL
值表示一个变量没有值。NULL 类型唯一可能的值就是 NULL
。
在下列情况下一个变量被认为是 NULL
:
被赋值为 NULL
。尚未被赋值。被 unset()。
NULL
类型只有一个值,就是不区分大小写的常量 NULL
。
使用 (unset) $var 将一个变量转换为 null将不会删除该变量或 unset 其值。仅是返回 NULL
值而已。
一些函数如 call_user_func() 或 usort() 可以接受用户自定义的回调函数作为参数。回调函数不止可以是简单函数,还可以是对象的方法,包括静态类方法。一个 PHP 的函数以 string类型传递其名称。可以使用任何内置或用户自定义函数,但除了语言结构例如:array(),echo,empty(),eval(),exit(),isset(),list(),print或 unset()。一个已实例化的对象的方法被作为数组传递,下标 0 包含该对象,下标 1 包含方法名。除了普通的用户自定义函数外,create_function()可以用来创建一个匿名回调函数。
<?php
// An example callback function
function my_callback_function() {
echo ‘hello world!‘;
}
// An example callback method
class MyClass {
static function myCallbackMethod() {
echo ‘Hello World!‘;
}
}
// Type 1: Simple callback
call_user_func(‘my_callback_function‘);
// Type 2: Static class method call
call_user_func(array(‘MyClass‘, ‘myCallbackMethod‘));
// Type 3: Object method call
$obj = new MyClass();
call_user_func(array($obj, ‘myCallbackMethod‘));
// Type 4: Static class method call (As of PHP 5.2.3)
call_user_func(‘MyClass::myCallbackMethod‘);
// Type 5: Relative static class method call (As of PHP 5.3.0)
class A {
public static function who() {
echo "A\n";
}
}
class B extends A {
public static function who() {
echo "B\n";
}
}
call_user_func(array(‘B‘, ‘parent::who‘)); // A
?>
在函数中注册有多个回调内容时(如使用call_user_func() 与 call_user_func_array()),如在前一个回调中有未捕获的异常,其后的将不再被调用。
mixed 说明一个参数可以接受多种不同的(但不一定是所有的)类型。例如 gettype() 可以接受所有的 PHP 类型,str_replace() 可以接受字符串和数组。number 说明一个参数可以是 integer 或者 float。本文档中在 PHP 5.4 引入 callable 类型之前使用 了 callback 伪类型。二者含义完全相同。void 作为返回类型意味着函数的返回值是无用的。void作为参数列表意味着函数不接受任何参数。在函数原型中,$...
表示等等的意思。当一个函数可以接受任意个参数时使用此变量名。
<?php
$foo = "0"; // $foo 是字符串 (ASCII 48)
$foo += 2; // $foo 现在是一个整数 (2)
$foo = $foo + 1.3; // $foo 现在是一个浮点数 (3.3)
$foo = 5 + "10 Little Piggies"; // $foo 是整数 (15)
$foo = 5 + "10 Small Pigs"; // $foo 是整数 (15)
?>
自动转换为 数组 的行为目前没有定义。
<?php
$a = ‘car‘; // $a is a string
$a[0] = ‘b‘; // $a is still a string
echo $a; // bar
?>
允许的强制转换有:注意在括号内允许有空格和制表符
- (int), (integer) - 转换为整形 integer
- (bool), (boolean) - 转换为布尔类型 boolean
- (float), (double), (real) - 转换为浮点型 float
- (string) - 转换为字符串 string
- (array) - 转换为数组 array
- (object) - 转换为对象 object
- (unset) - 转换为 NULL (PHP 5)
3.变量
$this 是一个特殊的变量,它不能被赋值。 使用引用赋值,简单地将一个 & 符号加到将要赋值的变量前(源变量)。
<?php
$foo = ‘Bob‘; // 将 ‘Bob‘ 赋给 $foo
$bar = &$foo; // 通过 $bar 引用 $foo
$bar = "My name is $bar"; // 修改 $bar 变量
echo $bar;
echo $foo; // $foo 的值也被修改
?>
有一点重要事项须指出,那就是只有有名字的变量才可以引用赋值。$bar = &(24 * 7); // 非法; 引用没有名字的表达式
未初始化的变量具有其类型的默认值 - 布尔类型的变量默认值是 FALSE
,整形和浮点型变量默认值是零,字符串型变量(例如用于 echo中)默认值是空字符串以及数组变量的默认值是空数组。isset() 语言结构可以用来检测一个变量是否已被初始化。
<?php
$a = 1;
$b = 2;
function Sum(){
global $a, $b;
$b = $a + $b;
}
Sum();
echo $b;
?>
等价于下面的(输出为3):
$GLOBALS[‘b‘] = $GLOBALS[‘a‘] + $GLOBALS[‘b‘];
$GLOBALS是一个关联数组,每一个变量为一个元素,键名对应变量名,值对应变量的内容。$GLOBALS之所以在全局范围内存在,是因为 $GLOBALS 是一个超全局变量。变量范围的另一个重要特性是静态变量(static variable)。静态变量仅在局部函数域中存在,但当程序执行离开此作用域时,其值并不丢失。如果在声明中用表达式的结果对其赋值会导致解析错误。static $int = 0; // correct
static $int = 1+2; // wrong (as it is an expression)
static $int = sqrt(121); // wrong (as it is an expression too)
可变变量:一个变量的变量名可以动态的设置和使用。一个普通的变量通过声明来设置。要将可变变量用于数组,必须解决一个模棱两可的问题。这就是当写下 $$a[1]时,解析器需要知道是想要 $a[1]作为一个变量呢,还是想要 $$a作为一个变量并取出该变量中索引为 [1] 的值。解决此问题的语法是,对第一种情况用${$a[1]},对第二种情况用 ${$a}[1]。
类的属性也可以通过可变属性名来访问。可变属性名将在该调用所处的范围内被解析。例如,对于 $foo->$bar 表达式,则会在本地范围来解析 $bar并且其值将被用于 $foo 的属性名。对于 $bar是数组单元时也是一样。
<?php
class foo {
var $bar = ‘I am bar.‘;
var $arr = array(‘I am A.‘, ‘I am B.‘, ‘I am C.‘);
var $r = ‘I am r.‘;
}
$foo = new foo();
$bar = ‘bar‘;
$baz = array(‘foo‘, ‘bar‘, ‘baz‘, ‘quux‘);
echo $foo->$bar . "\n";
echo $foo->$baz[1] . "\n";
$start = ‘b‘;
$end = ‘ar‘;
echo $foo->{$start . $end} . "\n";
$arr = ‘arr‘;
echo $foo->$arr[1] . "\n";
echo $foo->{$arr}[1] . "\n";
?>
I am bar.
I am bar.
I am bar.
I am r.
I am B.
注意,在 PHP 的函数和类的方法中,超全局变量不能用作可变变量。$this变量也是一个特殊变量,不能被动态引用。
变量名中的点和空格被转换成下划线。例如 <input name="a.b" /> 变成了 $_REQUEST["a_b"]。
4.常量
可以用 define() 函数来定义常量,在 PHP 5.3.0 以后,可以使用 const关键字在类定义之外定义常量。一个常量一旦被定义,就不能再改变或者取消定义。常量只能包含标量数据(boolean,integer,float和 string)。可以定义 resource 常量,但应尽量避免,因为会造成不可预料的结果。如果常量名是动态的,也可以用函数 constant() 来获取常量的值。用 get_defined_constants() 可以获得所有已定义的常量列表。如果只想检查是否定义了某常量,用 defined() 函数。
常量和变量有如下不同:
- 常量前面没有美元符号($);
- 常量只能用 define() 函数定义,而不能通过赋值语句;
- 常量可以不用理会变量的作用域而在任何地方定义和访问;
- 常量一旦定义就不能被重新定义或者取消定义;
- 常量的值只能是标量。
和使用 define() 来定义常量相反的是,使用 const关键字定义常量必须处于最顶端的作用区域,因为用此方法是在编译时定义的。这就意味着不能在函数内,循环内以及 if 语句之内用 const 来定义常量。
有八个魔术常量它们的值随着它们在代码中的位置改变而改变。
__LINE__ |
文件中的当前行号。 |
__FILE__ |
文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名。 |
__DIR__ |
文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录。 |
__FUNCTION__ |
函数名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该函数被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。 |
__CLASS__ |
类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的。类名包括其被声明的作用区域(例如 Foo\Bar)。注意自 PHP 5.4 起 __CLASS__ 对 trait 也起作用。当用在 trait 方法中时,__CLASS__ 是调用 trait 方法的类的名字。 |
__TRAIT__ |
Trait 的名字(PHP 5.4.0 新加)。自 PHP 5.4 起此常量返回 trait 被定义时的名字(区分大小写)。Trait 名包括其被声明的作用区域(例如 Foo\Bar)。 |
__METHOD__ |
类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)。 |
__NAMESPACE__ |
当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。 |
5.运算符
结合方向 | 运算符 | 附加信息 |
---|---|---|
无 | clone new | clone 和 new |
左 | [ | array() |
右 | ++ -- ~ (int) (float) (string) (array) (object) (bool) @ | 类型和递增/递减 |
无 | instanceof | 类型 |
右 | ! | 逻辑运算符 |
左 | * / % | 算术运算符 |
左 | + - . | 算术运算符和字符串运算符 |
左 | << >> | 位运算符 |
无 | == != === !== <> | 比较运算符 |
左 | & | 位运算符和引用 |
左 | ^ | 位运算符 |
左 | | | 位运算符 |
左 | && | 逻辑运算符 |
左 | || | 逻辑运算符 |
左 | ? : | 三元运算符 |
右 | = += -= *= /= .= %= &= |= ^= <<= >>= => | 赋值运算符 |
左 | and | 逻辑运算符 |
左 | xor | 逻辑运算符 |
左 | or | 逻辑运算符 |
左 | , | 多处用到 |
除法运算符总是返回浮点数。只有在下列情况例外:两个操作数都是整数(或字符串转换成的整数)并且正好能整除,这时它返回一个整数。取模运算符的操作数在运算之前都会转换成整数(除去小数部分)。取模运算符 % 的结果和被除数的符号(正负号)相同。基本的赋值运算符是"="。实际上意味着把右边表达式的值赋给左边的运算数。
$a = ($b = 4) + 5; // $a 现在成了 9,而 $b 成了 4。
在 PHP 中普通的传值赋值行为有个例外就是碰到对象 object 时,在 PHP 5 中是以引用赋值的,除非明确使用了 clone 关键字来拷贝。PHP 支持引用赋值,使用"$var = &$othervar;"语法。引用赋值意味着两个变量指向了同一个数据,没有拷贝任何东西。
位运算符:
$a & $b |
And(按位与) | 将把 $a 和 $b 中都为 1 的位设为 1。 |
$a | $b |
Or(按位或) | 将把 $a 和 $b 中任何一个为 1 的位设为 1。 |
$a ^ $b |
Xor(按位异或) | 将把 $a 和 $b 中一个为 1 另一个为 0 的位设为 1。 |
~ $a |
Not(按位取反) | 将 $a 中为 0 的位设为 1,反之亦然。 |
$a << $b |
Shift left(左移) | 将 $a 中的位向左移动 $b 次(每一次移动都表示"乘以 2")。 |
$a >> $b |
Shift right(右移) | 将 $a 中的位向右移动 $b 次(每一次移动都表示"除以 2")。 |
左移时右侧以零填充,符号位被移走意味着正负号不被保留。右移时左侧以符号位填充,意味着正负号被保留。
要注意数据类型的转换。如果左右参数都是字符串,则位运算符将对字符的 ASCII 值进行操作。不要在 32 位系统下向右移超过 32 位。不要在结果可能超过 32 的情况下左移。使用 gmp 扩展对超出 PHP_INT_MAX 的数值来进行位操作。
比较运算符:
$a == $b | 等于 | TRUE ,如果类型转换后 $a 等于 $b。 |
$a === $b | 全等 | TRUE ,如果 $a 等于 $b,并且它们的类型也相同。 |
$a != $b | 不等 | TRUE ,如果类型转换后 $a 不等于 $b。 |
$a <> $b | 不等 | TRUE ,如果类型转换后 $a 不等于 $b。 |
$a !== $b | 不全等 | TRUE ,如果 $a 不等于 $b,或者它们的类型不同。 |
$a < $b | 小与 | TRUE ,如果 $a 严格小于 $b。 |
$a > $b | 大于 | TRUE ,如果 $a 严格大于 $b。 |
$a <= $b | 小于等于 | TRUE ,如果 $a 小于或者等于 $b。 |
$a >= $b | 大于等于 | TRUE ,如果 $a 大于或者等于 $b。 |
var_dump(0 == "a"); // 0 == 0 -> true
var_dump("1" == "01"); // 1 == 1 -> true
var_dump("10" == "1e1"); // 10 == 10 -> true
由于浮点数 float 的内部表达方式,不应比较两个浮点数是否相等。另一个条件运算符是"?:"(或三元)运算符 。
$action = (empty($_POST[‘action‘])) ? ‘default‘ : $_POST[‘action‘];
表达式 (expr1) ? (expr2) : (expr3) 在 expr1 求值为 TRUE
时的值为 expr2,在 expr1 求值为 FALSE
时的值为 expr3。
自 PHP 5.3 起,可以省略三元运算符中间那部分。表达式 expr1 ?: expr3 在 expr1 求值为 TRUE
时返回expr1,否则返回 expr3。
PHP 支持一个错误控制运算符:@。当将其放置在一个 PHP 表达式之前,该表达式可能产生的任何错误信息都被忽略掉。
如果用 set_error_handler()设定了自定义的错误处理函数,仍然会被调用,但是此错误处理函数可以(并且也应该)调用 error_reporting(),而该函数在出错语句前有 @ 时将返回 0。如果激活了 track_errors特性,表达式所产生的任何错误信息都被存放在变量 $php_errormsg中。此变量在每次出错时都会被覆盖,所以如果想用它的话就要尽早检查。
PHP 支持一个执行运算符:反引号(``)。注意这不是单引号!PHP 将尝试将反引号中的内容作为外壳命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出)。使用反引号运算符"`"的效果与函数 shell_exec() 相同。
<?php
$output = `ls -al`;
echo "<pre>$output</pre>";
?>
反引号运算符在激活了安全模式或关闭了 shell_exec() 时是无效的。与其它某些语言不同,反引号不能在双引号字符串中使用。
递增或递减布尔值没有效果。递增递减运算符:
++$a | 前加 | $a 的值加一,然后返回 $a。 |
$a++ | 后加 | 返回 $a,然后将 $a 的值加一。 |
--$a | 前减 | $a 的值减一, 然后返回 $a。 |
$a-- | 后减 | 返回 $a,然后将 $a 的值减一。 |
逻辑运算符:
$a and $b | And(逻辑与) | TRUE ,如果 $a 和 $b 都为 TRUE 。 |
$a or $b | Or(逻辑或) | TRUE ,如果 $a 或 $b 任一为 TRUE 。 |
$a xor $b | Xor(逻辑异或) | TRUE ,如果 $a 或 $b 任一为 TRUE ,但不同时是。 |
! $a | Not(逻辑非) | TRUE ,如果 $a 不为 TRUE 。 |
$a && $b | And(逻辑与) | TRUE ,如果 $a 和 $b 都为 TRUE 。 |
$a || $b | Or(逻辑或) | TRUE ,如果 $a 或 $b 任一为 TRUE 。 |
"与"和"或"有两种不同形式运算符的原因是它们运算的优先级不同
有两个字符串(string)运算符。第一个是连接运算符("."),它返回其左右参数连接后的字符串。第二个是连接赋值运算符(".="),它将右边参数附加到左边的参数之后。
<?php
$a = "Hello ";
$b = $a . "World!"; // now $b contains "Hello World!"
$a = "Hello ";
$a .= "World!"; // now $a contains "Hello World!"
?>
数组运算符:
$a + $b | 联合 | $a 和 $b 的联合。 |
$a == $b | 相等 | 如果 $a 和 $b 具有相同的键/值对则为 TRUE 。 |
$a === $b | 全等 | 如果 $a 和 $b 具有相同的键/值对并且顺序和类型都相同则为 TRUE 。 |
$a != $b | 不等 | 如果 $a 不等于 $b 则为 TRUE 。 |
$a <> $b | 不等 | 如果 $a 不等于 $b 则为 TRUE 。 |
$a !== $b | 不全等 | 如果 $a 不全等于 $b 则为 TRUE 。 |
+ 运算符把右边的数组元素附加到左边的数组后面,两个数组中都有的键名,则只用左边数组中的,右边的被忽略。
<?php
$a = array("apple", "banana");
$b = array(1 => "banana", "0" => "apple");
var_dump($a == $b); // bool(true)
var_dump($a === $b); // bool(false)
?>
类型运算符:instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例。instanceof 也可用来确定一个变量是不是继承自某一父类的子类的实例。检查一个对象是否不是某个类的实例,可以使用逻辑运算符 not。instanceof也可用于确定一个变量是不是实现了某个接口的对象的实例。虽然 instanceof 通常直接与类名一起使用,但也可以使用对象或字符串变量:
<?php
interface MyInterface{ }
class MyClass implements MyInterface{ }
$a = new MyClass;
$b = new MyClass;
$c = ‘MyClass‘;
$d = ‘NotMyClass‘;
var_dump($a instanceof $b); // $b is an object of class MyClass
var_dump($a instanceof $c); // $c is a string ‘MyClass‘
var_dump($a instanceof $d); // $d is a string ‘NotMyClass‘
?>
bool(true) bool(true) bool(false)
如果被检测的变量不是对象,instanceof 并不发出任何错误信息而是返回 FALSE
。不允许用来检测常量。instanceof 运算符是 PHP 5 引进的。在此之前用 is_a(),但是后来 is_a() 被废弃而用 instanceof替代了。注意自 PHP 5.3.0 起,又恢复使用 is_a() 了。
6.流程控制
if 语句可以无限层地嵌套在其它 if 语句中,这给程序的不同部分的条件执行提供了充分的弹性。
else 语句仅在 if 以及 elseif(如果有的话)语句中的表达式的值为 FALSE
时执行。
elseif 的语句仅在之前的 if 和所有之前 elseif 的表达式值为 FALSE
,并且当前的 elseif 表达式值为 TRUE
时执行
必须要注意的是 elseif 与 else if只有在类似上例中使用花括号的情况下才认为是完全相同。如果用冒号来定义 if/elseif 条件,那就不能用两个单词的 else if,否则 PHP 会产生解析错误。
<?php
/* 不正确的使用方法: */
if($a > $b):
echo $a." is greater than ".$b;
else if($a == $b): // 将无法编译
echo "The above line causes a parse error.";
endif;
/* 正确的使用方法: */
if($a > $b):
echo $a." is greater than ".$b;
elseif($a == $b): // 注意使用了一个单词的 elseif
echo $a." equals ".$b;
else:
echo $a." is neither greater than or equal to ".$b;
endif;
?>
PHP 提供了一些流程控制的替代语法,包括 if,while,for,foreach和 switch。替代语法的基本形式是把左花括号({)换成冒号(:),把右花括号(})分别换成 endif;,endwhile;,endfor;,endforeach;以及 endswitch;。
不支持在同一个控制块内混合使用两种语法。
do-while 循环和 while循环非常相似,区别在于表达式的值是在每次循环结束时检查而不是开始时。和一般的 while 循环主要的区别是 do-while的循环语句保证会执行一次(表达式的真值在每次循环结束后检查),然而在一般的 while 循环中就不一定了(表达式真值在循环开始时检查,如果一开始就为 FALSE
则整个循环立即终止)。
foreach 语法结构提供了遍历数组的简单方式。foreach仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。有两种语法:
foreach (array_expression as $value) statement foreach (array_expression as $key => $value) statement
当 foreach开始执行时,数组内部的指针会自动指向第一个单元。这意味着不需要在 foreach 循环之前调用 reset()。
由于 foreach 依赖内部数组指针,在循环中修改其值将可能导致意外的行为。
<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
unset($value); // 最后取消掉引用
?>
数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用 unset() 来将其销毁。
以下的代码功能完全相同:
<?php
$arr = array("one", "two", "three");
reset($arr);
while (list(, $value) = each($arr)) {
echo "Value: $value<br>\n";
}
foreach ($arr as $value) {
echo "Value: $value<br />\n";
}
?>
或者:
<?php
$arr = array("one", "two", "three");
reset($arr);
while (list($key, $value) = each($arr)) {
echo "Key: $key; Value: $value<br />\n";
}
foreach ($arr as $key => $value) {
echo "Key: $key; Value: $value<br />\n";
}
?>
PHP 5.5 增添了遍历一个数组的数组的功能并且把嵌套的数组解包到循环变量中,只需将 list() 作为值提供。
<?php
$array = [
[1, 2],
[3, 4],
];
foreach ($array as list($a, $b)) {
// $a contains the first element of the nested array,
// and $b contains the second element.
echo "A: $a; B: $b\n";
}
?>
A: 1; B: 2 A: 3; B: 4
list() 中的单元可以少于嵌套数组的,此时多出来的数组单元将被忽略。echo "$a\n"; //会输出1 3
如果 list() 中列出的单元多于嵌套数组则会发出一条消息级别的错误信息。echo "A: $a; B: $b; C: $c\n";//会输出
Notice: Undefined offset: 2 in example.php on line 7 A: 1; B: 2; C: Notice: Undefined offset: 2 in example.php on line 7 A: 3; B: 4; C:
break 可以接受一个可选的数字参数来决定跳出几重循环。
<?php
$arr = array(‘one‘, ‘two‘, ‘three‘, ‘four‘, ‘stop‘, ‘five‘);
while (list (, $val) = each($arr)) {
if ($val == ‘stop‘) {
break; /* You could also write ‘break 1;‘ here. */
}
echo "$val<br />\n";
}
/* 使用可选参数 */
$i = 0;
while (++$i) {
switch ($i) {
case 5:echo "At 5<br />\n";
break 1; /* 只退出 switch. */
case 10:echo "At 10; quitting<br />\n";
break 2; /* 退出 switch 和 while 循环 */
default:
break;
}
}
?>
continue在循环结构用用来跳过本次循环中剩余的代码并在条件求值为真时开始执行下一次循环。continue 接受一个可选的数字参数来决定跳过几重循环到循环结尾。默认值是 1,即跳到当前循环末尾。省略 continue后面的分号会导致混淆。
<?php
while (list ($key, $value) = each($arr)) {
if (!($key % 2)) { // skip odd members
continue;
}
do_something_odd($value);
}
$i = 0;
while ($i++ < 5) {
echo "Outer<br />\n";
while (1) {
echo "Middle<br />\n";
while (1) {
echo "Inner<br />\n";
continue 3;
}
echo "This never gets output.<br />\n";
}
echo "Neither does this.<br />\n";
}
?>
注意和其它语言不同,continue语句作用到 switch 上的作用类似于 break。如果在循环中有一个 switch 并希望 continue 到外层循环中的下一轮循环,用 continue 2。注意 switch/case 作的是松散比较。允许使用分号代替 case 语句后的冒号.
declare 结构用来设定一段代码的执行指令。declare的语法和其它流程控制结构相似:
declare (directive) statement
directive 部分允许设定 declare代码段的行为。目前只认识两个指令:ticks以及 encoding。
declare 代码段中的 statement部分将被执行—怎样执行以及执行中有什么副作用出现取决于 directive 中设定的指令。declare 结构也可用于全局范围,影响到其后的所有代码(但如果有 declare 结构的文件被其它文件包含,则对包含它的父文件不起作用)。
Tick(时钟周期)是一个在 declare 代码段中解释器每执行 N 条可计时的低级语句就会发生的事件。N的值是在 declare 中的 directive 部分用 ticks=N
来指定的。不是所有语句都可计时。通常条件表达式和参数表达式都不可计时。在每个 tick 中出现的事件是由 register_tick_function()来指定的。注意每个 tick 中可以出现多个事件。
可以用 encoding 指令来对每段脚本指定其编码方式。对脚本指定编码方式:
<?php
declare(encoding=‘ISO-8859-1‘);
// code here
?>
如果在一个函数中调用 return语句,将立即结束此函数的执行并将它的参数作为函数的值返回。return也会终止 eval() 语句或者脚本文件的执行。如果在全局范围中调用,则当前脚本文件中止运行。如果当前脚本文件是被 include 的或者 require的,则控制交回调用文件。此外,如果当前脚本是被 include的,则 return 的值会被当作 include调用的返回值。如果在主脚本文件中调用 return,则脚本中止运行。如果当前脚本文件是在 php.ini中的配置选项 auto_prepend_file 或者 auto_append_file 所指定的,则此脚本文件中止运行。当用引用返回值时永远不要使用括号,这样行不通。只能通过引用返回变量,而不是语句的结果。如果使用 return ($a); 时其实不是返回一个变量,而是表达式 ($a) 的值(当然,此时该值也正是 $a 的值)。
require 和 include 几乎完全一样,除了处理失败的方式不同之外。require 在出错时产生 E_COMPILE_ERROR
级别的错误。换句话说将导致脚本中止而 include 只产生警告(E_WARNING
),脚本会继续运行。
被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path 指定的目录寻找。如果在 include_path 下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条警告;这一点和 require 不同,后者会发出一个致命错误。当一个文件被包含时,其中所包含的代码继承了 include 所在行的变量范围。从该处开始,调用文件在该行处可用的任何变量在被调用的文件中也都可用。不过所有在包含文件中定义的函数和类都具有全局作用域。
vars.php
<?php
$color = ‘green‘;
$fruit = ‘apple‘;
?>
test.php
<?php
echo "A $color $fruit"; // A
include ‘vars.php‘;
echo "A $color $fruit"; // A green apple
?>
如果 include 出现于调用文件中的一个函数里,则被调用的文件中所包含的所有代码将表现得如同它们是在该函数内部定义的一样。所以它将遵循该函数的变量范围。此规则的一个例外是魔术常量,它们是在发生包含之前就已被解析器处理的。
<?php
function foo(){
global $color;
include ‘vars.php‘;
echo "A $color $fruit";
}
foo(); // A green apple
echo "A $color $fruit"; // A green
?>
处理返回值:在失败时 include 返回 FALSE 并且发出警告。成功的包含则返回 1,除非在包含文件中另外给出了返回值。可以在被包括的文件中使用 return 语句来终止该文件中程序的执行并返回调用它的脚本。同样也可以从被包含的文件中返回值。可以像普通函数一样获得 include 调用的返回值。不过这在包含远程文件时却不行,除非远程文件的输出具有合法的 PHP 开始和结束标记(如同任何本地文件一样)。可以在标记内定义所需的变量,该变量在文件被包含的位置之后就可用了。
因为 include 是一个特殊的语言结构,其参数不需要括号。在比较其返回值时要注意。
return.php
<?php
$var = ‘PHP‘;
return $var;
?>
noreturn.php
<?php
$var = ‘PHP‘;
?>
testreturns.php
<?php
$foo = include ‘return.php‘;
echo $foo; // prints ‘PHP‘
$bar = include ‘noreturn.php‘;
echo $bar; // prints 1
?>
如果在包含文件中定义有函数,这些函数不管是在 return 之前还是之后定义的,都可以独立在主文件中使用。如果文件被包含两次,PHP 5 发出致命错误因为函数已经被定义,但是 PHP 4 不会对在 return 之后定义的函数报错。推荐使用 include_once 而不是检查文件是否已包含并在包含文件中有条件返回。要在脚本中自动包含文件,参见 php.ini 中的 auto_prepend_file 和 auto_append_file 配置选项。
require_once 语句和 require 语句完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。
include_once 语句在脚本执行期间包含并运行指定文件。此行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。如同此语句名字暗示的那样,只会包含一次。
goto 操作符可以用来跳转到程序中的另一位置。该目标位置可以用目标名称加上冒号来标记,而跳转指令是goto 之后接上目标位置的标记。PHP 中的 goto 有一定限制,目标位置只能位于同一个文件和作用域,也就是说无法跳出一个函数或类方法,也无法跳入到另一个函数。也无法跳入到任何循环或者 switch 结构中。可以跳出循环或者 switch,通常的用法是用 goto 代替多层的 break。
goto 操作符仅在 PHP 5.3及以上版本有效。
7.函数
函数无需在调用之前被定义,除非是下面例子中函数是有条件被定义时。当一个函数是有条件被定义时,其定义必须在调用之前先处理。
<?php
$makefoo = true;
/* 不能在此处调用foo()函数,因为它还不存在,但可以调用bar()函数。*/
bar();
if ($makefoo) {
function foo(){
echo "I don‘t exist until program execution reaches me.\n";
}
}
/* 现在可以安全调用函数 foo()了,因为 $makefoo 值为真 */
if ($makefoo) foo();
function bar(){
echo "I exist immediately upon program start.\n";
}
?>
函数中的函数:
<?php
function foo(){
function bar(){
echo "I don‘t exist until foo() is called.\n";
}
}
/* 现在还不能调用bar()函数,因为它还不存在 */
foo();
/* 现在可以调用bar()函数了,因为foo()函数的执行使得bar()函数变为已定义的函数 */
bar();
?>
PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数。PHP 的函数支持可变数量的参数和默认参数。
在 PHP 中可以调用递归函数。但是要避免递归函数/方法调用超过 100-200 层,因为可能会使堆栈崩溃从而使当前脚本终止。
<?php
function recursion($a){
if ($a < 20) {
echo "$a\n";
recursion($a + 1);
}
}
?>
PHP 支持按值传递参数(默认),通过引用传递参数以及默认参数。也支持可变长度参数列表。默认情况下,函数参数通过值传递(因而即使在函数内部改变参数的值,它并不会改变函数外部的值)。如果希望允许函数修改它的参数值,必须通过引用传递参数。
如果想要函数的一个参数总是通过引用传递,可以在函数定义中该参数的前面加上符号 &。用引用传递函数参数:
<?php
function add_some_extra(&$string){
$string .= ‘and something extra.‘;
}
$str = ‘This is a string, ‘;
add_some_extra($str);
echo $str; // outputs ‘This is a string, and something extra.‘
?>
在函数中使用默认参数:
<?php
function makecoffee($type = "cappuccino"){
return "Making a cup of $type.\n";
}
echo makecoffee(); //Making a cup of cappuccino.
echo makecoffee(null); //Making a cup of .
echo makecoffee("espresso"); //Making a cup of espresso.
?>
PHP 还允许使用数组 array 和特殊类型 NULL
作为默认参数:
<?php
function makecoffee($types = array("cappuccino"), $coffeeMaker = NULL){
$device = is_null($coffeeMaker) ? "hands" : $coffeeMaker;
return "Making a cup of ".join(", ", $types)." with $device.\n";
}
echo makecoffee();
echo makecoffee(array("cappuccino", "lavazza"), "teapot");
?>
默认值必须是常量表达式,不能是诸如变量,类成员,或者函数调用等。注意当使用默认参数时,任何默认参数必须放在任何非默认参数的右侧;否则,函数将不会按照预期的情况工作。自 PHP 5 起,传引用的参数也可以有默认值。
PHP 在用户自定义函数中支持可变数量的参数列表。
<?php
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4); //10
?>
从函数返回一个引用,必须在函数声明和指派返回值给一个变量时都使用引用运算符 &:
<?php
function &returns_reference(){
return $someref;
}
$newref =& returns_reference();
?>
可变函数可以用来实现包括回调函数,函数表在内的一些用途。
匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值。当然,也有其它应用的情况。
<?php
echo preg_replace_callback(‘~-([a-z])~‘, function ($match) {
return strtoupper($match[1]);
}, ‘hello-world‘);
// 输出 helloWorld
?>
闭包函数也可以作为变量的值来使用。PHP 会自动把此种表达式转换成内置类 Closure 的对象实例。
<?php
$greet = function($name){
printf("Hello %s\r\n", $name);
};
$greet(‘World‘);
$greet(‘PHP‘);
?>
闭包可以从父作用域中继承变量。 任何此类变量都应该用 use 语言结构传递进去。匿名函数目前是通过Closure 类来实现的。
8.类与对象
如果在 new 之后跟着的是一个包含有类名的字符串,则该类的一个实例被创建。如果该类属于一个名字空间,则必须使用其完整名称。
在类定义内部,可以用 new self 和 new parent 创建新对象。当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用克隆给一个已创建的对象建立一个新实例。
<?php
$instance = new SimpleClass();
$assigned = $instance;
$reference =& $instance;
$instance->var = ‘$assigned will have this value‘;
$instance = null; // $instance and $reference become null
var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>
NULL NULL object(SimpleClass)#1 (1) { ["var"]=> string(30) "$assigned will have this value" }
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。
当覆盖方法时,参数必须保持一致否则 PHP 将发出 E_STRICT
级别的错误信息。但构造函数例外,构造函数可在被覆盖时使用不同的参数。
自 PHP 5.5 起,关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。
<?php
namespace NS {
class ClassName {
}
echo ClassName::class; //NS\ClassName
}
?>
在类的成员方法里面,可以用 ->(对象运算符):$this->property(其中 property 是该属性名)这种方式来访问非静态属性。静态属性则是用 ::(双冒号):self::$property 来访问。当一个方法在类定义内部被调用时,有一个可用的伪变量 $this。$this 是一个到主叫对象的引用(通常是该方法所从属的对象,但如果是从第二个对象静态调用时也可能是另一个对象)。
跟 heredocs 不同,nowdocs 可在任何静态数据上下文中使用,包括属性声明。
<?php
class foo {
// 自 5.3.0 起
public $bar = <<<‘EOT‘
bar
EOT;
}
?>
常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。和 heredoc 不同,nowdoc 可以用在任何静态数据中。
<?php
class foo {
// 自 PHP 5.3.0 起
const bar = <<<‘EOT‘
bar
EOT;
}
?>
可以定义一个 __autoload() 函数,它会在试图使用尚未被定义的类时自动调用。通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。spl_autoload_register() 提供了一种更加灵活的方式来实现类的自动加载。因此,不再建议使用 __autoload() 函数,在以后的版本中它可能被弃用。如果类名比如被用于 call_user_func(),则它可能包含一些危险的字符,比如 ../。 建议您在这样的函数中不要使用用户的输入,起码需要在 __autoload() 时验证下输入。
本例尝试加载接口 ITest。
<?php
function __autoload($name) {
var_dump($name);
}
class Foo implements ITest {
}
/*
string(5) "ITest"
Fatal error: Interface ‘ITest‘ not found in ...
*/
?>
本例抛出一个异常并在 try/catch 语句块中演示。
<?php
function __autoload($name) {
echo "Want to load $name.\n";
throw new Exception("Unable to load $name.");
}
try {
$obj = new NonLoadableClass();
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
?>
Want to load NonLoadableClass. Unable to load NonLoadableClass.
如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。
为了实现向后兼容性,如果 PHP 5 在类中找不到 __construct() 函数并且也没有从父类继承一个的话,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。析构函数在脚本关闭时调用,此时所有的 HTTP 头信息已经发出。脚本关闭时的工作目录有可能和在 SAPI(如 apache)中时不同。试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。
对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。被定义为公有的类成员可以在任何地方被访问。被定义为受保护的类成员则可以被其自身以及其子类和父类访问。被定义为私有的类成员则只能被其定义所在的类访问。类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。
类中的方法可以被定义为公有,私有或受保护。如果没有设置这些关键字,则该方法默认为公有。
同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。
<?php
class Test{
private $foo;
public function __construct($foo){
$this->foo = $foo;
}
private function bar(){
echo ‘Accessed the private method.‘;
}
public function baz(Test $other){
// We can change the private property:
$other->foo = ‘hello‘;
var_dump($other->foo);
// We can also call the private method:
$other->bar();
}
}
$test = new Test(‘test‘);
$test->baz(new Test(‘other‘));
?>
string(5) "hello" Accessed the private method.
对象继承:
<?php
class foo{
public function printItem($string) {
echo ‘Foo: ‘ . $string . PHP_EOL;
}
public function printPHP(){
echo ‘PHP is great.‘ . PHP_EOL;
}
}
class bar extends foo{
public function printItem($string){
echo ‘Bar: ‘ . $string . PHP_EOL;
}
}
$foo = new foo();
$bar = new bar();
$foo->printItem(‘baz‘); // Output: ‘Foo: baz‘
$foo->printPHP(); // Output: ‘PHP is great‘
$bar->printItem(‘baz‘); // Output: ‘Bar: baz‘
$bar->printPHP(); // Output: ‘PHP is great‘
?>
范围解析操作符(::):可以用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法。当在类定义之外引用到这些项目时,要使用类名。自 PHP 5.3.0 起,可以通过变量来引用类,该变量的值不能是关键字(如 self,parent 和 static)。
self,parent 和 static 这三个特殊的关键字是用于在类定义的内部对其属性或方法进行访问的。当一个子类覆盖其父类中的方法时,PHP 不会调用父类中已被覆盖的方法。是否调用父类的方法取决于子类。这种机制也作用于构造函数和析构函数,重载以及魔术方法。
<?php
class MyClass{
protected function myFunc() {
echo "MyClass::myFunc()\n";
}
}
class OtherClass extends MyClass{ // 覆盖了父类的定义
public function myFunc(){ // 但还是可以调用父类中被覆盖的方法
parent::myFunc();
echo "OtherClass::myFunc()\n";
}
}
$class = new OtherClass();
$class->myFunc();
?>
声明类属性或方法为静态,就可以不实例化类而直接访问。静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)。
由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用。静态属性不可以由对象通过 -> 操作符来访问。
用静态方式调用一个非静态方法会导致一个 E_STRICT
级别的错误。就像其它所有的 PHP 静态变量一样,静态属性只能被初始化为文字或常量,不能使用表达式。所以可以把静态属性初始化为整数或数组,但不能初始化为另一个变量或函数返回值,也不能指向一个对象。
自 PHP 5.3.0 起,可以用一个变量来动态调用类。但该变量的值不能为关键字 self,parent 或 static。
定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;另外,这些方法的访问控制必须和父类中一样(或者更为宽松)。
<?php
abstract class AbstractClass{
// 我们的抽象方法仅需要定义需要的参数
abstract protected function prefixName($name);
}
class ConcreteClass extends AbstractClass{
// 我们的子类可以定义父类签名中不存在的可选参数
public function prefixName($name, $separator = ".") {
if ($name == "Pacman") {
$prefix = "Mr";
} elseif ($name == "Pacwoman") {
$prefix = "Mrs";
} else {
$prefix = "";
}
return "{$prefix}{$separator} {$name}";
}
}
$class = new ConcreteClass;
echo $class->prefixName("Pacman"), "\n";
echo $class->prefixName("Pacwoman"), "\n";
?>
Mr. Pacman Mrs. Pacwoman
使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。接口中定义的所有方法都必须是公有,这是接口的特性。要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。接口也可以继承,通过使用 extends 操作符。接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。接口加上类型约束,提供了一种很好的方式来确保某个对象包含有某些方法。
<?php
interface a{
const b = ‘Interface constant‘;
}
echo a::b; // 输出接口常量
class b implements a{
const b = ‘Class constant‘; // 错误写法,因为常量不能被覆盖。接口常量的概念和类常量是一样的。
}
?>
Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用方法集。Traits 和类组合的语义是定义了一种方式来减少复杂性,避免传统多继承和混入类(Mixin)相关的典型问题。
Trait 和一个类相似,但仅仅旨在用细粒度和一致的方式来组合功能。Trait 不能通过它自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用类的成员不需要继承。
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
从基类继承的成员被 trait 插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
从基类继承的成员被插入的 SayWorld Trait 中的 MyHelloWorld 方法所覆盖。其行为 MyHelloWorld 类中定义的方法一致。优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。
<?php
class Base {
public function sayHello() {
echo ‘Hello ‘;
}
}
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo ‘World!‘;
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello();//Hello World!
?>
通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。
<?php
trait Hello {
public function sayHello() {
echo ‘Hello ‘;
}
}
trait World {
public function sayWorld() {
echo ‘World‘;
}
}
class MyHelloWorld {
use Hello, World;
public function sayExclamationMark() {
echo ‘!‘;
}
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();//Hello World!
?>
如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。以上方式仅允许排除掉其它方法,as 操作符可以将其中一个冲突的方法以另一个名称来引入。
正如类能够使用 trait 一样,其它 trait 也能够使用 trait。在 trait 定义时使用一个或多个 trait,它能够组合其它 trait 中的部分或全部成员。
<?php
trait Hello {
public function sayHello() {
echo ‘Hello ‘;
}
}
trait World {
public function sayWorld() {
echo ‘World!‘;
}
}
trait HelloWorld {
use Hello, World;
}
class MyHelloWorld {
use HelloWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();//Hello World!
?>
为了对使用的类施加强制要求,trait 支持抽象方法的使用。
<?php
trait Hello {
public function sayHelloWorld() {
echo ‘Hello‘.$this->getWorld();
}
abstract public function getWorld();
}
class MyHelloWorld {
private $world;
use Hello;
public function getWorld() {
return $this->world;
}
public function setWorld($val) {
$this->world = $val;
}
}
?>
Traits 可以被静态成员静态方法定义。
<?php
trait Counter {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
class C1 {
use Counter;
}
class C2 {
use Counter;
}
$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>
Trait 同样可以定义属性。
<?php
trait PropertiesTrait {
public $x = 1;
}
class PropertiesExample {
use PropertiesTrait;
}
$example = new PropertiesExample;
$example->x;
?>
如果 trait 定义了一个属性,那类将不能定义同样名称的属性,否则会产生一个错误。如果该属性在类中的定义与在 trait 中的定义兼容(同样的可见性和初始值)则错误的级别是 E_STRICT
,否则是一个致命错误。
<?php
trait PropertiesTrait {
public $same = true;
public $different = false;
}
class PropertiesExample {
use PropertiesTrait;
public $same = true; // Strict Standards
public $different = true; // 致命错误
}
?>
当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。所有的重载方法都必须被声明为 public。PHP中的"重载"与其它绝大多数面向对象语言不同。传统的"重载"是用于提供多个同名的类方法,但各方法的参数类型和个数不同。
属性重载只能在对象中进行。在静态方法中,这些魔术方法将不会被调用。所以这些方法都不能被 声明为 static
publicvoid__set ( string$name
, mixed$value
)
publicmixed__get ( string$name
)
publicbool__isset ( string$name
)
publicvoid__unset ( string$name
)
在给不可访问属性赋值时,__set() 会被调用。
读取不可访问属性的值时,__get() 会被调用。
当对不可访问属性调用 isset() 或 empty() 时,__isset()会被调用。
当对不可访问属性调用 unset() 时,__unset()会被调用。
参数 $name 是指要操作的变量名称。__set()方法的 $value 参数指定了 $name 变量的值。
在除 isset()外的其它语言结构中无法使用重载的属性,这意味着当对一个重载的属性使用 empty() 时,重载魔术方法将不会被调用。为避开此限制,必须将重载属性赋值到本地变量再使用 empty()。
方法重载:
publicmixed__call ( string$name
, array$arguments
)
public staticmixed__callStatic ( string$name
, array$arguments
)
在对象中调用一个不可访问方法时,__call() 会被调用。
用静态方式中调用一个不可访问方法时,__callStatic() 会被调用。
$name 参数是要调用的方法名称。$arguments参数是一个枚举数组,包含着要传递给方法 $name 的参数。
PHP 5 提供了一种定义对象的方法使其可以通过单元列表来遍历,例如用 foreach语句。默认情况下,所有可见属性都将被用于遍历。foreach 遍历了所有其能够访问的可见属性。
<?php
class MyClass{
public $var1 = ‘value 1‘;
public $var2 = ‘value 2‘;
public $var3 = ‘value 3‘;
protected $protected = ‘protected var‘;
private $private = ‘private var‘;
function iterateVisible() {
echo "MyClass::iterateVisible:\n";
foreach($this as $key => $value) {
print "$key => $value\n";
}
}
}
$class = new MyClass();
foreach($class as $key => $value) {
print "$key => $value\n";
}
echo "\n";
$class->iterateVisible();
?>
var1 => value 1 var2 => value 2 var3 => value 3 MyClass::iterateVisible: var1 => value 1 var2 => value 2 var3 => value 3 protected => protected var private => private var
更进一步,可以实现 Iterator接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用。可以用 IteratorAggregate接口以替代实现所有的 Iterator 方法。IteratorAggregate只需要实现一个方法IteratorAggregate::getIterator(),其应返回一个实现了 Iterator 的类的实例。
9.类与对象:魔术方法
__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo()等方法在 PHP 中被称为"魔术方法"(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。
publicarray__sleep ( void )
void__wakeup ( void )
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL
被序列化,并产生一个 E_NOTICE
级别的错误。__sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。__sleep() 方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。与之相反,unserialize() 会检查是否存在一个 __wakeup()方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。__wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
<?php
class Connection {
protected $link;
private $server, $username, $password, $db;
public function __construct($server, $username, $password, $db){
$this->server = $server;
$this->username = $username;
$this->password = $password;
$this->db = $db;
$this->connect();
}
private function connect(){
$this->link = mysql_connect($this->server, $this->username, $this->password);
mysql_select_db($this->db, $this->link);
}
public function __sleep(){
return array(‘server‘, ‘username‘, ‘password‘, ‘db‘);
}
public function __wakeup(){
$this->connect();
}
}
?>
publicstring__toString ( void )
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR
级别的致命错误。不能在 __toString()方法中抛出异常。这么做会导致致命错误。
<?php
class TestClass{
public $foo;
public function __construct($foo) {
$this->foo = $foo;
}
public function __toString() {
return $this->foo;
}
}
$class = new TestClass(‘Hello‘);
echo $class; //Hello
?>
mixed__invoke ([ $...
] )
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
<?php
class CallableClass {
function __invoke($x) {
var_dump($x); //int(5)
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj)); //bool(true)
?>
staticobject__set_state ( array$properties
)
自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用。本方法的唯一参数是一个数组,其中包含按 array(‘property‘ => value, ...) 格式排列的类属性。
<?php
class A{
public $var1;
public $var2;
public static function __set_state($an_array) // As of PHP 5.1.0{
$obj = new A;
$obj->var1 = $an_array[‘var1‘];
$obj->var2 = $an_array[‘var2‘];
return $obj;
}
}
$a = new A;
$a->var1 = 5;
$a->var2 = ‘foo‘;
eval(‘$b = ‘ . var_export($a, true) . ‘;‘);
var_dump($b);
?>
object(A)#2 (2) { ["var1"]=>int(5) ["var2"]=>string(3) "foo" }
__debugInfo()
<?php
class C {
private $prop;
public function __construct($val) {
$this->prop = $val;
}
public function __debugInfo() {
return [
‘propSquared‘ => $this->prop ** 2,
];
}
}
var_dump(new C(42));
?>
object(C)#1 (1) { ["propSquared"]=>int(1764) }
Final 关键字:如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。属性不能被定义为 final,只有类和方法才能被定义为 final。
对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用。$copy_of_object = clone $object;当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。void__clone ( void )当复制完成时,如果定义了 __clone()方法,则新创建的对象(复制生成的对象)中的 __clone()方法会被调用,可用于修改属性的值(如果有必要的话)。
对象比较:当使用比较运算符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。而如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
类型约束:函数的参数可以指定必须为对象(在函数原型里面指定类的名字),接口,数组(PHP 5.1 起)或者 callable(PHP 5.4 起)。不过如果使用 NULL作为参数的默认值,那么在调用函数的时候依然可以使用 NULL 作为实参。如果一个类或接口指定了类型约束,则其所有的子类或实现也都如此。类型约束不能用于标量类型如 int 或 string。Traits 也不允许。
函数调用的参数与定义的参数类型不一致时,会抛出一个可捕获的致命错误。类型约束允许 NULL 值:
<?php
/* 接受 NULL 值 */
function test(stdClass $obj = NULL) { }
test(NULL);
test(new stdClass);
?>
后期静态绑定:后期绑定"的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为"静态绑定",因为它可以用于(但不限于)静态方法的调用。
使用 self:: 或者 __CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类:
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test(); //A
?>
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // 后期静态绑定从这里开始
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test(); //B
?>
在非静态环境下,所调用的类即为该对象实例所属的类。由于 $this->会在同一作用范围内尝试调用私有方法,而 static::则可能给出不同结果。另一个区别是 static:: 只能用于静态属性。
<?php
class A {
private function foo() {
echo "success!\n";
}
public function test() {
$this->foo();
static::foo();
}
}
class B extends A {
/* foo() will be copied to B, hence its scope will still be A and
* the call be successful */
}
class C extends A {
private function foo() {
/* original method is replaced; the scope of the new one is C */
}
}
$b = new B();
$b->test();
$c = new C();
$c->test(); //fails
?>
success! success! success! Fatal error: Call to private method C::foo() from context ‘A‘ in /tmp/test.php on line 9
后期静态绑定的解析会一直到取得一个完全解析了的静态调用为止。另一方面,如果静态调用使用 parent:: 或者 self:: 将转发调用信息。
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test(); //会输出:A C C
?>
php的引用是别名,就是两个不同的变量名字指向相同的内容。在php5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。
<?php
class A { public $foo = 1;}
$a = new A;
$b = $a; // $a ,$b都是同一个标识符的拷贝 // ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n"; //2
$c = new A;
$d = &$c; // $c ,$d是引用 // ($c,$d) = <id>
$d->foo = 2;
echo $c->foo."\n"; //2
$e = new A;
function foo($obj) { $obj->foo = 2;}// ($obj) = ($e) = <id>
foo($e);
echo $e->foo."\n"; //2
?>
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。 如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。
<?php
// classa.inc:
class A {
public $one = 1;
public function show_one() {
echo $this->one;
}
}
// page1.php:
include("classa.inc");
$a = new A;
$s = serialize($a);
file_put_contents(‘store‘, $s); // 把变量$s保存起来以便文件page2.php能够读到
// page2.php:
include("classa.inc"); // 要正确了解序列化,必须包含下面一个文件
$s = file_get_contents(‘store‘);
$a = unserialize($s);
$a->show_one(); // 现在可以使用对象$a里面的函数 show_one()
?>
在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。 不然有可能出现在解序列化对象的时候,没有找到该对象的类的定义,从而把没有方法的类__PHP_Incomplete_Class_Name作为该对象的类,导致返回一个没有用的对象。所以在上面的例子中,当运行session_register("a"),把变量$a放在会话里之后,需要在每个页面都包含文件classa.inc,而不是只有文件page1.php和page2.php。
10.命名空间
在PHP中,命名空间用来解决在编写类库或应用程序时创建可重用的代码如类或函数时碰到的两类问题:
- 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
- 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。
虽然任意合法的PHP代码都可以包含在命名空间中,但只有三种类型的代码受命名空间的影响,它们是:类,函数和常量。
命名空间通过关键字namespace来声明。如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间。
声明单个命名空间:
<?php
namespace MyProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
?>
在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare语句。另外,所有非 PHP 代码包括空白符都不能出现在命名空间的声明之前。允许将同一个命名空间的内容分割存放在不同的文件中。
PHP 命名空间也允许指定层次化的命名空间的名称。声明分层次的单个命名空间:
<?php
namespace MyProject\Sub\Level;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
?>
上面的例子创建了常量MyProject\Sub\Level\CONNECT_OK,类 MyProject\Sub\Level\Connection和函数 MyProject\Sub\Level\Connection。
也可以在同一个文件中定义多个命名空间。定义多个命名空间,简单组合语法(不建议):
<?php
namespace MyProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
namespace AnotherProject;
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
?>
定义多个命名空间,大括号语法:
<?php
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace AnotherProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
?>
将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法。全局代码必须用一个不带名称的 namespace 语句加上大括号括起来:
<?php
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace { // global code
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}
?>
定义多个命名空间和不包含在命名空间中的代码:
<?php
declare(encoding=‘UTF-8‘);
namespace MyProject {
const CONNECT_OK = 1;
class Connection { /* ... */ }
function connect() { /* ... */ }
}
namespace { // 全局代码
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}
?>
在文件系统中访问一个文件有三种方式:
<?php
namespace Foo\Bar\subnamespace;
const FOO = 1;
function foo() {}
class foo{ static function staticmethod() {} }
?>
<?php
namespace Foo\Bar;
include ‘file1.php‘;
const FOO = 2;
function foo() {}
class foo{ static function staticmethod() {} }
/* 非限定名称 */
foo(); // 解析为 Foo\Bar\foo resolves to function Foo\Bar\foo
foo::staticmethod(); // 解析为类 Foo\Bar\foo的静态方法staticmethod。resolves to class Foo\Bar\foo, method staticmethod
echo FOO; // resolves to constant Foo\Bar\FOO
/* 限定名称 */
subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo
subnamespace\foo::staticmethod(); // 解析为类 Foo\Bar\subnamespace\foo,
// 以及类的方法 staticmethod
echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO
/* 完全限定名称 */
\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
\Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod
echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO
?>
注意访问任意全局类、函数或常量,都可以使用完全限定名称,例如 \strlen() 或 \Exception 或 \INI_ALL。
<?php
namespace Foo;
function strlen() {}
const INI_ALL = 3;
class Exception {}
$a = \strlen(‘hi‘); // 调用全局函数strlen
$b = \INI_ALL; // 访问全局常量 INI_ALL
$c = new \Exception(‘error‘); // 实例化全局类 Exception
?>
PHP 命名空间的实现受到其语言自身的动态特征的影响。
<?php
class classname{ function __construct(){ echo __METHOD__,"\n"; } }
function funcname(){ echo __FUNCTION__,"\n"; }
const constname = "global";
$a = ‘classname‘;
$obj = new $a; // prints classname::__construct
$b = ‘funcname‘;
$b(); // prints funcname
echo constant(‘constname‘), "\n"; // prints global
?>
必须使用完全限定名称(包括命名空间前缀的类名称)。注意因为在动态的类名称、函数名称或常量名称中,限定名称和完全限定名称没有区别,因此其前导的反斜杠是不必要的。动态访问命名空间的元素:
<?php
namespace namespacename;
class classname{ function __construct() { echo __METHOD__,"\n"; } }
function funcname() { echo __FUNCTION__,"\n"; }
const constname = "namespaced";
include ‘example1.php‘;
$a = ‘classname‘;
$obj = new $a; // prints classname::__construct
$b = ‘funcname‘;
$b(); // prints funcname
echo constant(‘constname‘), "\n"; // prints global
/* note that if using double quotes, "\\namespacename\\classname" must be used */
$a = ‘\namespacename\classname‘;
$obj = new $a; // prints namespacename\classname::__construct
$a = ‘namespacename\classname‘;
$obj = new $a; // also prints namespacename\classname::__construct
$b = ‘namespacename\funcname‘;
$b(); // prints namespacename\funcname
$b = ‘\namespacename\funcname‘;
$b(); // also prints namespacename\funcname
echo constant(‘\namespacename\constname‘), "\n"; // prints namespaced
echo constant(‘namespacename\constname‘), "\n"; // also prints namespaced
?>
PHP支持两种抽象的访问当前命名空间内部元素的方法,__NAMESPACE__
魔术常量和namespace关键字。
常量__NAMESPACE__
的值是包含当前命名空间名称的字符串。在全局的,不包括在任何命名空间中的代码,它包含一个空的字符串。常量 __NAMESPACE__
在动态创建名称时很有用。
<?php
namespace MyProject;
echo ‘"‘, __NAMESPACE__, ‘"‘; // 输出 "MyProject"
?>
<?php
namespace MyProject;
function get($classname){
$a = __NAMESPACE__ . ‘\\‘ . $classname;
return new $a;
}
?>
关键字 namespace 可用来显式访问当前命名空间或子命名空间中的元素。它等价于类中的 self 操作符。
<?php
namespace MyProject;
use blah\blah as mine; // see "Using namespaces: importing/aliasing"
blah\mine(); // calls function MyProject\blah\mine()
namespace\blah\mine(); // calls function MyProject\blah\mine()
namespace\func(); // calls function MyProject\func()
namespace\sub\func(); // calls function MyProject\sub\func()
namespace\cname::method(); // calls static method "method" of class MyProject\cname
$a = new namespace\sub\cname(); // instantiates object of class MyProject\sub\cname
$b = namespace\CONSTANT; // assigns value of constant MyProject\CONSTANT to $b
?>
允许通过别名引用或导入外部的完全限定名称,是命名空间的一个重要特征。这有点类似于在类 unix 文件系统中可以创建对其它的文件或目录的符号连接。为类名称使用别名,或为命名空间名称使用别名。注意PHP不支持导入函数或常量。
在PHP中,别名是通过操作符 use 来实现的.
<?php
namespace foo;
use My\Full\Classname as Another;
// 下面的例子与 use My\Full\NSname as NSname 相同
use My\Full\NSname;
// 导入一个全局类
use \ArrayObject;
$obj = new namespace\Another; // 实例化 foo\Another 对象
$obj = new Another; // 实例化 My\Full\Classname 对象
NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func
$a = new ArrayObject(array(1)); // 实例化 ArrayObject 对象
// 如果不使用 "use \ArrayObject" ,则实例化一个 foo\ArrayObject 对象
?>
注意对命名空间中的名称(包含命名空间分隔符的完全限定名称如 Foo\Bar以及相对的不包含命名空间分隔符的全局名称如 FooBar)来说,前导的反斜杠是不必要的也不允许有反斜杠,因为导入的名称必须是完全限定的,不会根据当前的命名空间作相对解析。通过use操作符导入/使用别名,一行中包含多个use语句:
<?php
use My\Full\Classname as Another, My\Full\NSname;
$obj = new Another; // 实例化一个 My\Full\Classname 对象
$a = ‘Another‘;
$obj = new $a; // 实际化一个 Another 对象
?>
另外,导入操作只影响非限定名称和限定名称。完全限定名称由于是确定的,故不受导入的影响。
<?php
use My\Full\Classname as Another, My\Full\NSname;
$obj = new Another; // instantiates object of class My\Full\Classname
$obj = new \Another; // instantiates object of class Another
$obj = new Another\thing; // instantiates object of class My\Full\Classname\thing
$obj = new \Another\thing; // instantiates object of class Another\thing
?>
如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,在名称前加上前缀 \表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。
<?php
namespace A\B\C;
/* 这个函数是 A\B\C\fopen */
function fopen() {
/* ... */
$f = \fopen(...); // 调用全局的fopen函数
return $f;
}
?>
在一个命名空间中,当 PHP 遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。类名称总是解析到当前命名空间中的名称。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称。
<?php
namespace A\B\C;
class Exception extends \Exception {}
$a = new Exception(‘hi‘); // $a 是类 A\B\C\Exception 的一个对象
$b = new \Exception(‘hi‘); // $b 是类 Exception 的一个对象
$c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类
?>
对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会退而使用全局空间中的函数或常量。
<?php
namespace A\B\C;
const E_ERROR = 45;
function strlen($str){
return \strlen($str) - 1;
}
echo E_ERROR, "\n"; // 输出 "45"
echo INI_ALL, "\n"; // 输出 "7" - 使用全局常量 INI_ALL
echo strlen(‘hi‘), "\n"; // 输出 "1"
if (is_array(‘hi‘)) { // 输出 "is not array"
echo "is array\n";
} else {
echo "is not array\n";
}
?>
命名空间名称定义:
- 非限定名称Unqualified name,例如 Foo;限定名称Qualified name,例如 Foo\Bar
- 完全限定名称Fully qualified name,例如 \Foo\Bar。 namespace\Foo 也是一个完全限定名称。
<?php
namespace A;
use B\D, C\E as F;
// 函数调用
foo(); // 首先尝试调用定义在命名空间"A"中的函数foo()
// 再尝试调用全局函数 "foo"
\foo(); // 调用全局空间函数 "foo"
my\foo(); // 调用定义在命名空间"A\my"中函数 "foo"
F(); // 首先尝试调用定义在命名空间"A"中的函数 "F"
// 再尝试调用全局函数 "F"
// 类引用
new B(); // 创建命名空间 "A" 中定义的类 "B" 的一个对象
// 如果未找到,则尝试自动装载类 "A\B"
new D(); // 使用导入规则,创建命名空间 "B" 中定义的类 "D" 的一个对象
// 如果未找到,则尝试自动装载类 "B\D"
new F(); // 使用导入规则,创建命名空间 "C" 中定义的类 "E" 的一个对象
// 如果未找到,则尝试自动装载类 "C\E"
new \B(); // 创建定义在全局空间中的类 "B" 的一个对象
// 如果未发现,则尝试自动装载类 "B"
new \D(); // 创建定义在全局空间中的类 "D" 的一个对象
// 如果未发现,则尝试自动装载类 "D"
new \F(); // 创建定义在全局空间中的类 "F" 的一个对象
// 如果未发现,则尝试自动装载类 "F"
// 调用另一个命名空间中的静态方法或命名空间函数
B\foo(); // 调用命名空间 "A\B" 中函数 "foo"
B::foo(); // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法
// 如果未找到类 "A\B" ,则尝试自动装载类 "A\B"
D::foo(); // 使用导入规则,调用命名空间 "B" 中定义的类 "D" 的 "foo" 方法
// 如果类 "B\D" 未找到,则尝试自动装载类 "B\D"
\B\foo(); // 调用命名空间 "B" 中的函数 "foo"
\B::foo(); // 调用全局空间中的类 "B" 的 "foo" 方法
// 如果类 "B" 未找到,则尝试自动装载类 "B"
// 当前命名空间中的静态方法或函数
A\B::foo(); // 调用命名空间 "A\A" 中定义的类 "B" 的 "foo" 方法
// 如果类 "A\A\B" 未找到,则尝试自动装载类 "A\A\B"
\A\B::foo(); // 调用命名空间 "A\B" 中定义的类 "B" 的 "foo" 方法
// 如果类 "A\B" 未找到,则尝试自动装载类 "A\B"
?>