基本模式
class Myclass{
public $var = 'hello world!';
public function sayHello(){
echo $this->var."<br>";
}//this在实例化所在类后才会自动生成。
}
$value = new Myclass();
$value->sayHello();
对象赋值
$v = new Myclass();
$assigned = $v;
$reference = &$v;
$v->var = "hi";
$v = null;
var_dump($v);//null
var_dump($reference);//null
var_dump($assigned);//var => hi;
//
// $objectVar --->+---------+
// |(handle1)----+
// $reference --->+---------+ |
// |
// +---------+ |
// $assignment -->|(handle1)----+
// +---------+ |
// |
// v
// Object(1):foo="bar"
//by php.net/manual/zh/language.oop5.basic.php
新增return new static
static后期静态绑定从被调用的对象开始,逐级向祖先类中找对应方法。
self是写在哪个类中,就调用哪个类的方法
class Myclass{
public function say(){
echo "this is Myclass.";
}
public function selfsay(){
self::say();
}
public function staticsay(){
static::say();
}
}
class Aclass extends Myclass{
public function say(){
echo "this is Aclass.";
}
}
$a = new Aclass();
$a->say();//aclass
$a->selfsay();//myclass
$a->staticsay();//aclass
extends关键字
extends用于类的继承。php不支持多重继承。
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但如果父类方法定义时使用了final则不可覆盖。可用parent::来访问被覆盖的方法或者属性。
覆盖方法时,保持参数的一致。构造函数除外。
$this 与 self 的不同
$this: 当前对象
self: 所在当前类
class Classy {
const STAT = 'S' ; // no dollar sign for constants
static $stat = 'Static' ;
public $publ = 'Public' ;
private $priv = 'Private' ;
protected $prot = 'Protected' ;
function __construct( ){ }
public function showMe( ){
print self::STAT ; // refer to a (static) constant like this=S
print self::$stat ; // static variable=Static
print $this->stat ; // legal, but not what you might think: empty result=
print $this->publ ; // refer to an object variable like this=Public
}
}
属性
类的属性变量是可以初始化的,但是初始化的值必须是常数,及编译时可以得到其值,而不依赖于运行时的信息才能求值。
错误的如:
$var = 'ni '.'hao';
$var = 1+2;
$var = self:myfunction();
$var = $myvar;
正确的如:
$var = myconstant;
$var = array(true,false);
$var = <<<MM
hello world
MM;
常量
定义时不需要用$,总是public,必须是保持定值,不可变,可用变量来动态调用类,但该变量不能为关键字(如self,parent,static)
class Myclass{
const constant = "constant value";
function showConstant(){
echo self::constant;//use self in class
}
}
$name = "Myclass";
echo $name::constant;//the same as Myclass::constant
echo Myclass::constant;//use class_name out of class
$class = new Myclass();
echo $class::constant;//the same as Myclass::constant
父类中的常量,可以被子类重写
abstract class dbObject{
const TABLE_NAME = 'undefined';
public static function GetAll(){
$db = get_called_class();
return "SELECT * FROM '".$db::TABLE_NAME."'";
//also can return static::TABLE_NAME;
}
}
class dbA extends dbObject{
const TABLE_NAME = "a";
}
class dbB extends dbA{
const TABLE_NAME = "b";
}
echo dbA::GetAll();//a
echo dbB::GetAll();//b
自动加载类
__autoload():在试图使用尚未被定义的类时自动调用
function __autoload($class_name){
require_once $class_name.'.php';
}
$obj = new Myclass();
抛出的异常不能被catch捕捉会导致致命错误。
function __autoload($name){
throw new Exception("unable to load $name.");
}
try{
$obj = new Nonloadableclass();
}catch(Exception $e){
echo $e->getMessage();
}
构造函数和析构函数
构造函数:void __construt()
会在每次创建新对象的时候调用此方法,适合做初始化的操作。
如果子类定义了构造函数,要使用父类的构造函数,可以通过parent::__construct()。
如果没有定义,则会调用父类的构造方法。
如果子类的构造方法含参,而new的时候没有传参,并不会调用父类的无参构造函数,而是直接报错。
析构函数:void __destruct()
在某个对象的所有引用都被删除或者当对象被显式销毁时执行。(所有)
要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。
class T{
public $link;
public function setlink(T $t){
$this->link = $t;
}
}
$a = new T();
$b = new T();
$a->setlink($b);
$b->setlink($a);
$a = null;
$b = null;
//their __destruct() will not be called,because the $link still point to them.(memory leak)
访问可见性
class Bar
{
public function test() {
$this->testPrivate();
$this->testPublic();
}
public function testPublic() {
echo "Bar::testPublic\n";
}
private function testPrivate() {
echo "Bar::testPrivate\n";
}
}
class Foo extends Bar
{
public function testPublic() {
echo "Foo::testPublic\n";
}
private function testPrivate() {//并不是重写,private父方法子类不可见,所以不能重写。
echo "Foo::testPrivate\n";
}
}
$myFoo = new foo();
$myFoo->test();
// Bar::testPrivate
// Foo::testPublic
//http://php.net/manual/zh/language.oop5.visibility.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';//private
var_dump($other->foo);
// We can also call the private method:
$other->bar();//private
}
}
$test = new Test('test');
$test->baz(new Test('other'));
注意
在设置类属性时,尽量设置其属性为protected或者private,通过提供public的set,get方法进行属性的访问和设定,同时,在set方法中要注意判断设定值的类型。
static静态
声明的静态方法和属性,可以不通过实例化直接访问。静态方法不能通过实例化访问,但静态方法可以。$this在静态方法中不可用。
抽象类
抽象类不能被实例化
如果类内有抽象方法,则改类应定义为抽象类
定义的抽象方法只是声明了其调用方式(参数),不能定义具体功能实现
继承一个抽象类时,子类必须定义父类的所有抽象方法,访问控制必须宽松或等于父类的。可包含父类中不存在的可选参数。
接口
接口与抽象类的不同在,抽象类可以包含实现的方法,而接口所有的方法都是需要实现的。
接口可以继承自其他的接口。在类实现时就需要把所有的方法都实现。
trait
用来定义复用的代码块。
trait Hello{
$name = 'hello';
function say(){
static $num = 0;
$num +=1;
echo $num;
echo "in trait";
}
}
class Base{
public function say(){
echo 'in base';
}
}
class Child extends Base{
use hello;
public function say(){
echo 'in child';
}
}
//优先级:child——say >trait——say >base——say
冲突:
trait A{
public function a(){}
}
trait B{
public function a(){}
public function b(){}
}
trait A_B{//组合使用
use A,B;
}
class Myclass{
use A,B{
B::a insteadof A;//用B的a方法,不用A的
B::b as c;//B的b方法起别名为c
B::a as protected;//改变权限
}
}
trait包含抽象方法
trait Hello{
public function say(){
echo 'hello';
}
abstract public function get();
}
class Myclass{
use Hello;
public function get(){
echo 'world';
}
}
trait包含静态变量
trait C{
public function add(){
static $c = 0;
$c = $c + 1;
echo $c;
}
}
class C1{
use C;
}
class C2{
use C;
}
$d = new C1();
$d ->add();//1
$e = new C2();
$e ->add();//1
匿名类
匿名类使用外部类的变量则需要根据不同的权限采取不同的方法。
私有变量通过构造函数传递,protected变量通过继承外部类,public变量可以直接使用。
class Outer
{
private $prop = 1;
protected $prop2 = 2;
protected function func1()
{
return 3;
}
public function func2()
{
return new class($this->prop) extends Outer {
private $prop3;
public function __construct($prop)
{
$this->prop3 = $prop;
}
public function func3()
{
return $this->prop2 + $this->prop3 + $this->func1();
}
};
}
}
echo (new Outer)->func2()->func3();//6
重载
当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用(通过魔术方法实现,参数不能为引用传递)。
属性重载:
给不可访问或不存在属性赋值:
public void __set(string $name,mixed $value)
读取不可访问或不存在属性:
public mixed __get(string $name)
对不存在或不可访问属性调用isset或empty:
public bool __isset(string $name)
对不存在或不可访问属性调用unset:
public void __unset(string $name)
方法重载:
调用不存在或不可访问的方法时:
public mixed __call(string $name,array $args)
用静态方式调用一个不可访问或不存在的方法:
public static mixed __callstatic (string $name,array $args)
遍历对象
所有可见属性都可被用于遍历
直接用foreach:
class Myclass{
public $a = 1;
protected $b = 2;
private $c = 3;
public function check(){
foreach($this as $key=>$var){
print "$key => $var";
}
}
}
$class = new Myclass();
foreach($class as $key=>$var){
print "$key => $var";//1
}
$class->check();//1 2 3
让类实现iterator接口,实现其中的方法:
class Myiterator implements Iterator{
//implements the function in Iterator
}
$it = new Myiterator();
foreach($it as $a => $b){
print "$a:$b";
}
通过实现iteratorAggregate来遍历对象,只需实现getiterator()方法,返回一个实现了iterator的类的实例:
class Mycl implements IteratorAggregate{
public function getIterator(){
return new Myiterator();
}
}
$co = new Mycl();
foreach($co as $key => $val){
print "$key => $val";
}
魔术方法:
__toString():类被当作字符串输出时调用
__invoke():类被当作方法使用时调用
__debugInfo():在用var_dump时被调用php5.6
class Myclass{
public function __toString(){
return "this is Myclass.";
}
public function __invoke($name){
echo "$name ";
}
public function __debugInfo(){
return ['myclass' =>1];
}
$cla = new Myclass();
echo $cla;//this is Myclass
$cla("hi");//hi
var_dump(new Myclass());
final关键字:
父类中的方法声明为final,则子类不可覆盖此方法,即使是private的方法。若类声明为final,则不能被继承。
对象复制:
当调用clone 方法时,一般都是浅复制,也就是所有的引用属性(类,数组不是),复制后仍是指向原来变量的引用。所以可用通过__clone()方法进行,复制的设定,从而形成完全拷贝。
区别与 = ,=意味着指向同一个内存区的引用。
//= 的拷贝方式,直接是原来的各个属性的引用
class Myclass{
public $num;
}
$ob1 = new Myclass();
$ob1->num = 2;
$ob2 = $ob1;
$ob2->num = 0;
//ob1:0 ob2:0
//浅拷贝,当属性是对象是也会是引用,但数组不会。
//解决方法:在含有对象作为属性的类中,定义__clone方法,并且在内调用该属性对象的clone 方法。
class myclass{
public $num;
}
$a = new myclass();
$d = 10;
$a->num = &$d;
$b = clone $a;//两者的num都是对d的引用
$b->num = 20;
//a:20 b:20
<?php
class SubObject{
static $instances = 0;
public $instance;
public function __construct() {
$this->instance = ++self::$instances;
}
public function __clone() {
$this->instance = ++self::$instances;
}
}
class MyCloneable
{
public $object1;
public $object2;
function __clone()
{
// 强制复制一份this->object, 否则仍然指向同一个对象
$this->object1 = clone $this->object1;
}
}
$obj = new MyCloneable();
$obj->object1 = new SubObject();//instances=1
$obj->object2 = new SubObject();//instances=2
$obj2 = clone $obj;//instances=3
print("Original Object:\n");
print_r($obj);
print("Cloned Object:\n");
print_r($obj2);
$obj2->object2->instance = 5;
echo $obj->object2->instance;//对object2的引用所以:5
?>
输出:
Original Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 1
)
[object2] => SubObject Object
(
[instance] => 2
)
)
Cloned Object:
MyCloneable Object
(
[object1] => SubObject Object
(
[instance] => 3
)
[object2] => SubObject Object
(
[instance] => 2
)
)
5
对象比较:
当使用比较运算符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。如果属性值是某类的实例,则会递归的去比较。
而如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)。
(这里与变量的比较用法,不同。===不会进行自动的类型转换,需要类型,值都等。)
当用< >进行类对象的比较时,会根据对象的属性进行逐一的比较,不等就直接返回结果。
static后期静态绑定:
后期静态绑定工作原理是存储了在上一个“非转发调用”(non-forwarding call)的类名。当进行静态方法调用时,该类名即为明确指定的那个(通常在 :: 运算符左侧部分);当进行非静态方法调用时,即为该对象所属的类。所谓的“转发调用”(forwarding call)指的是通过以下几种方式进行的静态调用:self::,parent::,static:: 以及 forward_static_call()。可用 get_called_class() 函数来得到被调用的方法所在的类名,static:: 则指出了其范围。
“后期绑定”的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。也可以称之为“静态绑定”,因为它可以用于(但不限于)静态方法的调用。
而self::或者CLASS 对当前类的静态引用,取决于定义当前方法所在的类。
在非静态环境下,所调用的类即为该对象实例所属的类。由于 $this-> 会在同一作用范围内尝试调用私有方法,也会尝试调用私有的属性,但其他权限的不会,而 static:: 则可能给出不同结果。另一个区别是 static:: 只能用于静态属性。
byhttp://php.net/manual/zh/language.oop5.late-static-bindings.php
对象和引用
A reference is not a pointer. However, an object handle IS a pointer.
<?php
class Foo {
private static $used;
private $id;
public function __construct() {
$id = $used++;
}
public function __clone() {
$id = $used++;
}
}
$a = new Foo; // $a is a pointer pointing to Foo object 0
$b = $a; // $b is a pointer pointing to Foo object 0, however, $b is a copy of $a
$c = &$a; // $c and $a are now references of a pointer pointing to Foo object 0
$a = new Foo; // $a and $c are now references of a pointer pointing to Foo object 1, $b is still a pointer pointing to Foo object 0
unset($a); // A reference with reference count 1 is automatically converted back to a value. Now $c is a pointer to Foo object 1
$a = &$b; // $a and $b are now references of a pointer pointing to Foo object 0
$a = NULL; // $a and $b now become a reference to NULL. Foo object 0 can be garbage collected now
unset($b); // $b no longer exists and $a is now NULL
$a = clone $c; // $a is now a pointer to Foo object 2, $c remains a pointer to Foo object 1
unset($c); // Foo object 1 can be garbage collected now.
$c = $a; // $c and $a are pointers pointing to Foo object 2
unset($a); // Foo object 2 is still pointed by $c
$a = &$c; // Foo object 2 has 1 pointers pointing to it only, that pointer has 2 references: $a and $c;
const ABC = TRUE;
if(ABC) {
$a = NULL; // Foo object 2 can be garbage collected now because $a and $c are now a reference to the same NULL value
} else {
unset($a); // Foo object 2 is still pointed to $c
}
$t = new Foo;
$l = $t;
$t = NULL;//$l still point to foo
当对象作为参数传递,传递的是[该对象指针的拷贝]
pointers to objects, not objects themselves, are passed to functions. These pointers are COPIES of the original unless you use "&" in your parameter list to actually pass the originals. Only when you dig into the internals of an object will the originals change(->).