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 提供了方法來取出函數、類和方法中的文檔注釋


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM