php类与对象

自php5起,php具有了完整的对象模型。

类名可以是任何非 PHP 保留字的合法标签。一个合法类名以字母或下划线开头,后面跟着若干字母,数字或下划线

一个类可以包含有属于自己的常量变量(称为“属性”)以及函数(称为“方法”)

当一个方法在类定义内部被调用时,有一个可用的伪变量 $this$this 是一个到主叫对象的引用(通常是该方法所从属的对象,但如果是从第二个对象静态调用时也可能是另一个对象

 静态无$this

class A
{
    function foo()
    {
        if (isset($this)) {
            echo '$this is defined (';
            echo get_class($this);
            echo ")\n";
        } else {
            echo "\$this is not defined.\n";
        }
    }
}
//php7+
class B
{
    function bar()
    {
        A::foo();
    }
}

$a = new A();
$a->foo();//$this is defined (A)
//但还是会运行$this is not defined. 
A::foo();//Deprecated: Non-static method A::foo() should not be called statically

$b = new B();
$b->bar();//$this is not defined. 

B::bar();//$this is not defined.

创建类的实例 new关键字

要创建一个类的实例,必须使用 new 关键字。当创建新对象时该对象总是被赋值,除非该对象定义了构造函数并且在出错时抛出了一个异常。类应在被实例化之前定义(某些情况下则必须这样)

如果在 new 之后跟着的是一个包含有类名的字符串 string,则该类的一个实例被创建。如果该类属于一个命名空间,则必须使用其完整名称

class SimpleClass
{
    
}
$instance = new SimpleClass();

// 也可以这样做:
$className = 'SimpleClass';
$instance = new $className();

 在类定义内部,可以用 new self 和 new parent 创建新对象。???

新实例赋值给其他变量,则会访问同一实例,如果想要建立新实例则用克隆

赋值(内存报错数据,并将变量指向这个内存地址)

class SimpleClass
{
    public $arg;
    public function getArg ()
    {
        echo $this->arg.'</br>';
    }
}

$instance = new SimpleClass();
$instance1 = new SimpleClass();

$assigned = $instance;
$reference  =& $instance;
$instance->arg = 123;
$instance->getArg();//123
$assigned->getArg();//123
$reference->getArg();//123
$instance1->getArg();//
// unset($instance);
$instance = 'abc';
@var_dump($instance);
var_dump($reference);
var_dump($assigned);

 php5.3增加了两种方法新增实例

class Test
{
    var $a;
    static public function getNew()//第二种方法
    {
        return new static;
    }
}

class Child extends Test
{}

$obj1 = new Test();
$obj2 = new $obj1;//第一种方法

echo get_class($obj2);//test
var_dump($obj1 !== $obj2);//true

$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);//true
echo get_class($obj3);//test

$obj4 = Child::getNew();
var_dump($obj4 instanceof Child); //true
var_dump($obj4 instanceof Test);//true
echo get_class($obj4);//child

PHP 5.4.0 起,可以通过一个表达式来访问新创建对象的成员

echo (new DateTime())->format('Y');

类的属性和方法依赖于命名空间,故存在同名的情况

存在同名情况时,为避免匿名函数调用冲突,请用下面的方式调用匿名函数

class Foo
{
    public $bar;
    
    public function __construct() 
    {
        $this->bar = function() {
            return 42;
        };
    }
    
    public function bar ()
    {
        return 45;
    }
    
}

$obj = new Foo();

// as of PHP 5.3.0:
$func = $obj->bar;
echo $func(), PHP_EOL;//42

// alternatively, as of PHP 7.0.0:
echo ($obj->bar)(), PHP_EOL;//42

echo $obj->bar();//45

 继承

类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP不支持多重继承,一个类只能继承一个基类

被继承的方法和属性可以通过用同样的名字重新声明被覆盖,覆盖后,可以通过 parent:: 来访问被覆盖的方法或属性。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。

class Father
{
    public $aar;
    
    public function bar ()
    {
        return 'father';
    }
    
    final public function testFinal ()
    {
        return 'father';
    }
    
}
class Son extends father
{
    public function bar ()
    {
        return 'son';
    }
    
    public function callFatherBar ()
    {
        return parent::bar();
    }
    //Cannot override final method Father::testFinal()
    // public function testFinal()
    // {
        
    // }
    //如果父类定义是设置了final 则父类方法均不能覆盖 final class Father
    //属性无final的概念
}
echo (new Son())->bar();//son
echo (new Son())->callFatherBar();//father

当覆盖方法时,参数必须保持一致否则 PHP 将发出 E_STRICT 级别的错误信息。但构造函数例外,构造函数可在被覆盖时使用不同的参数

获取完全限定名称 php5.5+

::class

namespace NS {
    class ClassName 
    {
    }
    
    echo ClassName::class;//NS\ClassName
}

变量

类的变量成员叫做“属性”,或者叫“字段”、“特征”,在本文档统一称为“属性”

属性声明是由关键字 publicprotected 或者 private 开头,然后跟一个普通的变量声明来组成。属性中的变量可以初始化,但是初始化的值必须是常数,不能时表达式

为了兼容还可以用var 来声明属性,后面还可以跟上三p,如没有跟,默认为public

在类的方法内获取属性

$this->property//非静态

self::$property//静态

class SimpleClass
{
    // 正确的属性声明
   public $var6 = myConstant;
   public $var7 = array(true, false);

   //在 PHP 5.3.0 及之后,下面的声明也正确,//nowdoc方式相当于单引号
   public $var8 = <<<'EOD'
hello world
EOD;
}

 常量

常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。

php5.6+ 可为表达式

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

自 PHP 5.3.0 起,可以用一个变量来动态调用类。但该变量的值不能为关键字(如 selfparent 或 static

class MyClass
{
    const constant = 'constant value';

    function showConstant() {
        echo  self::constant . "\n";
    }
}

echo MyClass::constant . "\n";

$classname = "MyClass";
echo $classname::constant . "\n"; // 自 5.3.0 起,不能为关键字(如 self,parent 或 static)
$class = new MyClass();
$class->showConstant();

echo $class::constant."\n"; // 自 PHP 5.3.0 起

静态数据:不包含任何变量的数据

类的变量和常量申明为静态数据

和 heredoc 不同,nowdoc 可以用在任何静态数据中。

自动加载

为了解决新建每个类时,需要手动inlcude问题,使用spl_autoload_register() 函数可以注册任意数量的自动加载器。

通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类??

__autoload()也有类似功能,后续版本要弃用,尽量不使用

 spl_autoload_register ([ callable $autoload_function [, bool $throw = true //注册失败是否抛出异常[, bool $prepend = false ]//是否放在函数队列最前]] ) : bool

多次注册,函数内有个自动序列,prepend控制是否放在队列最前

//TestAutoLoad.inc.php

class TestAutoLoad
{
    static $var = 'abc';
    
    function __construct ()
    {
        echo self::$var;
    }
}

//index.php
function __autoload ($className)//__autoload() is deprecated, use spl_autoload_register() instead
{
    include "./$className.inc.php";
}

$obj = new TestAutoload();

function myAutoload ($className)
{
    include "./$className.inc.php";
}

function myAutoload1 ($className)
{
    include "./$className.inc.php";
}

spl_autoload_register('myAutoload', true);//spl_autoload_register自动加入到__autoload函数队列

spl_autoload_register('myAutoload1', true);

$obj = new TestAutoload();

$obj = new TestAutoload();//并没有抛出多次include的异常,猜测函数内部判断,只include了一次

传入数组

class ClassAutoloader {
    public function __construct() {
        spl_autoload_register(array($this, 'loader'));
    }
    private function loader($className) {
        echo 'Trying to load ', $className, ' via ', __METHOD__, "()\n";
        include $className . '.php';
    }
}

$autoloader = new ClassAutoloader();

$obj = new Class1();
$obj = new Class2();

自5.3起,可以使用匿名函数

// 或者,自 PHP 5.3.0 起可以使用一个匿名函数
spl_autoload_register(function ($class) {
    include 'classes/' . $class . '.class.php';
});

自5.3起,亦可以使用类中静态方法

namespace Foobar;

class Foo {
    static public function test($name) {
        print '[['. $name .']]';
    }
}

spl_autoload_register(__NAMESPACE__ .'\Foo::test'); // 自 PHP 5.3.0 起

new InexistentClass;//[[Foobar\InexistentClass]]

接口亦可以

//ITest.php

interface ITest
{
    
}


//index.php
spl_autoload_register(function ($name) {
    var_dump($name);
    include "$name.php";
});

class Foo implements ITest {
}

 异常处理

spl_autoload_register(function ($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";
}
spl_autoload_register(function ($name) {
    echo "Want to load $name.\n";
    throw new MissingException("Unable to load $name.");// Want to load MissingException. Uncaught Error: Class 'MissingException' not found 
});

try {
    $obj = new NonLoadableClass();//Want to load NonLoadableClass.
} catch (Exception $e) {
    echo $e->getMessage(), "\n";
}

 构造函数及构析函数r

PHP 5 允行开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

__construct($arg...):void

如果子类中定义了构造函数,不会隐式的调用父类的构造函数,需要用parent::__construct()调用,如果子类没有构造函数,父类的构造函数将和普通函数一样被子类继承。

class Father
{
     function __construct ()//不可为private
    {
        echo 'Father';
    }
}

class Child extends Father
{    //当Child无构造函数时,自动调用父类的 Father
    function __construct ()
    {
        echo 'Child';
    }
    
    function callFatherConstruct ()
    {
        parent::__construct();
    }
}

$obj = new Child;//Child 

$obj->callFatherConstruct();//Father

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

namespace Foo;
class Bar {
    public function Bar() {
        // treated as constructor in PHP 5.3.0-5.3.2
        // treated as regular method as of PHP 5.3.3
    }
}

析构函数不是构析函数。。手动狗头 

PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行

class MyDestructableClass {
   function __construct() {
       print "In constructor\n";
       $this->name = "MyDestructableClass";
   }

   function __destruct() {
       print "Destroying " . $this->name . "\n";
   }
}

$obj = new MyDestructableClass(); //In constructor

unset($obj); //Destroying MyDestructableClass

$obj = null;//Destroying MyDestructableClass

echo 6666; //6666

//Destroying MyDestructableClass 程序结束后,释放所有变量

exit 和die也生效

析构函数在脚本关闭时调用,此时所有的 HTTP 头信息已经发出!!脚本关闭时的工作目录有可能和在 SAPI(如 apache)中时不同??!!

析构函数子类的策略与构造函数相同

试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误!!

重点:访问控制(可见性)

三个关键字控制 public 公有;protected 受保护;private 私有的

public:可以在任何地方被访问

protected:可以被自身或其子类和父类访问

private:只能被其定义所在的类访问

属性的访问控制

类属性必须定义为三种控制之一,如为var,则视为公有

class Father
{
    public $public = "fatherPublic\n";
    protected $protected = "fatherProtected\n";
    private $private = "fatherPrivate\n";
    private $private1 = "fatherPrivate1\n";
    
    public function showAttribution()
    {
        // var_dump($this);
        echo $this->public;
        echo $this->protected;
        echo $this->private;
        echo $this->private1;
    }
}

class Me extends Father
{    
    // 可以对 public 和 protected 进行重定义,但 private 而不能,如下var_dump将保留Me:private,不知如何访问到这个属性
    public $public = "mePublic\n";
    protected $protected = "meProtected\n";
    private $private = "mePrivate\n";
    //当重写showAttribution 方法时,$this->private访问到了Me:private
    public function showAttribution()
    {
        // var_dump($this);结果与继承的showAttribution一致
        echo $this->public;
        echo $this->protected;
        echo $this->private;
        echo $this->private1;
    }
    
}
//var_dump()
/*object(Me)#1 (4) { 
    ["public"]=> string(9) "mePublic " 
    ["protected":protected]=> string(12) "meProtected " 
    ["private":"Me":private]=> string(10) "mePrivate " 
    ["private1":"Father":private]=> string(14) "fatherPrivate " 
    ["private":"Father":private]=> string(14) "fatherPrivate " 
}
*/
$me = new Me;
echo $me->public;//mePublic
// echo $me->protected;//Uncaught Error: Cannot access protected property 
//echo $me->private;//Uncaught Error: Cannot access private property
$me->showAttribution();//mePublic meProtected mePrivate 

/*结论    
可以对 public 和 protected 和private 进行重定义,public 和 protected直接覆盖
而继承而来的方法访问private的是父类的,自己的方法访问private是重写后的
*/

方法的访问控制

类中的方法可以被定义为公有,私有或受保护。如果没有设置这些关键字,则该方法默认为公有

class Father
{
    public function __construct () {}
    
    public function myPublic () 
    {
        echo "fatherPublic</br>";
    }
    
    protected function myProtected () 
    {
        echo "fatherProtected</br>";
    }
    
    private function myPrivate () 
    {
        echo "fatherPrivate</br>";
    }
    
    function foo ()
    {
        $this->myPublic();
        $this->myProtected();
        $this->myPrivate();
    }
    
}

$father = new Father();
$father->myPublic();//通过
//$father->myProtected();//Uncaught Error: Call to protected method
//$father->myPrivate();//Uncaught Error: Call to private method
$father->foo();//通过


class Me extends Father
{    
    function foo1()
    {
        $this->myPublic();
        $this->myProtected();
        $this->myPrivate();
    }
    public function myPublic () 
    {
        echo "mePublic</br>";
    }
    
    protected function myProtected () 
    {
        echo "meProtected</br>";
    }
    private function myPrivate () 
    {
        echo "mePrivate</br>";
    }
}

$me = new Me();
$me->foo1();//mePublic meProtected mePrivate
$me->foo();//mePublic meProtected fatherPrivate

其他对象的访问控制

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

这是由于在这些对象的内部具体实现的细节都是已知的。

class A
{
    private $b;
    
    public function __construct($b)
    {
        $this->b = $b;
    }
    
    private function myPrivate ()
    {
        echo 'private';
    }
    
    public function showPrivate (A $c)
    {    //改变私有属性
        $c->b = 'hello';
        var_dump($c->b);
        //调用私有方法
        $c->myPrivate();
    }
}

$test = new A('test');//hello

$test->showPrivate(new A('other'));//private

 继承

继承将会影响到类与类,对象与对象之间的关系。

当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能。

继承对于功能的设计和抽象是非常有用的,而且对于类似的对象增加新功能就无须重新再写这些公用的功能。

除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口

范围解释操作符 ::

用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法

 

class A
{
    public const MYCONST = 123;
    
    public static $myStatic = 'A static var';
    
    public static  $a = 666;
    
    protected function myFunc ()
    {
        echo 'A::myFunc';
    }
    
    public static function b ()
    {
        echo 'fun b';
    }
}

//访问常量
echo A::MYCONST;
echo 'A'::MYCONST;
$a = 'A';
echo $a::MYCONST;

//访问静态成员
echo $a::$a;//666
echo $a::b();

//self parent  static 三个特殊的关键字用于在类定义的内部对其属性或方法进行访问
class B extends A
{
    public static $myStatic = 'my static var';
    
    public static function show ()
    {
        echo parent::MYCONST;
        echo self::$myStatic;//my static var
        echo static::$myStatic;//效果等同上
        echo parent::$myStatic;//A static var
    }
    //覆盖了父类的定义
    public function myFunc ()
    {
        //依旧可以调用父类中被覆盖的方法
        echo 'B::myFunc';
        parent::myFunc();
    }
}

$b = 'B';
$b::show();
//当一个子类覆盖其父类中的方法时,PHP 不会调用父类中已被覆盖的方法。是否调用父类的方法取决于子类。这种机制也作用于构造函数和析构函数,重载以及魔术方法
$b = new B();
$b->myFunc();//B::myFunc A::myFunc

 static静态关键字

作用一:申明

声明类属性或方法为静态,就可以不实例化类而直接访问

静态属性不能通过一个类已实例化的对象来访问(但静态方法可以),静态属性不可以由对象通过 -> 操作符来访问。

class A
{
    public static $myStatic = 'A static var';

    public static function myFunc ()
    {
        echo 'A::myFunc';
    }
}

$a = new A();
echo $a->myStatic;// Accessing static property A::$myStatic as non static
$a->myFunc();//有效,A::myFunc

由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用

class A
{
    public $myStatic = 'A static var';

    public static function myFunc ()
    {
        echo $this->myStatic;//Uncaught Error: Using $this when not in object context 
    }
}

$a = new A();
$a->myFunc();//Uncaught Error: Using $this when not in object context 

就像其它所有的 PHP 静态变量一样,静态属性只能被初始化为文字或常量,不能使用表达式。所以可以把静态属性初始化为整数或数组,但不能初始化为另一个变量或函数返回值,也不能指向一个对象。

抽象类 php5+

定义为抽象的类不能被实例化

任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的;

被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现;

继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;

这些方法的访问控制必须和父类中一样(或者更为宽松)!!

此外方法的调用方式必须匹配,即类型和所需参数数量必须一致,子类可多设置有默认值的参数

abstract class Foo
{
    public static $my_static = 'foo';
    //强制要求子类定义这类方法
    abstract protected function protectedFunc ($arg);
    abstract public function publicFunc (...$arg);//参数数量要一致
    //普通方法(非抽象方法)
    public function nomalFunc ($arg)
    {
        echo 'nomalFunc';
    }
}

class Foo1 extends Foo
{    // 我们的子类可以定义父类签名中不存在的可选参数,及默认值
    public function protectedFunc ($arg, $arg1 = 123, $arg2 = 456)//大于等于protected的控制方式
    {
        echo "protectedFunc\n$arg\n$arg1\n$arg2";
    }
    public function  publicFunc (...$arg)////参数数量要一致
    {    
        echo 'publicFunc '.$arg[0];
    }
}

$foo1 = new Foo1();
$foo1->publicFunc('test', 'test1');
$foo1->protectedFunc('test', 'test1');

接口

 使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。

接口中定义的所有方法都必须是公有,其中定义所有的方法都是空的

实现(implements)

要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称

类要实现接口,必须使用和接口中所定义的方法完全一致的方式。否则会导致致命错误

接口也可以继承,通过使用 extends 操作符

//经典例子
interface Iter
{
    public function setValue ($val);
    
    public function getValue ();
    
} 
//实现接口
class A implements Iter
{
    public $val;
    public function setValue ($val)
    {
        $this->val = $val;
    }
    public function getValue ()
    {
        echo $this->val;
    }
}

$a = new A();
$a->setValue('xxx');
$a->getValue();//xxx

实现多个接口

//实现多个接口 
interface Iter
{
    public function setValue ($val);
    
    public function getValue ();
    
} 

interface Ext
{
    public function varDumpValue ();
}
//实现接口
class A implements Iter, Ext
{
    public $val;
    public function setValue ($val)
    {
        $this->val = $val;
    }
    public function getValue ()
    {
        echo $this->val;
    }
    public function varDumpValue ()
    {
        var_dump($this->val);
    }
}

$a = new A();
$a->setValue('xxx');
$a->getValue();//xxx
$a->varDumpValue();

可扩充接口,继承

//可扩充接口 
interface Iter
{
    public function setValue ($val);
    
    public function getValue ();
    
} 

interface Ext extends Iter
{
    public function varDumpValue ();
}
//实现接口
class A implements Ext
{
    public $val;
    public function setValue ($val)
    {
        $this->val = $val;
    }
    public function getValue ()
    {
        echo $this->val;
    }
    public function varDumpValue ()
    {
        var_dump($this->val);
    }
}

$a = new A();
$a->setValue('xxx');
$a->getValue();//xxx
$a->varDumpValue();

参数类型如有限定,也必须一致,string对应string
// public function baz(Baz $baz);//interface
// public function baz(Foo $foo);//class

接口中也可以定义常量,但是不能被子类或子接口所覆盖,也必须是公有的

interface A
{
    const B = '567';
}
echo A::B;
class B implements A
{
    const B = '789';//不能被子类或子接口所覆盖
}
echo B::B;

Trait

自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait,实现类的水平多次继承

用法类内用use

优先级 当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法,由于优先级而trait内方法生效,此方法必须public

trait A
{
    private function funcA ()
    {
        echo 'Trait';
    }
}

class B
{
    public function funcA ()
    {
        echo 'classB';
    }
}

class C extends B
{
    public function funcA ()
    {
        echo 'classC';
    }
    use A;//classC
}


$c = new C();
$c->funcA();

trait 和 类 的名字不能重复

多此trait,trait间方法不能重名

trait A
{
    public function funcA ()
    {
        echo 'traitA';
    }
}

trait B
{
    public function funcB ()
    {
        echo 'traitB';
    }
}

class C
{
    public function funcA ()
    {
        echo 'classC';
    }
}

class D extends C
{
    use A, B;
}


$d = new D();
$d->funcA();//traitA
$d->funcB();//traitB

如有重名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个

1、排除冲突,insteadOf

2、保留冲突,as

trait A
{
    public function funcA ()
    {
        echo 'traitA';
    }
}

trait B
{
    public function funcA ()
    {
        echo 'traitB';
    }
}

class C
{
    use A, B
    {
        A::funcA insteadOf B;
    }
}

class D
{
    use A, B
    {
        A::funcA insteadOf B;
        B::funcA as BfuncA;
    }
}

$c = new C();
$c->funcA();//traitA

$d = new D();
$d->funcA();//traitA
$d->BfuncA();//traitB

 as 还可以修改方法的控制方式

trait A
{
    public function funcA ()
    {
        echo 'traitA';
    }
}

class C
{
    use A
    {
        A::funcA as protected;
    }
}

class D
{
    use A
    {
        funcA as protected AfuncA;
    }
}

$c = new C();
//$c->funcA();//错误,funcA已经时protected

$d = new D();
$d->funcA();//traitA
$d->AfuncA();//报错,别名是protected,但这并不影响原名的使用

嵌套trait

trait A
{
    public function funcA ()
    {
        echo 'traitA';
    }
}

trait B
{
    public function funcB ()
    {
        echo 'traitB';
    }
}

trait C
{
    use A, B;
}
class D
{
    use C;
}

$d = new D();
$d->funcA();//traitA
$d->funcB();//traitB

为了对使用的类施加强制要求,trait 支持抽象方法的使用

trait A
{
    public function funcA ()
    {
        echo 'traitA';
    }
    
    abstract public function funcB ();
}

class D
{
    use A;
    public function funcB ()
    {
        echo 'funcB';
    }
}

$d = new D();
$d->funcA();//traitA
$d->funcB();//funcB

trait类方法的静态变量和方法及属性的用法

trait Counter {
    //定义属性
    public static $a = 123;
    public function inc() {
        //静态变量
        static $c = 0;
        $c = $c + 1;
        echo "$c\n";
    }
    //静态方法
    public static function funcA ()
    {
        echo 'static func';
    }
}
//Trait 定义了一个属性后,类就不能定义同样名称的属性
//属性是兼容的(同样的访问可见度、初始默认值) php7+ 不报错
//不兼容致命错误
class C1 {
    public static $a = 123;//必须完全相同,有何意义?
    use Counter;
    
}

class C2 {
    use Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
$q = new C2(); $q->inc(); // echo 2 //静态变量

$o::funcA();//static func
Counter::funcA();//static func
C1::funcA();//static func

 php7开始支持匿名类

匿名类很有用,可以创建一次性的简单对象。考虑消耗吗

//php7 -
class B
{
    public function c ()
    {
        echo 666;
    }
}

function a (B $b) {
    $b->c();
}

a(new B);//666

//php7+
function a ($b) {
    $b->c();
}

a(new class {
    public function c ()
    {
        echo 777;
    }
});//777

匿名可继承可实现

class SomeClass {}
interface SomeInterface {}
trait SomeTrait {}
var_dump(new class (10) extends SomeClass implements SomeInterface {
    private $num;
    public function __construct ($num)
    {
        $this->num = $num;
    }    
    use SomeTrait;
});
//object(class@anonymous)#1 (1) { ["num":"class@anonymous":private]=> int(10) }

匿名类被嵌套进普通 Class 后,不能访问这个外部类(Outer class)的 private(私有)、protected(受保护)方法或者属性,作用域不在这个普通类内,是为独立作用域

为了解决这个矛盾:

使用extends 获得protected的属性和方法;

使用构造器(即构造函数)传进private的属性和方法。

class Outer
{
    private $prop1 = 1;
    protected $prop2 =2;
    
    protected function func1 ()
    {
        return 3;
    }
    
    public function func2 ()
    {
        return new class ($this->prop1) 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();

声明的同一个匿名类,所创建的对象都是这个类的实例,有一个相同的隐式名字

function a ()
{
    return new class {};
}
//匿名类的名称是通过引擎赋予的,不用管这个名字,无实际意义
if (get_class(a()) === get_class(a())){
    echo get_class(a());//class@anonymousE:\mc\webroot\index.php000002767DE00029
}

 类的重载

指动态地创建类属性和方法。我们是通过魔术方法(magic methods)来实现的。

当调用当前环境下未定义或不可见(访问控制)的类属性或方法时,重载方法会被调用。

所有的重载方法都必须被声明为 public

目的是为了调用不用访问的属性和方法吗?

!!这些魔术方法的参数都不能通过引用传递

!!PHP中的重载与其它绝大多数面向对象语言不同。传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

属性的魔术方法

class PropertyTest
{
    //被重载的数据保持在此
    private $data = [];
    
    //重载不能用在可以访问的属性
    public $declared = 1;
    
    //只有从类外部访问这个属性时,重载才会发生
    private $hidden = 2;
    
    //在给不可访问属性赋值时,__set() 会被调用
    public function __set ($name, $value)
    {
        echo "Setting '$name' to '$value'\n";
        $this->data[$name] = $value;
    }
    
    //读取不可访问属性的值时,__get() 会被调用
    public function __get ($name)
    {
        echo "Getting '$name'\n";
        if(array_key_exists($name, $this->data)){
            return $this->data[$name];
        }
        //如果不存在此key,报错
        //debug_backtrace()可返回当前环境,由此组装报错string
        $trace = debug_backtrace();
        //产生一个用户级别的error/warning/notice信息
        trigger_error('Undefined property via __get(): ' . $name .
            ' in ' . $trace[0]['file'] .
            ' on line ' . $trace[0]['line'],
            E_USER_NOTICE);
        return null;    
    }
    
    //php5.1+可用isset和unset
    public function __isset ($name)
    {
        echo "Is '$name' set?\n";
        return isset($this->data[$name]);
    }
    
    public function __unset ($name)
    {
        echo "Unsetting '$name'\n";
        unset($this->data[$name]);
    }
    //只有从类外部访问这个属性时,重载才会发生
    public function getHidden ()
    {
        return $this->hidden;
    }
}

$test = new PropertyTest ();
//Undefined property via __get(): a in E:\mc\webroot\index.php on line 40
echo $test->a;//Getting 'a'
$test->a = 123;//Setting 'a' to '123'
//Getting 'a'
echo $test->a;//123
echo $test->getHidden();//2 不会发生重载
echo $test->hidden;//Getting 'hidden' 发生重载 

方法重载

class MethodTest
{
    public function __call ($name,  $arguments)
    {
        // 注意: $name 的值区分大小写
        echo "Calling object method '$name' "
            . implode(', ', $arguments). "\n";
    }
    
    //php5.3+
    public static function __callStatic ($name, $arguments)
    {
        // 注意: $name 的值区分大小写
        echo "Calling static method '$name' "
             . implode(', ', $arguments). "\n";
    }
    
}

$obj = new MethodTest;
$obj->runTest('1234');//Call$ing object method 'runTest' 1234
MethodTest::runTest('5678');//Calling static method 'runTest' 5678

 遍历对象

默认情况下,所有可见属性都将被用于遍历

class A
{
    public $var1 = 1;
    public $var2 = 2;
    public $var3 = 3;
    
    protected $var4 = 4;
    private $var5 = 5;
    
    public function goThrough ()
    {
        foreach($this as $key=>$val)
        {
            print "$key => $val</br>";
        }
    }
    
}

$a = new A;
foreach($a as $key => $value) {
    print "$kemy => $value</br>";
}//123
echo '<hr>';
$a->goThrough();//12345

更进一步,可以实现 Iterator 接口。可以让对象自行决定如何遍历以及每次遍历时那些值可用??

迭代器

魔术方法

命令类方法时,不要与魔术方法名字冲突,除非是想用它

__开头,所以命名类方法时,不要这样命名

补充一些魔术方法的用法

__sleep()用于serialize()函数zhun调用时,序列化操作前调用此魔术方法,此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称和数组;

自定义序列化:方法常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用

__wakeup()与__sleep刚好相反,与unserialize()对应,用于预先准备对象需要的资源

//一个数据库连接的类
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();
    }
    
    public function connect ()
    {
        $this->link = mysql_connect($this->server, $this->userName, $this->password);
        mysql_select_db($this->db, $this->link);
    }
    
    public function __sleep ()
    {
        return ['server',  'userName', 'password', 'db'];//仅保持有用信息
    }
    
    public function __wakeup ()
    {
        $this->connect();//sleep恢复之后所需要的操作
    }
    
}

__toString()方法用于一个类被当成字符串时应怎样回应

不能在__toString中抛出异常,php5.2起,可用于任何字符串环境

//类当成string使用时 如何反应 toString
class Test
{
    public $foo;
    
    public function __construct ($foo)
    {
        $this->foo = $foo;
    }
    
    public function __toString ()
    {
        return $this->foo;
    }
    
}

echo new Test('waiting for');//waiting for

__invoke方法php5.3+

当尝试以调用函数方式调用一个对象时,此方法会被自动调用

class CallableClass
{
    function __invoke ($x)
    {
        var_dump($x);
    }

}
$obj = new CallableClass;
$obj(5);//int(5) 
var_dump(is_callable($obj));//bool(true)

 __set_state()

自 PHP 5.1.0 起当调用 var_export() 导出类时,此静态 方法会被调用

class A
{
    public $var1;
    public $var2;
    private $var4;
    public static function __set_state ($arr)
    {
        //下面全删了也一样的效果,有啥用??
        $obj = new A;
        $obj->var1 = $arr['var1'];
        $obj->var2 = $arr['var2'];
        $obj->var4 = 55;
        return $obj;
    }
}

$a = new A;
$a->var1 = 1;
$a->var2 = 2;

var_export($a);//A::__set_state(array( 'var1' => 1, 'var2' => 2, 'var4' => NULL, ))

eval('$b = '.var_export($a, true).';');

var_dump($b);
/*object(A)#2 (3) { ["var1"]=> int(1) ["var2"]=> int(2) ["var4":"A":private]=> int(55) }*/

__debuginfo():array php5.6+

var_dump会把类的所有属性打出来,不管是什么访问控制

如果设置了debuginfo,则会先调用这个魔术方法,该显示什么,不该显示什么,该如何显示,由此控制

class A
{
    public $var4;
    public function __debugInfo ()
    {
        return [
            'test' => $this->var4**2
        ];
    }
}

$a = new A;
$a->var4 = 42;

var_dump($a);//object(A)#1 (1) { ["test"]=> int(1764) }

final 关键字 php5.6+

如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。

我走到头了,不要想着继承我的(财产)

final class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }
   
   // 这里无论你是否将方法声明为final,都没有关系
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
}
// 产生 Fatal error: Class ChildClass may not inherit from final class (BaseClass)

对象的复制

对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。对象中的 __clone() 方法不能被直接调用

__clone()也是一种魔术方法

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();//1
$obj->object2 = new SubObject();//2

$b = clone $obj->object2;//由clone方法加上1,3

echo $b::$instances;//3

echo SubObject::$instances;//3

$obj2 = clone $obj;//调用clone方法,再调用SubObject clone方法,加上1,4
print_r($obj);//1 2
echo '</br>';
print_r($obj2);//

 对象比较

当使用比较运算符(==)比较两个对象变量时,比较的原则是:如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等

如果使用全等运算符(===),这两个对象变量一定要指向某个类的同一个实例(即同一个对象)

class A
{
    public $a;
    private $b;
    
    function __construct ($a, $b)
    {
        $this->a = $a;
        $this->b = $b;
    }
}

function compareObj ($a, $b)
{
    if($a == $b)
    {
        if($a === $b)
            echo "===\n";
        else
            echo "==\n";
        return;
    }
    echo "<>\n";
}

$a = new A(1,2);
$b = new A(1,2);

compareObj($a, $b);//==

$a = new A(1,2);
$b = $a;

compareObj($a, $b);//===

$a = new A(1,2);
$b = &$a;

compareObj($a, $b);//===

$a = new A(1,2);
$b = clone $a;

compareObj($a, $b);//===

//PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。
$a->a = 3;
echo $b->a;//3    !!!

类型约束

如果一个类或接口指定了类型约束,则其所有的子类或实现也都如此

class A
{
    //参数必须是B类实例
    public function test (B $b)
    {
        echo $b->var;
    }
    
    //参数必须是数组
    public function testArr (array $arr)
    {
        var_dump($arr);
    }
    
    //第一个参数必须为递归类型, Traversable:检测一个类是否可以使用 foreach 进行遍历的接口
    public function testInterface (Traversable $iterator)
    {
        echo get_class($iterator);
    }
    
    //回调类型
    public function testCallable (callable $callback, $data)
    {
        call_user_func($callback, $data);
    }
    
    public function testString (string $str)
    {
        echo $str;
    }

}
class B
{
    public $var = 'xffg';
}

$a = new A;
$a->test(new B);//xffg
$a->testArr([1,2]);
//ArrayObject This class allows objects to work as arrays
//__construc(array())
$a->testInterface(new ArrayObject([]));//ArrayObject
$a->testCallable('var_dump', '666');//string(3) "666"
$a->testString('xxx');//xxx

后期静态绑定

 自 PHP 5.3.0 起,PHP 增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类

如何使用

class A
{
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();//self的限制 A
        static::who();//后期静态绑定的用法 B
    }

}
class B extends A
{
    public static function who() {
        echo __CLASS__;
    }
}

B::test();//call A::test -> A::who or B::who

 继承覆盖方法时,以更改访问控制,不可更改是否静态

方法为非静态时

class A
{
    private function who() {
        echo __CLASS__;
    }
    public function test() {
        $this->who();
        static::who();
    }

}
class B extends A
{
    public function who() {
        echo __CLASS__;
    }
}

class C extends A
{
}



$b = new B();
$b->test();//A ($this->who()) B (static::who())

$c = new C();
$c->test();//AA

多重转发,绑定规则:

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

class A
{
    private static function who() {
        echo __CLASS__."\n";
    }
    public static function foo() {
        static::who();
    }

}
class B extends A
{
    public static function test() {
        A::foo();//A
        parent::foo();//C
        self::foo();//C
    }
    
    public static function who () 
    {
        echo __CLASS__."\n";
    }
}

class C extends B
{
    public static function who() {
        echo __CLASS__."\n";
    }
}

C::test();//A C C

 对象和引用

在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。

PHP 的引用是别名,就是两个不同的变量名字指向相同的内容。在 PHP 5,一个对象变量已经不再保存整个对象的值。只是保存一个标识符来访问真正的对象内容。 当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容

 

class A {
    public $foo = 1;
}  

$a = new A;
$b = $a;     // $a ,$b都是同一个标识符的拷贝
             // ($a) = ($b) = <id>
$b->foo = 2;
echo $a->foo."\n";//2

$b = null;
echo $a->foo."\n";//2

$c = new A;
$d = &$c;    // $c ,$d是引用
             // ($c,$d) = <id>
$d->foo = 2;
echo $c->foo."\n";//2

$d = null;//$c也为null 此时应用unset
echo $c->foo."\n";//Trying to get property 'foo' of non-object 

$e = new A;
function foo($obj) {
    // ($obj) = ($e) = <id>
    $obj->foo = 2;
}

foo($e);
echo $e->foo."\n";//2

对象的序列化

// 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);
  // 把变量$s保存起来以便文件page2.php能够读到
  file_put_contents('store', $s);

// page2.php:
  
  // 要正确了解序列化,必须包含下面一个文件
  include("classa.inc");

  $s = file_get_contents('store');
  $a = unserialize($s);

  // 现在可以使用对象$a里面的函数 show_one()
  $a->show_one();

用于传递实例从一个页面到另一个页面

当一个应用程序使用函数session_register()来保存对象到会话中时,在每个页面结束的时候这些对象都会自动序列化,而在每个页面开始的时候又自动解序列化。 所以一旦对象被保存在会话中,整个应用程序的页面都能使用这些对象。但是,session_register()在php5.4.0之后被移除了

当反序列化之后,如果此类没有定义,还是不能用的

反射

PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释

posted @ 2019-07-13 16:34  ValleyUp  阅读(1505)  评论(0编辑  收藏  举报