PHP Conference Japan 2024

Overloading

在 PHP 中,Overloading 提供動態「建立」屬性和方法的方式。這些動態實體會透過可在類別中建立以處理各種動作類型的魔術方法進行處理。

當與未宣告或在目前範圍中不可的屬性或方法互動時,會呼叫 Overloading 方法。本節的其餘部分將使用「無法存取的屬性」和「無法存取的方法」等術語來指稱宣告和可見性的組合。

所有 Overloading 方法都必須定義為 public

注意:

這些魔術方法的任何引數都不能以傳參考方式傳遞

注意:

PHP 對「Overloading」的解釋與大多數物件導向語言不同。傳統上,Overloading 提供使用相同名稱但具有不同數量和類型的引數的多種方法的能力。

屬性 Overloading

public __set(string $name, mixed $value): void
public __get(string $name): mixed
public __isset(string $name): bool
public __unset(string $name): void

當將資料寫入無法存取(受保護或私有)或不存在的屬性時,會執行__set()

__get()用於從無法存取(受保護或私有)或不存在的屬性讀取資料。

當在無法存取(受保護或私有)或不存在的屬性上呼叫 isset()empty() 時,會觸發__isset()

當在無法存取(受保護或私有)或不存在的屬性上使用 unset() 時,會呼叫__unset()

$name 引數是要互動的屬性名稱。__set() 方法的 $value 引數會指定應將 $name 屬性設定為的值。

屬性 Overloading 僅在物件內容中運作。這些魔術方法不會在靜態內容中觸發。因此,這些方法不應宣告為static。如果其中一個魔術 Overloading 方法宣告為 static,則會發出警告。

注意:

由於 PHP 處理指派運算子的方式,因此會忽略 __set() 的傳回值。同樣地,當將指派鏈結在一起時,永遠不會呼叫 __get(),例如:

 $a = $obj->b = 8; 

注意:

PHP 不會從同一個 Overloading 方法中呼叫 Overloading 方法。這表示,例如,在 __get() 內寫入 return $this->foo 會傳回 null,如果沒有定義 foo 屬性,則會發出 E_WARNING,而不是第二次呼叫 __get()。但是,Overloading 方法可能會隱式呼叫其他 Overloading 方法(例如 __set() 會觸發 __get())。

範例 #1 透過 __get()__set()__isset()__unset() 方法 Overloading 屬性

<?php
class PropertyTest
{
/** 覆載資料的位置。 */
private $data = array();

/** 宣告的屬性不會使用覆載。 */
public $declared = 1;

/** 只有在類別外部存取時才會使用覆載。 */
private $hidden = 2;

public function
__set($name, $value)
{
echo
"設定 '$name' 為 '$value'\n";
$this->data[$name] = $value;
}

public function
__get($name)
{
echo
"取得 '$name'\n";
if (
array_key_exists($name, $this->data)) {
return
$this->data[$name];
}

$trace = debug_backtrace();
trigger_error(
'透過 __get() 存取未定義的屬性:' . $name .
' 在 ' . $trace[0]['file'] .
' 的第 ' . $trace[0]['line'],
E_USER_NOTICE);
return
null;
}

public function
__isset($name)
{
echo
"是否已設定 '$name'?\n";
return isset(
$this->data[$name]);
}

public function
__unset($name)
{
echo
"取消設定 '$name'\n";
unset(
$this->data[$name]);
}

/** 這不是魔術方法,只是為了示範。 */
public function getHidden()
{
return
$this->hidden;
}
}


echo
"<pre>\n";

$obj = new PropertyTest;

$obj->a = 1;
echo
$obj->a . "\n\n";

var_dump(isset($obj->a));
unset(
$obj->a);
var_dump(isset($obj->a));
echo
"\n";

echo
$obj->declared . "\n\n";

echo
"讓我們來試驗名為 'hidden' 的私有屬性:\n";
echo
"私有屬性在類別內部是可見的,所以不會使用 __get() ...\n";
echo
$obj->getHidden() . "\n";
echo
"私有屬性在類別外部是不可見的,所以會使用 __get() ...\n";
echo
$obj->hidden . "\n";
?>

上面的範例將會輸出

Setting 'a' to '1'
Getting 'a'
1

Is 'a' set?
bool(true)
Unsetting 'a'
Is 'a' set?
bool(false)

1

Let's experiment with the private property named 'hidden':
Privates are visible inside the class, so __get() not used...
2
Privates not visible outside of class, so __get() is used...
Getting 'hidden'


Notice:  Undefined property via __get(): hidden in <file> on line 70 in <file> on line 29

方法覆載

public __call(string $name, array $arguments): mixed
public static __callStatic(string $name, array $arguments): mixed

當在物件情境中調用不可存取的方法時,會觸發 __call()

當在靜態情境中調用不可存取的方法時,會觸發 __callStatic()

$name 引數是被呼叫的方法名稱。$arguments 引數是一個列舉陣列,包含傳遞給 $name 方法的參數。

範例 #2 透過 __call()__callStatic() 方法覆載方法

<?php
class MethodTest
{
public function
__call($name, $arguments)
{
// 注意:$name 的值是區分大小寫的。
echo "呼叫物件方法 '$name' " .
implode(', ', $arguments). "\n";
}

public static function
__callStatic($name, $arguments)
{
// 注意:$name 的值是區分大小寫的。
echo "呼叫靜態方法 '$name' " .
implode(', ', $arguments). "\n";
}
}

$obj = new MethodTest;
$obj->runTest('在物件情境中');

MethodTest::runTest('在靜態情境中');
?>

上面的範例將會輸出

Calling object method 'runTest' in object context
Calling static method 'runTest' in static context
新增註解

使用者提供的註解 27 則註解

theaceofthespade at gmail dot com
12 年前
請注意!這點可能看似顯而易見,但請記住,當你決定是否要使用 __get、__set 和 __call 作為存取類別中資料的方式(而不是硬編碼 getter 和 setter)時,請留意這樣做會妨礙你的 IDE 進行任何自動完成、語法高亮或文件顯示的功能。

此外,當與其他人協作時,這已超出個人偏好的範疇。即使沒有 IDE,直接查看程式碼中硬編碼的成員和方法定義,也比必須仔細檢查程式碼並拼湊出在 __get 和 __set 中組裝的方法/成員名稱要容易得多。

如果你仍然決定在類別中所有地方都使用 __get 和 __set,請務必加入詳細的註解和文件,這樣與你協作的人(或之後從你那裡繼承程式碼的人)就不必浪費時間解讀你的程式碼才能使用它。
匿名
8 年前
首先,如果你讀到這篇,請幫這個列表上第一個說「overloading(多載)」這個詞用於描述這種行為是不恰當的留言按讚。因為這真的不是一個好名稱。你正在為一個已接受的資訊科技術語賦予新的定義。

其次,我同意你會讀到的所有關於這個功能的批評。就像把它命名為「overloading」一樣,這個功能也是非常不好的實作方式。請不要在生產環境中使用它。老實說,盡量避免使用它。特別是如果你是 PHP 的初學者。它可能會讓你的程式碼產生非常難以預期的反應。在這種情況下,你可能正在學習錯誤的程式碼編寫方式!

最後,因為 __get、__set 和 __call 的存在,以下程式碼可以執行。這是不正常的行為。而且可能會導致很多問題/錯誤。

<?php

class BadPractice {
// 兩個真實的屬性
public $DontAllowVariableNameWithTypos = true;
protected
$Number = 0;
// 一個私有方法
private function veryPrivateMethod() { }
// 以及三個非常神奇的方法,它們會讓你對 PHP 的所有認知看起來都不一致
// 與你對 PHP 的所有認知不一致。
public function __get($n) {}
public function
__set($n, $v) {}
public function
__call($n, $v) {}
}

// 讓我們在生產環境中看看我們的 BadPractice!
$UnexpectedBehaviour = new BadPractice;

// 大多數 IDE 上沒有語法高亮
$UnexpectedBehaviour->SynTaxHighlighting = false;

// 大多數 IDE 上沒有自動完成
$UnexpectedBehaviour->AutoCompletion = false;

// 這將導致問題發生
$UnexpectedBehaviour->DontAllowVariableNameWithTyphos = false; // 請見下方

// 取得、設定和呼叫任何你想要的東西!
$UnexpectedBehaviour->EveryPosibleMethodCallAllowed(true, 'Why Not?');

// 當然,為什麼不使用你能想到的最不合法的屬性名稱
$UnexpectedBehaviour->{'100%Illegal+Names'} = 'allowed';

// 這種非常令人困惑的語法似乎允許存取 $Number,但由於
// 可見性降低,它會轉到 __set()
$UnexpectedBehaviour->Number = 10;

// 我們似乎也可以遞增它!(這真的很動態!:-) NULL++ LMAO
$UnexpectedBehaviour->Number++;

// 當然,這會輸出 NULL(透過 __get),而不是預期的 11
var_dump($UnexpectedBehaviour->Number);

// 當然,私有方法呼叫現在看起來有效!
// (這會轉到 __call,所以不會發生嚴重錯誤)
$UnexpectedBehaviour->veryPrivateMethod();

// 因為之前 __set 為 false,所以下一個表達式為 true
// 如果我們沒有 __set,之前的賦值就會失敗
// 然後你就會更正拼寫錯誤,而這段程式碼就不會被執行。
// (這真的會很麻煩)
if ($UnexpectedBehaviour->DontAllowVariableNameWithTypos) {
// 如果這段程式碼區塊刪除了一個檔案,或是在
// 資料庫上執行刪除,你可能會難過很久!
$UnexpectedBehaviour->executeStuffYouDontWantHere(true);
}
?>
egingell at sisna dot com
17 年前
小詞彙說明:這*不是*「overloading(多載)」,而是「overriding(覆寫)」。

多載:多次宣告一個函數,使用不同的參數集,如下所示
<?php

function foo($a) {
return
$a;
}

function
foo($a, $b) {
return
$a + $b;
}

echo
foo(5); // 輸出 "5"
echo foo(5, 2); // 輸出 "7"

?>

覆寫:透過重新宣告,使用一個新的方法取代父類別的方法,如下所示
<?php

class foo {
function new(
$args) {
// 執行某些操作。
}
}

class
bar extends foo {
function new(
$args) {
// 執行不同的操作。
}
}

?>
匿名
9 年前
使用魔術方法,特別是 __get()、__set() 和 __call(),實際上會停用大多數 IDE(例如:IntelliSense)中受影響類別的自動完成功能。

為了克服這個不便之處,請使用 phpDoc 讓 IDE 知道這些魔術方法和屬性:@method、@property、@property-read、@property-write。

/**
* @property-read name
* @property-read price
*/
class MyClass
{
private $properties = array('name' => 'IceFruit', 'price' => 2.49)

public function __get($name)
{
return $this->properties($name);
}
}
pogregoire##live.fr
8 年前
了解在 PHP 中,封裝很容易被破壞是很重要的。例如
class Object{

}

$Object = new Object();
$Objet->barbarianProperties = 'boom';

var_dump($Objet);// object(Objet)#1 (1) { ["barbarianProperties"]=> string(7) "boom" }

因此,可以從類別定義之外加入屬性。
因此,為了保護封裝,在類別中引入 __set() 是必要的

class Objet{
public function __set($name,$value){
throw new Exception ('no');
}
}
turabgarip at gmail dot com
3 年前
我同意「overloading(多載)」這個詞對於這個功能來說是不正確的。但我不同意這個功能是完全錯誤的。你也可以使用正確的程式碼來進行「不好的實作方式」。

例如,__call() 非常適用於外部整合實作,我用它來將呼叫轉發到不需要在本機實作的 SOAP 方法。因此你不必撰寫「空的函式主體」。假設你連接的 SOAP 服務有一個「庫存更新」方法。你所要做的就是將產品代碼和庫存數量傳遞給 SOAP。

<?php

class Inventory {

public
__construct() {
// 設定並連接到 SOAP 服務
$this->soap = new SoapClient();
}

public
__call($soapMethod, $params) {
$this->soap->{$soapMethod}(params);
}
}

// 現在你可以使用任何 SOAP 方法,而不需要包裝器
$stock = new Inventory();
$stock->updatePrice($product_id, 20);
$stock->saveProduct($product_info);

?>

當然,你需要一個參數映射,但依我誠實的看法,這比擁有大量鏡像方法好得多,例如

<?php

class Inventory {

public function
updateStock($product_id, $stock) {
$soapClient->updateStock($product_id, $stock);
}
public function
updatePrice($product_id, $price) {
$soapClient->updateStock($product_id, $price);
}
// ...
}

?>
Ant P.
15 年前
使用 __call() 時要格外小心:如果你在某處輸入了函式呼叫的錯誤,它不會觸發未定義函式的錯誤,而是會傳遞給 __call(),可能導致各種奇怪的副作用。
在 5.3 之前的版本中,如果沒有 __callStatic,對不存在的函式進行靜態呼叫也會落到 __call 中!
這讓我困惑了好幾個小時,希望這個評論能讓其他人避免遇到相同的問題。
gabe at fijiwebdesign dot com
10 年前
請注意,您可以透過 unset() 屬性來在執行時為類別實例啟用現有屬性的「多載」。

例如

<?php
class Test {

public
$property1;

public function
__get($name)
{
return
"針對 " . get_class($this) . "->\$$name 呼叫 Get \n";
}

}
?>

可以 unset() 公開屬性 $property1,使其可以透過 __get() 動態處理。

<?php
$Test
= new Test();
unset(
$Test->property1); // 啟用多載
echo $Test->property1; // 針對 Test->\$property1 呼叫 Get
?>

如果您想要代理或延遲載入屬性,但又希望在程式碼和偵錯中擁有文件和可見性,與對不存在的無法存取屬性使用 __get()、__isset()、__set() 相比,這非常有用。
johannes dot kingma at gmail dot com
3 年前
__get 函式的一個有趣用途是屬性/函式合併,對屬性和函式使用相同的名稱。

範例
<?php
class prop_fun {
private
$prop = 123;

public function
__get( $property ) {
if(
property_exists( $this, $property ) ){
return
$this-> $property;
}
throw new
Exception( "沒有此屬性 $property。" );
}
public function
prop() {
return
456;
}
}

$o = new prop_fun();

echo
$o-> prop . '<br>' . PHP_EOL;
echo
$o-> prop() . '<br>' . PHP_EOL;
?>

這將輸出 123 和 456。這看起來像是一個有趣的臨時解決方案,但我將它用於包含日期類型屬性和函式的類別中,讓我能夠撰寫

<?php
class date_class {
/** @property int $date */
private $the_date;

public function
__get( $property ) {
if(
property_exists( $this, $property ) ){
return
$this-> $property;
}
throw new
Exception( "沒有此屬性 $property。" );
}
public function
the_date( $datetime ) {
return
strtotime( $datetime, $this-> the_date );
}

public function
__construct() {
$this-> the_date = time();
}
}

$date_object = new date_class();

$today = $date_object-> the_date;
$nextyear = $date_object-> the_date("+1 year");

echo
date( "d/m/Y", $today) . '<br>';
echo
date( "d/m/Y", $nextyear );
?>

我喜歡這樣,因為它的屬性具有自我文件化的特性。我在使用者輸入的公用程式類別中使用了這個方法。
navarr at gtaero dot net
14 年前
如果您想要讓陣列 $obj->variable[] 等的運作方式更自然,您需要透過參照傳回 __get。

<?php
class Variables
{
public function
__construct()
{
if(
session_id() === "")
{
session_start();
}
}
public function
__set($name,$value)
{
$_SESSION["Variables"][$name] = $value;
}
public function &
__get($name)
{
return
$_SESSION["Variables"][$name];
}
public function
__isset($name)
{
return isset(
$_SESSION["Variables"][$name]);
}
}
?>
php at lanar dot com dot au
14 年前
請注意,__isset 不會在鏈式檢查時呼叫。
如果執行 isset( $x->a->b ),其中 $x 是宣告了 __isset() 的類別,則不會呼叫 __isset()。

<?php

class demo
{
var
$id ;
function
__construct( $id = '誰知道' )
{
$this->id = $id ;
}
function
__get( $prop )
{
echo
"\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') 實例 ', $this->id ;
return new
demo( '自動建立' ) ; // 無論如何都為示範傳回類別
}
function
__isset( $prop )
{
echo
"\n", __FILE__, ':', __LINE__, ' ', __METHOD__, '(', $prop, ') 實例 ', $this->id ;
return
FALSE ;
}
}
$x = new demo( '示範' ) ;
echo
"\n", '執行 isset( $x->a ) 時,如預期呼叫示範的 __isset()' ;
$ret = isset( $x->a ) ;
echo
"\n", '執行 isset( $x->a->b ) 時,在沒有呼叫 __isset() 的情況下呼叫示範的 __get()' ;
$ret = isset( $x->a->b ) ;
?>

輸出

執行 isset( $x->a ) 時,如預期呼叫示範的 __isset()
C:\htdocs\test.php:31 demo::__isset(a) 實例 示範
執行 isset( $x->a->b ) 時,在沒有呼叫 __isset() 的情況下呼叫示範的 __get()
C:\htdocs\test.php:26 demo::__get(a) 實例 示範
C:\htdocs\test.php:31 demo::__isset(b) 實例 自動建立
PHP at jyopp dotKomm
18 年前
這是一個用於記錄函式呼叫的實用類別。它儲存呼叫和引數的序列,這些序列稍後可以套用至物件。這可以用於編寫常見操作序列的腳本,或在標頭檔中製作可以稍後在物件上重播的「可外掛」操作序列。

如果它使用要陰影化的物件進行實例化,它的行為就像一個中介者,並在呼叫傳入時在此物件上執行呼叫,並傳回執行的值。

這是一個非常通用的實作;如果需要在重播過程中處理錯誤碼或例外狀況,則應變更它。
<?php
class MethodCallLog {
private
$callLog = array();
private
$object;

public function
__construct($object = null) {
$this->object = $object;
}
public function
__call($m, $a) {
$this->callLog[] = array($m, $a);
if (
$this->object) return call_user_func_array(array(&$this->object,$m),$a);
return
true;
}
public function
Replay(&$object) {
foreach (
$this->callLog as $c) {
call_user_func_array(array(&$object,$c[0]), $c[1]);
}
}
public function
GetEntries() {
$rVal = array();
foreach (
$this->callLog as $c) {
$rVal[] = "$c[0](".implode(', ', $c[1]).");";
}
return
$rVal;
}
public function
Clear() {
$this->callLog = array();
}
}

$log = new MethodCallLog();
$log->Method1();
$log->Method2("Value");
$log->Method1($a, $b, $c);
// Execute these method calls on a set of objects...
foreach ($array as $o) $log->Replay($o);
?>
cottton at i-stats dot net
10 年前
實際上,我認為您不需要 __set 等方法。
您可以使用它來設定(預先定義的)受保護的(在「某些」情況下是私有的)屬性。但誰會想要那樣呢?
(取消註解 private 或 protected 來測試它)
(pastebin 因為太長...) => http://pastebin.com/By4gHrt5
jstubbs at work-at dot co dot jp
18 年前
<?php $myclass->foo['bar'] = 'baz'; ?>

當覆寫 __get 和 __set 時,上面的程式碼可以(如預期地)運作,但它取決於您的 __get 實作,而不是您的 __set。事實上,永遠不會使用上面的程式碼呼叫 __set。看來 PHP(至少在 5.1 版時)會使用對 __get 所傳回任何內容的參考。更詳細地說,上面的程式碼基本上與以下程式碼相同:

<?php
$tmp_array
= &$myclass->foo;
$tmp_array['bar'] = 'baz';
unset(
$tmp_array);
?>

因此,如果您的 __get 實作如下所示,則上述程式碼不會執行任何動作

<?php
function __get($name) {
return
array_key_exists($name, $this->values)
?
$this->values[$name] : null;
}
?>

您實際上需要像以下程式碼一樣,在 __get 中設定值並傳回該值

<?php
function __get($name) {
if (!
array_key_exists($name, $this->values))
$this->values[$name] = null;
return
$this->values[$name];
}
?>
justmyoponion at gmail dot com
5 年前
如果您不夠專注,請不要使用它。
否則它非常強大,您可以建構非常複雜的程式碼來處理許多事情,就像 Zend Framework 所做的那樣。
matthijs at yourmediafactory dot com
16 年前
雖然 PHP 本身不支援真正的多載,但我必須不同意那些聲稱無法透過 __call 實現多載的人。

是的,它並不好看,但根據其引數的類型多載成員絕對是可能的。一個例子
<?php
class A {

public function
__call ($member, $arguments) {
if(
is_object($arguments[0]))
$member = $member . 'Object';
if(
is_array($arguments[0]))
$member = $member . 'Array';
$this -> $member($arguments);
}

private function
testArray () {
echo
"Array.";
}

private function
testObject () {
echo
"Object.";
}
}

class
B {
}

$class = new A;
$class -> test(array()); // 輸出 'Array.'
$class -> test(new B); // 輸出 'Object.'
?>

當然,這種用法值得懷疑(我自己從來不需要它,但話又說回來,我只有非常簡約的 C++ 和 JAVA 背景)。但是,如果您在函式中使用一些嚴格的命名慣例,則可以使用此一般原則,並選擇性地以其他建議為基礎來建構「形式」的多載。

當然,一旦 PHP 允許您多次宣告相同的成員,但具有不同的引數,就會變得容易得多,因為如果您將其與反射類別結合,「真實」的多載就會掌握在優秀的 OO 程式設計師手中。讓我們拭目以待!
timshaw at mail dot NOSPAMusa dot com
16 年前
如果物件的已宣告公開成員已取消設定,則將呼叫 __get 多載方法。

<?php
class c {
public
$p ;
public function
__get($name) { return "__get of $name" ; }
}

$c = new c ;
echo
$c->p, "\n" ; // 已宣告的公開成員值為空
$c->p = 5 ;
echo
$c->p, "\n" ; // 已宣告的公開成員值為 5
unset($c->p) ;
echo
$c->p, "\n" ; // 取消設定後,值為 "__get of p"
?>
alexandre at nospam dot gaigalas dot net
17 年前
PHP 5.2.1

可以使用變數方法/屬性名稱呼叫名稱無效的魔術方法

<?php

class foo
{
function
__get($n)
{
print_r($n);
}
function
__call($m, $a)
{
print_r($m);
}
}

$test = new foo;
$varname = 'invalid,variable+name';
$test->$varname;
$test->$varname();

?>

我只是不知道這到底是個臭蟲還是個功能 :)
daevid at daevid dot com
15 年前
這裡有一個方便的小常式,可以建議您嘗試設定但不存在的屬性。例如:

嘗試在類別 'User' 中 __get() 不存在的屬性/變數 'operator_id'。

正在檢查 operator 並建議以下項目

* id_operator
* operator_name
* operator_code

請享用。

<?php
/**
* 當 __get() 或 __set() 失敗時,建議替代屬性
*
* @param string $property
* @return string
* @author Daevid Vincent [daevid@daevid.com]
* @date 05/12/09
* @see __get(), __set(), __call()
*/
public function suggest_alternative($property)
{
$parts = explode('_',$property);
foreach(
$parts as $i => $p) if ($p == '_' || $p == 'id') unset($parts[$i]);

echo
'正在檢查 <b>'.implode(', ',$parts)."</b> 並建議以下項目:<br/>\n";

echo
"<ul>";
foreach(
$this as $key => $value)
foreach(
$parts as $p)
if (
stripos($key, $p) !== false) print '<li>'.$key."</li>\n";
echo
"</ul>";
}

只需將其放入您的 __get() 或 __set() 中,就像這樣:

public function
__get($property)
{
echo
"<p><font color='#ff0000'>嘗試在類別 '".$this->get_class_name()."' 中 __get() 不存在的屬性/變數 '".$property."'。</font><p>\n";
$this->suggest_alternative($property);
exit;
}
?>
Marius
19 年前
對於任何正在考慮遍歷某些變數樹的人來說
通過使用 __get() 和 __set()。我嘗試這樣做,並發現一個
問題:您可以使用返回的方式來處理一連串的 __get()
可以處理後續 __get() 的物件,但您不能
以這種方式處理 __get() 和 __set()。
例如,如果您想要
<?php
print($obj->val1->val2->val3); // 三個 __get() 呼叫
?> - 這將會有效,
但如果您想要
<?php
$obj
->val1->val2 = $val; // 一個 __get() 和一個 __set() 呼叫
?> - 這將會失敗並顯示訊息
"致命錯誤:無法存取具有超載屬性存取的物件的未定義屬性"
超載屬性存取"
但是,如果您不將 __get() 和 __set() 混合在一個表達式中,
它將會有效
<?php
$obj
->val1 = $val; // 只有一個 __set() 呼叫
$val2 = $obj->val1->val2; // 兩個 __get() 呼叫
$val2->val3 = $val; // 一個 __set() 呼叫
?>

如您所見,您可以將表達式的 __get() 和 __set() 部分分割成兩個表達式,使其運作。
表達式分割成兩個表達式,使其運作。

順便一提,這對我來說似乎是一個錯誤,我必須回報它。
dans at dansheps dot com
13 年前
由於這件事困擾我一段時間,我想我最好在這裡發表一下...

對於巢狀呼叫私有/受保護變數(可能還有函式)的情況,它會在第一個物件上呼叫 __get(),如果您返回巢狀物件,它會接著在巢狀物件上呼叫 __get(),因為,它也是受保護的。

例如
<?php
class A
{
protected
$B

public function __construct()
{
$this->B = new B();
}

public function
__get($variable)
{
echo
"Class A::Variable " . $variable . "\n\r";
$retval = $this->{$variable};
return
$retval;
}
}

class
B
{
protected
$val

public function __construct()
{
$this->val = 1;
}

public function
__get($variable)
{
echo
"Class B::Variable " . $variable . "\n\r";
$retval = $this->{$variable};
return
$retval;
}
}

$A = new A();

echo
"Final Value: " . $A->B->val;
?>

這將會返回類似以下結果...

Class A::Variable B
Class B::Variable val
Final Value: 1

它將呼叫分成 $A->B 和 $B->val

希望這對某些人有幫助
Daniel Smith
13 年前
小心 __call,以防您有受保護/私有的方法。執行此操作

<?php
class TestMagicCallMethod {
public function
foo()
{
echo
__METHOD__.PHP_EOL;
}

public function
__call($method, $args)
{
echo
__METHOD__.PHP_EOL;
if(
method_exists($this, $method))
{
$this->$method();
}
}

protected function
bar()
{
echo
__METHOD__.PHP_EOL;
}

private function
baz()
{
echo
__METHOD__.PHP_EOL;
}
}

$test = new TestMagicCallMethod();
$test->foo();
/**
* 輸出:
* TestMagicCallMethod::foo
*/

$test->bar();
/**
* 輸出:
* TestMagicCallMethod::__call
* TestMagicCallMethod::bar
*/

$test->baz();
/**
* 輸出:
* TestMagicCallMethod::__call
* TestMagicCallMethod::baz
*/
?>

..可能不是您應該做的事情。請務必確認在 __call 中呼叫的方法是允許的,因為您可能不希望透過打字錯誤或其他方式存取所有私有/受保護的方法。
Nanhe Kumar
10 年前
<?php
// 如何更好地理解 __call 函式的實作
class Employee {

protected
$_name;
protected
$_email;
protected
$_compony;

public function
__call($name, $arguments) {
$action = substr($name, 0, 3);
switch (
$action) {
case
'get':
$property = '_' . strtolower(substr($name, 3));
if(
property_exists($this,$property)){
return
$this->{$property};
}else{
$trace = debug_backtrace();
trigger_error('未定義的屬性 ' . $name . ' 在 ' . $trace[0]['file'] . ' 的第 ' . $trace[0]['line'], E_USER_NOTICE);
return
null;
}
break;
case
'set':
$property = '_' . strtolower(substr($name, 3));
if(
property_exists($this,$property)){
$this->{$property} = $arguments[0];
}else{
$trace = debug_backtrace();
trigger_error('未定義的屬性 ' . $name . ' 在 ' . $trace[0]['file'] . ' 的第 ' . $trace[0]['line'], E_USER_NOTICE);
return
null;
}

break;
default :
return
FALSE;
}
}

}

$s = new Employee();
$s->setName('Nanhe Kumar');
$s->setEmail('nanhe.kumar@gmail.com');
echo
$s->getName(); //Nanhe Kumar
echo $s->getEmail(); // nanhe.kumar@gmail.com
$s->setAge(10); //Notice: Undefined property setAge in
?>
Adeel Khan
17 年前
觀察

<?php
class Foo {
function
__call($m, $a) {
die(
$m);
}
}

$foo = new Foo;
print
$foo->{'wow!'}();

// 輸出 'wow!'
?>

這個方法允許您呼叫包含無效字元的函式。
DevilDude at darkmaker dot com
20 年前
PHP 5 有一個簡單的遞迴系統,可防止您在覆載函式內使用覆載,這表示您無法在 __get 方法中取得覆載的變數,也無法在 _get 方法呼叫的任何函式/方法中取得。但是,您可以在其內部手動呼叫 __get 來執行相同的動作。
strata_ranger at hotmail dot com
15 年前
結合先前注意到的兩件事

1 - 取消設定物件成員會將其從物件中完全移除,後續對該成員的使用將由魔術方法處理。
2 - PHP 不會從自身內部遞迴呼叫一個魔術方法 (至少對於相同的 $name)。

這表示如果物件成員已被 unset(),則可以透過在物件的 __set() 方法中建立它來重新宣告該物件成員 (為 public),如下所示

<?php
class Foo
{
function
__set($name, $value)
{
// 將新的 (public) 成員新增至此物件。
// 這之所以有效,是因為 __set() 不會遞迴呼叫自身。
$this->$name= $value;
}
}

$foo = new Foo();

// 此時 $foo 沒有任何成員
var_dump($foo);

// 此處將呼叫 __set()
$foo->bar = 'something'; // 呼叫 __set()

// $foo 現在包含一個成員
var_dump($foo);

// 因為 'bar' 現在已宣告,所以不會呼叫 __set()
$foo->bar = 'other thing';

?>

另外請注意,如果您想要中斷涉及物件成員的參考而不觸發魔術功能,請勿直接 unset() 物件成員。請改用 =& 將物件成員繫結至任何方便的 null 變數。
php at sleep is the enemy dot co dot uk
17 年前
只是為了加強和詳細說明 DevilDude at darkmaker dot com 在 2004 年 9 月 22 日 07:57 時說的話。

當使用 __set 時,遞迴偵測功能可能會特別危險。當 PHP 遇到通常會呼叫 __set 但會導致遞迴的語句時,它不會發出警告或只是不執行該語句,而是會像根本沒有定義 __set 方法一樣運作。在這種情況下,預設的行為是動態地將指定的屬性新增至物件,從而破壞該屬性的所有後續 __set 或 __get 呼叫的預期功能。

範例

<?php

class TestClass{

public
$values = array();

public function
__get($name){
return
$this->values[$name];
}

public function
__set($name, $value){
$this->values[$name] = $value;
$this->validate($name);
}

public function
validate($name){
/*
下一行將呼叫 __get
但是當我們嘗試再次呼叫 __set 時
PHP 將拒絕並僅將一個名為 $name 的
屬性新增至 $this
*/
$this->$name = trim($this->$name);
}
}

$tc = new TestClass();

$tc->foo = 'bar';
$tc->values['foo'] = 'boing';

echo
'$tc->foo == ' . $tc->foo . '<br>';
echo
'$tc ' . (property_exists($tc, 'foo') ? '現在有一個' : '仍然沒有') . ' 名為 "foo" 的屬性<br>';

/*
輸出:
$tc->foo == bar
$tc 現在有一個名為 "foo" 的屬性
*/

?>
To Top