类与对象 - PHP手册笔记

原文:类与对象 - PHP手册笔记

基本概念

PHP对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝。

当创建新对象时,该对象总是被赋值,除非该对象定义了构造函数并且在出错时抛出了一个异常。类应在被实例化之前定义。

创建对象时,如果该类属于一个名字空间,则必须使用其完整名称。

在类定义内部,可以用new selfnew parent创建对象。

<?php
$instance = new stdClass();
$assigned = $instance;
$reference = & $instance;
$instance->var = '$assigned will have this value.';
$instance = null;
var_dump($instance);
var_dump($reference);
var_dump($assigned);

这段代码的输出如下,这是为什么呢?

null
null
object(stdClass)[1]
  public 'var' => string '$assigned will have this value.' (length=31)

PHP 5.3引进了两个新方法来创建一个对象的实例,可以使用下面的方法创建实例。

<?php
class Test {
	static public function getNew() {
		return new static;
	}
}
class Child extends Test {}
$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);  // true
$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);  // true
$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);  // true
var_dump($obj1 == $obj2);  // true

PHP不支持多重继承,被继承的方法和属性可以通过同样的名字重新声明被覆盖,注意参数必须保持一致,当然构造函数除外。但是如果父类定义方法时使用了final,则该方法不可被覆盖。可以通过parent::来访问被覆盖的方法和属性,parent::只能访问父类中的常量const,不能访问变量。

<?php
class A {
	private $name = 'A';
	const conname = 'A';
	public function getName() {
		return $this->name;
	}
}
class B extends A {
	private $name = 'B';
	const conname = 'B';
	public function getName() {
		return $this->name;
	}
	public function getParent() {
		return parent::conname;
	}
}
class C extends B {
	private $name = 'C';
	const conname = 'C';
	public function getName() {
		return $this->name;
	}
	public function getParent() {
		return parent::conname;
	}
}
$a = new A;
var_dump($a->getName());  // A
$b = new B;
var_dump($b->getName());  // B
var_dump($b->getParent());  // A
$c = new C;
var_dump($c->getName());  // C
var_dump($c->getParent());  // B

自PHP 5.5起,关键词class也可用于类名的解析。使用ClassName::class你可以获取一个字符串,包含了类ClassName的完全限定名称。

<?php
namespace NS {
	class ClassName {}
	echo ClassName::class;  // NS\ClassName
}

属性

属性,也就是类的变量成员。属性中的变量可以初始化,但是初始化的值必须是常数。这里的常数是指 PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值。

在类的成员方法里,访问非静态属性使用$this->property,访问静态属性使用self::$property。静态属性声明时使用static关键字。

类常量

在定义常量时不需要$符号和访问控制关键字。

接口(interface)中也可以定义常量。

自动加载类

写面向对象的应用程序时,通常对每个类的定义简历一个PHP源文件。当某个文件需要调用这些类时,需要在文件开头写一个长长的包含文件列表。其实,并不需要这样,可以定义一个__autoload()函数,它会在试图使用尚未被定义的类时自动调用。

手册Tip说,spl_autoload_register()提供了一种更加灵活的方式来实现类的自动加载,这个后面再看。

自动加载不可用于PHP的CLI交互模式,也就是命令行模式。

用户输入中可能存在危险字符,起码要在__autoload()时验证下输入。

可以通过下面的方式自动加载类。

<?php
function __autoload($class_name) {
	require_once $class_name.'.php';
}
$obj1 = new MyClass1();
$obj2 = new MyClass2();

对于异常处理,后面再看。

构造函数和析构函数

PHP 5允许开发者在一个类中定义一个方法作为构造函数,构造函数也不支持重载。

如果子类中定义了构造函数,则不会隐式调用父类的构造函数,否则会如同一个普通类方法那样从父类继承(前提是未被定义为private)。要执行父类的构造函数,需要在子类构造函数中调用parent::__construct()

与其它方法不同,当__construct()与父类__construct()具有不同参数时,可以覆盖。

自PHP 5.3.3起,在命名空间中,与类名同名的方法不再作为构造函数。

析构函数会在某个对象的所有引用都被删除或者对象被显示销毁时执行。析构函数即使在使用exit()终止脚本运行时也会被调用。

试图在析构函数中抛出异常,将会导致致命错误。

访问控制

类属性必须定义为公有、受保护、私有之一,不能省略关键字。如果类中方法没有设置访问控制的关键字,则该方法默认为公有。

同一个类的对象,即使不是同一个实例,也可以互相访问对方的私有与保护成员。示例程序如下。

<?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) {
		$other->foo = 'hello';
		var_dump($other->foo);
		$other->bar();
	}
}
$test = new Test('test');
$test->baz(new Test('other'));

对象继承

如果一个类扩展了另一个,则父类必须在子类前被声明。

范围解析操作符

范围解析操作符,简单地说就是一对冒号,可以用于访问静态成员、类常量,还可以用于调用父类中的属性和方法。

当在类定义之外引用这些项目时,要使用类名。

static

使用static关键字可以用来定义静态方法和属性,也可用于定义静态变量以及后期静态绑定。声明类属性或方法为静态,就可以不实例化类而直接访问。

静态属性不能通过一个类已实例化的对象来访问,但静态方法可以。

如果没有指定访问控制,属性和方法默认为公有。

用静态方法调用一个非静态方法会导致一个E_STRICT级别的错误。

抽象类

PHP 5支持抽象类和抽象方法。类中如果有一个抽象方法,那这个类必须被声明为抽象的。

抽象类不能被实例化。抽象方法只是声明了其调用方式(参数),不能定义其具体的功能实现。继承抽象类时,子类必须定义父类中的所有抽象方法,且这些方法的访问控制必须和父类一样活更宽松。

方法的调用方式必须匹配。但是,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。这也试用与PHP 5.4起的构造函数。可以在子类中定义父类签名中不存在的可选参数。

<?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');
echo $class->prefixName('Pacwoman');

对象接口

听说过接口,一直没用过。使用接口,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容,也就是说接口中定义的所有方法都是空的。接口中定义的所有方法都必须是公有的,这是接口的特性。

接口也可以继承多个接口,用逗号分隔,使用extends操作符。类中必须实现接口中定义的所有方法,否则会报错。要实现一个接口,使用implements操作符。类可以实现多个接口,用逗号分隔。实现多个接口时,接口中的方法不能有重名。类要实现接口,必须使用和接口中所定义的方法完全一致的方式。

接口中也可定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口覆盖。

traits

从PHP 5.4.0开始,可以使用traits实现代码复用。Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 不能通过它自身来实例化。它为传统继承增加了水平特性的组合。

优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。

通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误,为解决冲突,需使用insteadof操作符来指明使用冲突方法中的哪一个,这种方法仅允许排除掉其它方法。as操作符可以将其中一个冲突的方法以另一个名称(别名)来引入。

<?php
trait A {
	public function smallTalk() {
		echo 'a';
	}
	public function bigTalk() {
		echo 'A';
	}
}
trait B {
	public function smallTalk() {
		echo 'b';
	}
	public function bigTalk() {
		echo 'B';
	}
}
class Talker {
	use A, B {
		B::smallTalk insteadof A;
		A::bigTalk insteadof B;
		B::bigTalk as talk;
	}
}
$t = new Talker();
$t->smallTalk();  // b
$t->bigTalk();  // A
$t->talk();  // B

使用as操作符还可以用来调整方法的访问控制,或者给方法一个改变了访问控制的别名,原版方法的访问控制规则没有改变。

<?php
trait HelloWorld {
	public function sayHello() {
		echo 'Hello World.';
	}
}
class MyClass1 {
	use HelloWorld {
		sayHello as protected;
	}
}
class MyClass2 {
	use HelloWorld {
		sayHello as private myPrivateHello;
	}
}

就像类能够使用trait那样,多个trait能够组合为一个trait

为了对使用的类施加强制要求,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;
	}
}
$c = new MyHelloWorld;
$c->setWorld('world');
$c->sayHelloWorld();

如果trait定义了一个属性,那类将不能定义同样名称的属性,否则会产生错误。

重载

PHP提供的重载是指动态地创建类属性和方法,与其它绝大多数面向对象语言不同。通过魔术方法来实现。当使用不可访问的属性或方法时,重载方法会被调用。所有的重载方法都必须被声明为public

使用__get()__set()__isset()__unset()进行属性重载,示例如下。

<?php
class PropertyTest {
	private $data = array();
	public $declared = 1;
	private $hidden = 2;
	public function __set($name, $value) {
		echo "Setting $name to $value. " . '<br>';
		$this->data[$name] = $value;
	}
	public function __get($name) {
		echo "Getting $name. <br>";
		if(array_key_exists($name, $this->data)) {
			return $this->data[$name];
		}
		return null;
	}
	public function __isset($name) {
		echo "Is $name set? <br>";
		return isset($this->data[$name]);
	}
	public function __unset($name) {
		echo "Unsetting $name. <br>";
		unset($this->data[$name]);
	}
}
$obj = new PropertyTest;
$obj->a = 1;
var_dump($obj->a);
var_dump(isset($obj->a));
unset($obj->a);
var_dump(isset($obj->a));
var_dump($obj->declared);
var_dump($obj->hidden);

输出结果如下:

Setting a to 1.
Getting a.
int 1
Is a set?
boolean true
Unsetting a.
Is a set?
boolean false
int 1
Getting hidden.
null

在对象中调用一个不可访问方法时,__call()会被调用。用静态方式中调用一个不可访问方法时,__callStatic()会被调用。参数为调用方法的名称和一个枚举数组,注意区分大小写。

使用__call()__callStatic()对方法重载,示例如下。

<?php
class MethodTest {
	public function __call($name, $arguments) {
		echo "Calling object method $name " .
			implode(', ', $arguments) . '<br>';
	}
	public static function __callStatic($name, $arguments) {
		echo "Calling static method $name " .
			implode(', ', $arguments) . '<br>';
	}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');

遍历对象

对象可以用过单元列表来遍历,例如用foreach语句。默认所有可见属性都将被用于遍历。

<?php
class MyClass {
	public $var1 = 'value 1';
	public $var2 = 'value 2';
	public $var3 = 'value 3';
	private $var4 = 'value 4';
	protected $var5 = 'value 5';
}
$obj = new MyClass;
foreach($obj as $key => $value) {
	echo "$key => $value <br>";
}

示例程序2实现了Iterator接口的对象遍历,示例程序3通过实现IteratorAggregate来遍历对象。

魔术方法

PHP 将所有以__(两个下划线)开头的类方法保留为魔术方法。定义类方法时,除魔术方法外,建议不要以__为前缀。

前面遇到过的魔术方法有:__construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset()。后面将会介绍:__sleep()__wakeup()__toString()__invoke()__set_state()__clone()__debugInfo()

__sleep__wakeup不清楚具体做什么用的,示例程序中给出了个数据库连接的例子。

__toString方法用于一个类被当成字符串时应怎样回应。此方法必须返回一个字符串,且不能再方法中抛出异常。如果将一个未定义__toString()方法的对象转换为字符串,将产生错误。

当尝试以调用函数的方式调用一个对象时,__invoke()方法会被调用。

当调用var_export()导出类时,__set_state()会被调用。

当调用var_dump()时,__debugInfo会被调用。PHP 5.6新加入,没合适的环境无法测试。

final

果父类中的方法被声明为final,则子类无法覆盖该方法。如果一个类被声明为final,则不能被继承。属性不能被定义为final,只有类和方法才能被定义为final

对象复制

多数情况,我们不需要完全复制一个对象,但有时确实需要。对象复制可以通过clone关键字来完成。这种复制是通过调用对象的__clone()方法实现的,但是对象中的__clone()方法不能被直接调用。

对象比较

比较运算符==为真的条件是:两个对象的属性和属性值都相等,而且两个对象是同一个类的实例。

继承与统一个基类的两个子类的对象不会相等==

<?php
class Base {}
class A extends Base {}
class B extends Base {}
$a = new A;
$b = new B;
var_dump($a == $b);  // false

全等运算符===为真的条件是:两个对象变量一定要指向某个类的同一个实例(即同一个对象)。

类型约束

类型约束是指函数的参数可以指定必须为对象、接口、数组或者callable类型。但是类型约束不能用于标量类型如intstringtraits也不允许。类型约束允许NULL值。

后期静态绑定

后期静态绑定,用于在继承范围内引用静态调用的类。

转发调用,指的是通过以下几种方式进行的静态调用:self::parent::static::以及forward_static_call()

后期静态绑定的工作原理是,存储了上一个非转发调用的类名。

当进行静态方法调用时,该类名即为明确指定的那个;当进行非静态方法调用时,即为该对象所属的类。

使用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
B::who();  // B

static::关键字表示运行时最初调用的类,后期静态绑定就是这样使用。如下面程序所示,也就是说调用test()时引用的类是B而不是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
B::who();  // B

示例2给出的是非静态环境下使用static::

后期静态绑定的解析,会一直到取得一个完全解析了的静态调用为止。另外,如果静态调用使用parent::self::将转发调用信息。

<?php
class A {
	public static function foo() {
		static::who();
	}
	public static function who() {
		echo __CLASS__;
	}
}
class B extends A {
	public static function test() {
		A::foo();
		parent::foo();
		self::foo();
	}
	public static function who() {
		echo __CLASS__;
	}
}
class C extends B {
	public static function who() {
		echo __CLASS__;
	}
}
C::test();  // ACC

那么问题来了,结果为什么是这样的呢?

对象和引用

默认情况下,对象时通过引用传递的。但这种说法不完全正确,其实两个对象变量不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

对象序列化

所有PHP里面的值,都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变为原来的值。

序列化一个对象,将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()一个对象,这个对象的类必须已经定义过。在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。

(全文完)

时间: 2024-12-05 02:57:16

类与对象 - PHP手册笔记的相关文章

php中的类、对象学习笔记

面向对象思想 面向对象程序设计(Object-Oriented Programming,OOP)是一种程序设计范型,同事也是一种程序开发方法.它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性.灵活性和可扩展性. 面向过程.面向对象以及函数式编程被人们称为编程语言中的三大范式(实际上,面向过程与面向对象都同属于命令式编程),是三种不同编码和设计风格.其中面向对象的核心思想是对象.封装.可重用性和可扩展性. 面向对象是一种更高级.更抽象的思维方式,面向过程虽然也是一种抽象,但面向

类型 - PHP手册笔记

原文:类型 - PHP手册笔记 类型简介 PHP 支持 8 种原始数据类型. 四种标量类型: boolean(布尔型,不区分大小写) integer(整型) float(浮点型,也称作double) string(字符串) 两种复合类型: array(数组) object(对象) 最后是两种特殊类型: resource(资源) NULL(无类型) 如果想查看某个表达式的值和类型,用var_dump()函数. <?php $a = array(1, 2, array("a", &q

运算符 - PHP手册笔记

原文:运算符 - PHP手册笔记 运算符优先级 每种编程语言都有运算符,运算符要学会灵活使用. 运算符拥有不同的优先级和结合方向. <?php var_dump(1 <= 1 == 1); // true var_dump(true ? 0 : true ? 1 : 2); // 2 $a = 1; var_dump(++$a + $a++); // may print 4 or 5 在需要的时候使用括号,可以增强代码的可读性. 算术运算符 取模运算符的结果和被除数的符号相同. 赋值运算符将原

php的类和对象(翻译:midiguy)

对象 /**********************************************************翻译:midiguy 翻译错误之处还请各位指出E-Mail:midiguy@263.netQQ:5149927***********************************************************/ 面向对象程序设计的来源得自于人们看待电话.汽车这些物体的想法.很多程序设计者在讨论面向对象程序设计的时候喜欢用"包装"或者"

引用 - PHP手册笔记

原文:引用 - PHP手册笔记 引用是什么 PHP中的引用意味着,用不同的变量名访问同一变量内容,类似于Unix的文件名和文件本身(变量名是目录条目,变量内容是文件本身,即用不同的目录条目访问同一文件),可以看做Unix文件系统中的硬链接. 文件系统中的链接有两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link).默认情况下,ln命令产生硬链接.硬连接指通过索引节点来进行的连接.在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个

函数 - PHP手册笔记

原文:函数 - PHP手册笔记 用户自定义函数 函数无需在调用前被定义,除非是有条件定义的. PHP中的所有函数和类都具有全局作用域.PHP不支持函数重载,也不可能取消定义或者重定义已声明的函数. 特意试了下,我的电脑上的PHP递归栈的最大深度为100. 函数参数 PHP支持按值传递参数(默认).通过引用传递参数以及默认参数.也支持可变长度参数列表.PHP允许使用数组和特殊类型NULL作为默认参数.如果希望允许函数修改参数值,必须通过引用传递参数. 注意!当使用默认参数时,任何默认参数必须放在任

流程控制 - PHP手册笔记

原文:流程控制 - PHP手册笔记 脚本由语句构成,语句靠流程控制实现功能,这一节主要介绍了几个关键字的使用. elseif elseif和else if的行为完全一样,如果用冒号来定义if/elseif条件,那就不能用两个单词的else if,否则PHP会产生解析错误. <?php $a = 1; $b = 2; if($a > $b) : echo "$a is greater than $b"; elseif($a == $b) : echo "$a equ

常量 - PHP手册笔记

原文:常量 - PHP手册笔记 常量语法 常量在脚本执行期间其值不能改变.常量大小写敏感,传统上常量标识符总是大写.常量一旦定义就不能被重新定义或取消定义,常量的值只能是标量. 可以用define()函数来定义常量,也可以使用const关键字在类定义之外定义常量.自定义常量不要以双下划线开头,可能会与魔术常量冲突. define('FOO', 'something'); const FOO = 'something'; 使用const关键字定义常量必须处于最顶端的作用区域,因为用此方法是在编译时

命名空间 - PHP手册笔记

原文:命名空间 - PHP手册笔记 概述 命名空间是一种封装事物的方法.在很多地方都可以见到这种抽象概念,比如在操作系统中,目录用来将相关文件分组,对于目录中的文件来说,目录就扮演了命名空间的角色.这个原理应用到程序设计领域就是命名空间的概念.PHP 5.3后的版本开始支持命名空间. 定义命名空间 命名空间通过关键字namespace来声明.任何合法的PHP代码都可以包含在命名空间中,但只有类.函数和常量这三种类型受命名空间的影响. 在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的de