SPL 數組重載

前言

數組重載: 數組重載是指將對象做爲數組使用的過程。具備這種功能的對象也稱爲索引器。php

數組重載是將對象做爲數組使用的一個過程。這意味着容許使用 [ ] 數組語法訪問數據。git

數組重載的學習令我想起一個問題: 爲什麼JavaScript中的一切皆對象?或者簡單的說是爲什麼JavaScript的數組是對象,能夠以arr.lenth的方式調屬性,以arr.append()的方式調方法呢?github

固然對於大多的JavaScript Developer,是不會有這個疑問的。由於JavaScript中沒有數組這種變量類型。而大多數第一門語言是C或者是PHP的開發者,第一次接觸JavaScript的數組時,難免會以爲彆扭。web

若是你也是一個沒法理解JavaScript的數組是對象的PHPer,以爲很難以想象,這篇文章或許能幫助你。好了,先把問題放下。咱們來學習SPL的數組重載,說不定學着學着就明白了呢。後端


1、ArrayAccess Interface

ArrayAccess Interface:PHP提供的索引器接口,屬於預約義接口之一,是數組重載的核心,它提供了掛載到Zend引擎所必須的功能。設計模式

索引器接口:該接口提供了將對象做爲數組訪問的功能,也稱爲數組式訪問接口或數組重載接口。數組

ArrayAccess Interface
實現ArrayAccess接口的類,就是一個 索引器。能夠 使用標準的數組語法讀取和操縱對象中的內容。 注意:ArrayAccess接口提供訪問數組同樣訪問對象能力的接口,但不提供迭代訪問能力。因此不能迭代訪問該索引器,即不能foreach。除非該索引器同時實現了迭代器接口。同理不能對該索引器使用count計數函數,除非其實現計數器接口

ArrayAccess Interface 實現:bash

<?php
//自定義索引器類 實現 ArrayAccess接口
class MyArray implements ArrayAccess{
	protected $_arr =array() ;	//用於存放數據
	
	//設置或替換給定偏移量上的數據
	public function offsetSet($offset,$value){
		$this->_arr[$offset] = $value;
	}
	//返回指定偏移量位置上的數據
	public function offsetGet($offset){
		return $this->_arr[$offset];
	}
	//判斷指定的偏移量是否存在於數組中
	public  function offsetExists($offset){
		return array_key_exists($offset,$this->_arr);
	}
	//刪除指定偏移量位置上的數據
	public function OffsetUnset($offset){
		unset($this->_arr[$offset]);
	}
}

//使用示例
$myArr = new MyArray;
//賦值
$myArr['first']=1;
$myArr['second']=2;
//使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//改變$myArr['second']值
$myArr['second']='two';
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//刪除$myArr['second']
unset($myArr['second']);
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;

複製代碼

運行結果:微信

這裏咱們能夠看到實現ArrayAccess接口後,咱們能夠把一個對象當作數組同樣 賦值使用刪除某個鍵值。 爲何實現該接口,就有這種功能呢?咱們來一探究竟。與往常的套路同樣咱們在每一個方法裏,加上怎麼一句

echo __METHOD__,PHP_EOL;架構

<?php
//自定義索引器類 實現 ArrayAccess接口
class MyArray implements ArrayAccess{
	protected $_arr =array() ;	//用於存放數據
	
	//設置或替換給定偏移量上的數據
	public function offsetSet($offset,$value){
		echo __METHOD__,PHP_EOL;
		$this->_arr[$offset] = $value;
	}
	//返回指定偏移量位置上的數據
	public function offsetGet($offset){
		echo __METHOD__,PHP_EOL;
		return $this->_arr[$offset];
	}
	//判斷指定的偏移量是否存在於數組中
	public  function offsetExists($offset){
		echo __METHOD__,PHP_EOL;
		return array_key_exists($offset,$this->_arr);
	}
	//刪除指定偏移量位置上的數據
	public function OffsetUnset($offset){
		echo __METHOD__,PHP_EOL;
		unset($this->_arr[$offset]);
	}
}

//使用示例
$myArr = new MyArray;
//賦值
$myArr['first']=1;
$myArr['second']=2;
//使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//改變$myArr['second']值
$myArr['second']='two';
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;
//刪除$myArr['second']
unset($myArr['second']);
//再次使用$myArr['second']
echo $myArr['second'].PHP_EOL;
複製代碼

再次運行:

經過分析,咱們能夠知道:當咱們實現 ArrayAccess接口後,咱們以數組的方式給索引器對象賦值時,PHP自動幫咱們調用了 offsetSet方法。 echo 這個索引器對象某個鍵值時,則調用了 offsetGet方法。 isset判斷該鍵值時,則自動調用 offsetExists方法。刪除該鍵值時,則自動調用 offsetUnset方法。

經過上面的代碼,咱們能夠看到實現ArrayAccess接口的一個簡單形式。它爲咱們演示了數組機制是如何運做的

提供ArrayAccess接口的主要緣由是並不是全部的集合都是基於真實的數組。使用ArrayAccess接口的可能會將請求代理到面向服務的架構(SOA)的後端程序,或者其餘形式的離線程序。這容許推遲底層數組的獲取時間,直到它被實際訪問時纔去獲取這些數據。

然而,對於大多數狀況來講,可能會使用數組做爲底層數據的表達形式。而後,你將會向這個類添加處理這一數據的方法。爲實現這一目的,SPL提供了內置的ArrayObject類。


2、Countable Interface

Countable Interface:SPL提供的計數接口,實現該接口的類可被用於**count()**函數計數,並返回數據長度。

Countable Interface

咱們知道,當一個類實現了ArrayAccess接口,就能夠把它當成一個數組同樣操做。可是並不能使用count函數進行計數。若是咱們想讓這個對象更像一個數組,就必須實現計數接口,爲它提供計數能力。

實現Countable接口很是簡單,只須要實現它的抽象方法count方法。由手冊咱們知道,count方法必須返回一個int類型的值,實際上這個值就是Array對象的有效元素數目。

代碼示例:

//自定義數組對象實現Countable接口
class MyArray implements Countable{
	protected $_arr = array(1,2,3); //爲演示方便直接賦值
	public function count(){
		return count($this->_arr);
	}
}
//實例化自定義數組對象
$myArr = new MyArray;
//計數
echo count($myArr);
複製代碼

運行結果:

一樣的,咱們加上一句 echo __METHOD__,PHP_EOL;

<?php
//自定義數組對象實現Countable接口
class MyArray implements Countable{
	protected $_arr = array(1,2,3); //爲演示方便直接賦值
	public function count(){
		echo __METHOD__,PHP_EOL;
		return count($this->_arr);
	}
}
//實例化自定義數組對象
$myArr = new MyArray;
//計數
echo count($myArr);
複製代碼

再次運行:

如咱們意料中的同樣,實現Countable的接口後,當咱們把這個對象傳遞給Count函數時,PHP會幫咱們自動調用對象裏的Count方法。

固然,這個有一個問題: 爲何SPL內置的一個實現Countable接口的類ArrayObject,實現count方法時,方法體裏無實現代碼,也沒有返回值,可是依然可以計數。而咱們若是沒有return,則默認是null。使用Count函數計數時,就會返回零。爲何ArrayObject不用返回呢?

這個很納悶,若是你知道答案,請你告訴我。謝謝! 具體怎麼回事,這裏給出 ArrayObject源碼地址

好了,雖然存在一點疑問,可是不影響。Countable接口的實現就是怎麼簡單。

若是說咱們有一個類同時實現了ArrayAccessCountable,甚至還有Iterator接口,那麼這個類的操做幾乎和數組無差別了。實現ArrayAccess接口擁有數組訪問,實現Countable接口擁有計數的能力,實現Iterator接口擁有迭代訪問的能力。


3、ArrayObject class

ArrayObject : ArrayObject是SPL內置的一個數組對象類,它同時實現了ArrayAccessCountableIteratorAggregate接口。提供了迭代,排序和處理數據很是有用的方法,與數組的使用幾乎是無差異的。其中實現IteratorAggregate接口委託的迭代器是在構造方法傳遞參數中默認了ArrayIterator迭代器。

ArrayObject簡單使用:

$myArr = new ArrayObject ;
//賦值
$myArr['first']=1;
$myArr['second']=7;
$myArr['third']=5;
$myArr['fourth']=9;
//打印
print_r($myArr);
//刪除
unset($myArr['fourth']);
//遍歷
foreach($myArr as $key => $val){
	echo $key.'=>'.$val.PHP_EOL;
}

複製代碼

運行結果:

ArrayObject內部是基於Array實現的,因此它的限制在於只能處理真實的已經徹底填充好的數據。可是它能夠爲應用程序提供有價值的基類。

也就是說,若是你要自定義一個實現ArrayAccess接口的類,實際上不少時候咱們直接繼承ArrayObject就能夠了。

ArrayObject的好處在於,你自定義的類能夠繼承它能夠獲得與數組使用無差別的功能。

好了,到這裏。你想一想實例化一個SPL的ArrayObject對象,獲得了什麼?

一個與數組使用無差別的對象

記住上面這句話,它意味着什麼呢?咱們再想一下爲何JavaScript中數組也是對象呢?SPL提供的ArrayObject不就和JavaScript的數組很類似了嗎?打破思惟的藩籬,咱們換一個視角來看這個問題。

假如PHP一開始就和JavaScript同樣,沒有提供數組這種變量類型,只給咱們提供了ArrayObject類。甚至array()函數返回的不是一個數組,而是一個ArrayObject的實例。咱們不是也能夠經過ArrayObject創造出一個與傳統數組概念上使用無差別的**‘新數組’嗎。而這種新數組的使用與傳統數組無差異,且這種新數組還能調用方法,調用屬性。這不正是JavaScript中數組嗎?那麼這個時候,這種概念的數組,不就是所謂的數組也是對象**嗎?

因此說,JavaScript的數組從這個角度上看,就和PHP中SPL提供的ArrayObject的實例化數組對象同樣。JavaScript沒有像PHP這種純粹的數組,它定義的數組就已是一個對象了。是的,PHP的數組相對於JavaScript是底層了,PHP徹底能夠封裝出JavaScript那種數組對象。

固然,相較於JavaScript,只有PHP的數組不是對象,它就算是純粹的數組嗎?未必吧。PHP的數組與C語言比較呢?C語言的數組是沒法動態擴展的,而PHP是能夠的。從這個角度上來看,PHP中的數組是否相較與C語言也是一種封裝呢?


4、使用ArrayObject內置方法進行排序

實現ArrayAccess接口的索引器,包括ArrayObject的實例雖與數組使用幾乎無差別,但卻沒法使用PHP提供的sortasort,ksort等函數進行排序。

$myArr = new ArrayObject ;
//賦值
$myArr['first']=1;
$myArr['second']=7;
$myArr['third']=5;
$myArr['fourth']=9;
//打印
asort($myArr);
複製代碼

運行結果:

可是若是你這樣寫:

$arr = new ArrayObject;
$arr = [1,2,4,5,8,3];
print_r($arr);
sort($arr);     //排序
print_r($arr);
複製代碼

運行結果:

貌似是可使用的,可是其實是不行的。 由於你寫 $arr = [1,2,4,5,8,3];時,已經爲 $arr 從新賦值一個數組了, $arr的變量類型已經不是 ArrayObject對象,而是數組類型了。數組固然能調用 asort函數咯。

因此,實現ArrayAccess接口的數組對象沒法使用PHP自帶的排序函數進行排序,這點你要注意。 不過,方便的是SPL中ArrayObject類已經擁有asort,ksort等方法它讓咱們能夠實現相似於JavaScript那種操做,調用$arr->asort();給這個數組對象排序。

代碼示例:

$myArr = new ArrayObject ;
//賦值
$myArr['first']=1;
$myArr['second']=7;
$myArr['third']=5;
$myArr['fourth']=9;
//排序
$myArr->asort();
//打印
print_r($myArr);
複製代碼

運行結果:


5、SPL購物車

ArrayObject的最多見應用是在web購物車中,令購物車類繼承自ArrayObject,這樣你實例化的購物車,不只是一個對象並且仍是一個數組。

代碼示例:

<?php
//商品類
class Product{
	public $_name;	//商品名稱
	public $_price;	//商品價格
	public function __construct($name,$price){
		$this->_name = $name;
		$this->_price = $price;
	}
}
//購物車類
class Cart extends ArrayObject{
	//計算購物車商品總價格
	public function sum(){
		$num = 0;
		foreach($this as $product){
			$num+=$product->_price ;
		}
		return PHP_EOL.'購物車總價 : '.$num.PHP_EOL;
	}
}

//實例化商品
$book = new Product('<補刀心法>',57);
$pen = new Product('破鐵牌鋼筆',2);
//實例化購物車
$cart = new Cart;
$cart[] = $book;
$cart[] = $pen;
//查看打印
print_r($cart);
//查看購物車商品總數
echo count($cart);
//計算購物車商品總價格
echo $cart->sum();
複製代碼

運行結果:

從上面代碼能夠看到,經過讓購物車類繼承ArrayObject,咱們能夠以數組的形式添加商品,計算商品個數,購物車總金額。使用這種方法可讓購物車對象的操做變得更加簡單方便。

固然這裏咱們要強調一點,storage屬性是ArrayObject的私有屬性,Cart做爲ArrayObject的子類,是沒法直接訪問和使用的。可是因爲ArrayObject的特性,咱們能夠在Cart中使用$this來訪問父類的storage屬性,同理可使用$this[0]來訪問第一個元素。

ArrayObject 實現了ArrayAccess接口,全部繼承它的類實例出來的對象均可以當作數組通常使用。其實,ArrayObject的構造方法還容許傳入一個數組,它可讓咱們把一個數組當成對象通常使用。

$arr = [1,8,3,9,5];
$arr = new ArrayObject($arr);
$arr->append(2);  //添加一個元素
echo $arr->offsetGet($arr->count()-1);   //獲取最後一個數據 
複製代碼

運行結果:


6、使用對象做爲數組鍵值

有些時候,咱們或許須要將對象的做爲數組鍵值。在PHP中若是直接這麼作,明顯是不行的,會獲得一個警告。

<?php
class MyObject{}
$a = new MyObject;
$arr = array($a=>'test');
複製代碼

運行結果:

幸運地是,SPL爲咱們提供一個獲取對象散列值的函數spl_object_hash()

這個函數能夠爲全部的對象實例建立一個惟一的標識符。即便兩個對象屬於同一類,它的標識符依然是惟一的。

<?php
class MyObject{}
$a = new MyObject;
$b = new MyObject;

echo spl_object_hash($a);
echo PHP_EOL;
echo spl_object_hash($b);
複製代碼

因而咱們能夠經過這個函數間接實現把一個對象看成數組的鍵值

<?php
class MyObject{}
$a = new MyObject;
//使用對象做爲數組鍵值
$arr = array(spl_object_hash($a)=>'test');
//輸出
echo $arr[spl_object_hash($a)];
複製代碼

運行結果:

使用對象做爲鍵值的好處是能夠在一個數組中避免存放同個對象的多個引用。

<?php
class MyObject{}
$a = new MyObject;
$b = $a;

//賦值
$arr[spl_object_hash($a)] = $a;    
$arr[spl_object_hash($b)] = $b;
//打印
print_r($arr);
複製代碼

運行結果:

因爲 $a$b是同一個對象實例, spl_object_hash()返回值相同,因此key值相同。 $arr[spl_object_hash($b)] = $b;並無爲數組建立新元素。保證了數組中相同對象實例只有一個。

這裏咱們還要強調一點:雖然在一個腳本中對一個對象獲取屢次散列值,獲得的結果是同樣的。可是每次運行這個腳本獲取這個對象的散列值是不一樣的。

<?php
class MyObject{}
$a = new MyObject;
$b = $a;


echo spl_object_hash($a);    //第一次獲取
echo PHP_EOL;
echo spl_object_hash($b);   //第二次獲取
複製代碼

運行結果:


上一篇:SPL迭代器接口介紹

感謝閱讀,因爲筆者能力有限,文章不可避免地有失偏頗 後續更新SPL的其餘內容,歡迎你們評論指正


我最近的學習總結:


歡迎你們關注個人微信公衆號 火風鼎
相關文章
相關標籤/搜索