PHP Conference Japan 2024

可見性

屬性、方法或(從 PHP 7.1.0 起)常數的可見性,可以透過在宣告前加上關鍵字 publicprotectedprivate 來定義。宣告為 public 的類別成員可以在任何地方存取。宣告為 protected 的成員只能在類別本身及其繼承類別和父類別中存取。宣告為 private 的成員只能由定義該成員的類別存取。

屬性可見性

類別屬性可以定義為 public、private 或 protected。沒有明確指定可見性關鍵字的屬性,會被定義為 public。

範例 #1 屬性宣告

<?php
/**
* 定義 MyClass
*/
class MyClass
{
public
$public = 'Public';
protected
$protected = 'Protected';
private
$private = 'Private';

function
printHello()
{
echo
$this->public;
echo
$this->protected;
echo
$this->private;
}
}

$obj = new MyClass();
echo
$obj->public; // 可以正常運作
echo $obj->protected; // 嚴重錯誤
echo $obj->private; // 嚴重錯誤
$obj->printHello(); // 顯示 Public, Protected 和 Private


/**
* 定義 MyClass2
*/
class MyClass2 extends MyClass
{
// 我們可以重新宣告 public 和 protected 屬性,但不能重新宣告 private 屬性
public $public = 'Public2';
protected
$protected = 'Protected2';

function
printHello()
{
echo
$this->public;
echo
$this->protected;
echo
$this->private;
}
}

$obj2 = new MyClass2();
echo
$obj2->public; // 可以正常運作
echo $obj2->protected; // 嚴重錯誤
echo $obj2->private; // 未定義
$obj2->printHello(); // 顯示 Public2, Protected2, 未定義

?>

非對稱屬性可見性

從 PHP 8.4 開始,屬性可以設定非對稱的可見性,讀取(get)和寫入(set)的範圍不同。具體來說,可以單獨指定 set 的可見性,前提是它不能比預設的可見性更寬鬆。

範例 #2 非對稱屬性可見性

<?php
class Book
{
public function
__construct(
public private(
set) string $title,
public protected(
set) string $author,
protected private(
set) int $pubYear,
) {}
}

class
SpecialBook extends Book
{
public function
update(string $author, int $year): void
{
$this->author = $author; // OK
$this->pubYear = $year; // 致命錯誤
}
}

$b = new Book('How to PHP', 'Peter H. Peterson', 2024);

echo
$b->title; // 可以運作
echo $b->author; // 可以運作
echo $b->pubYear; // 致命錯誤

$b->title = 'How not to PHP'; // 致命錯誤
$b->author = 'Pedro H. Peterson'; // 致命錯誤
$b->pubYear = 2023; // 致命錯誤
?>

關於非對稱可見性有一些注意事項。

  • 只有具類型提示的屬性才能擁有獨立的 set 可見性。
  • set 的可見性必須與 get 相同或更具限制性。也就是說,public protected(set)protected protected(set) 是允許的,但 protected public(set) 會導致語法錯誤。
  • 如果屬性是 public,則主要的可見性可以省略。也就是說,public private(set)private(set) 會產生相同的結果。
  • 具有 private(set) 可見性的屬性會自動成為 final,並且不能在子類別中重新宣告。
  • 取得屬性參考會遵循 set 可見性,而不是 get。這是因為參考可以用來修改屬性值。
  • 同樣地,嘗試寫入陣列屬性在內部涉及 getset 操作,因此將遵循 set 可見性,因為它始終更具限制性。

注意事項設定可見性宣告中不允許使用空格。private(set) 是正確的,而 private( set ) 不正確,會導致語法錯誤。

當一個類別繼承另一個類別時,子類別可以重新定義任何非 final 的屬性。在這樣做的時候,它可以擴展主要的存取權限或 set 的存取權限,前提是新的存取權限與父類別相同或更寬鬆。但是,請注意,如果覆寫 private 屬性,它實際上並不會更改父類別的屬性,而是會建立一個具有不同內部名稱的新屬性。

範例 #3 非對稱屬性繼承

<?php
class Book
{
protected
string $title;
public protected(
set) string $author;
protected private(
set) int $pubYear;
}

class
SpecialBook extends Book
{
public protected(
set) $title; // 可以,因為讀取權限更寬鬆,而寫入權限相同。
public string $author; // 可以,因為讀取權限相同,而寫入權限更寬鬆。
public protected(set) int $pubYear; // 致命錯誤。private(set) 屬性是 final。
}
?>

方法可見性

類別方法可以定義為 public、private 或 protected。未明確宣告可見性關鍵字的方法會被定義為 public。

範例 #4 方法宣告

<?php
/**
* Define MyClass
*/
class MyClass
{
// Declare a public constructor
public function __construct() { }

// Declare a public method
public function MyPublic() { }

// Declare a protected method
protected function MyProtected() { }

// Declare a private method
private function MyPrivate() { }

// This is public
function Foo()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate();
}
}

$myclass = new MyClass;
$myclass->MyPublic(); // Works
$myclass->MyProtected(); // Fatal Error
$myclass->MyPrivate(); // Fatal Error
$myclass->Foo(); // Public, Protected and Private work


/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// This is public
function Foo2()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate(); // Fatal Error
}
}

$myclass2 = new MyClass2;
$myclass2->MyPublic(); // Works
$myclass2->Foo2(); // Public and Protected work, not Private

class Bar
{
public function
test() {
$this->testPrivate();
$this->testPublic();
}

public function
testPublic() {
echo
"Bar::testPublic\n";
}

private function
testPrivate() {
echo
"Bar::testPrivate\n";
}
}

class
Foo extends Bar
{
public function
testPublic() {
echo
"Foo::testPublic\n";
}

private function
testPrivate() {
echo
"Foo::testPrivate\n";
}
}

$myFoo = new Foo();
$myFoo->test(); // Bar::testPrivate
// Foo::testPublic
?>

常數可見性

從 PHP 7.1.0 開始,類別常數可以定義為 public、private 或 protected。未明確宣告可見性關鍵字的常數會被定義為 public。

範例 #5 PHP 7.1.0 之後的常數宣告

<?php
/**
* 定義 MyClass
*/
class MyClass
{
// 宣告一個公開常數
public const MY_PUBLIC = 'public';

// 宣告一個保護常數
protected const MY_PROTECTED = 'protected';

// 宣告一個私有常數
private const MY_PRIVATE = 'private';

public function
foo()
{
echo
self::MY_PUBLIC;
echo
self::MY_PROTECTED;
echo
self::MY_PRIVATE;
}
}

$myclass = new MyClass();
MyClass::MY_PUBLIC; // 可以正常運作
MyClass::MY_PROTECTED; // 致命錯誤
MyClass::MY_PRIVATE; // 致命錯誤
$myclass->foo(); // 公開、保護和私有皆可正常運作


/**
* 定義 MyClass2
*/
class MyClass2 extends MyClass
{
// 這是公開的
function foo2()
{
echo
self::MY_PUBLIC;
echo
self::MY_PROTECTED;
echo
self::MY_PRIVATE; // 致命錯誤
}
}

$myclass2 = new MyClass2;
echo
MyClass2::MY_PUBLIC; // 可以正常運作
$myclass2->foo2(); // 公開和保護可以正常運作,但私有不允許
?>

來自其他物件的存取權限

即使不是同一個實例,相同類型的物件也能夠互相存取彼此的私有和保護成員。這是因為在這些物件內部,實作的特定細節已經是已知的。

範例 #6 存取相同物件類型的私有成員

<?php
class Test
{
private
$foo;

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

private function
bar()
{
echo
'Accessed the private method.';
}

public function
baz(Test $other)
{
// 我們可以更改私有屬性:
$other->foo = 'hello';
var_dump($other->foo);

// 我們也可以呼叫私有方法:
$other->bar();
}
}

$test = new Test('test');

$test->baz(new Test('other'));
?>

上述範例將輸出

string(5) "hello"
Accessed the private method.
新增註解

使用者貢獻的註解 6 則註解

pgl at yoyo dot org
9 年前
簡要說明一下,可以同時宣告多個屬性的可見性,只需用逗號分隔即可。

例如

<?php
class a
{
protected
$a, $b;

public
$c, $d;

private
$e, $f;
}
?>
alperenberatdurmus at gmail dot com
1 年前
動態屬性是「公開的」。
<?php
class MyClass {
public function
setProperty($value) {
$this->dynamicProperty = $value;
}
}
$obj = new MyClass();
$obj->setProperty('Hello World');
echo
$obj->dynamicProperty; // 輸出 "Hello World"
?>

這種用法也一樣
<?php
class MyClass {
}
$obj = new MyClass();
$obj->dynamicProperty = 'Hello World';
echo
$obj->dynamicProperty; // 輸出 "Hello World"
?>
jc dot flash at gmail dot com
12 年前
如果沒有被覆寫,子類別中的 self::$foo 實際上指的是父類別的 self::$foo
<?php
class one
{
protected static
$foo = "bar";
public function
change_foo($value)
{
self::$foo = $value;
}
}

class
two extends one
{
public function
tell_me()
{
echo
self::$foo;
}
}
$first = new one;
$second = new two;

$second->tell_me(); // bar
$first->change_foo("restaurant");
$second->tell_me(); // restaurant
?>
bishop at php dot net
8 年前
> 宣告為 protected 的成員只能在類別本身及其繼承的類別中存取。
> 宣告為 private 的成員只能由定義該成員的類別存取。
>
>

這並不完全正確。物件外部的程式碼可以取得和設定 private 和 protected 的成員。

<?php
class Sealed { private $value = 'foo'; }

$sealed = new Sealed;
var_dump($sealed); // private $value => string(3) "foo"

call_user_func(\Closure::bind(
function () use (
$sealed) { $sealed->value = 'BAZ'; },
null,
$sealed
));

var_dump($sealed); // private $value => string(3) "BAZ"

?>

神奇之處就在於 \Closure::bind,它允許匿名函數綁定到特定類別的作用域。 \Closure::bind 的文件說明如下:

> 如果給定一個物件,將使用該物件的類型
> 作為替代。這決定了綁定物件的 protected 和
> private 方法的存取權限。

因此,實際上,我們在運行時為 $sealed 添加了一個 setter,然後呼叫該 setter。這可以延伸到通用函數,強制設定和取得物件成員。

<?php
function force_set($object, $property, $value) {
call_user_func(\Closure::bind(
function () use (
$object, $property, $value) {
$object->{$property} = $value;
},
null,
$object
));
}

function
force_get($object, $property) {
return
call_user_func(\Closure::bind(
function () use (
$object, $property) {
return
$object->{$property};
},
null,
$object
));
}

force_set($sealed, 'value', 'quux');
var_dump(force_get($sealed, 'value')); // 'quux'

?>

您或許不應該在正式產品的程式碼中依賴此功能,但將其用於除錯和測試則相當方便。
Joshua Watt
17 年前
我在任何地方都找不到相關文件,但您可以如同預期般在同一個類別的不同實例中存取 protected 和 private 的成員變數。

例如:

<?php
類別 A
{
protected
$prot;
private
$priv;

public function
__construct($a, $b)
{
$this->prot = $a;
$this->priv = $b;
}

public function
print_other(A $other)
{
echo
$other->prot;
echo
$other->priv;
}
}

類別
B 繼承 A
{
}

$a = new A("a_protected", "a_private");
$other_a = new A("other_a_protected", "other_a_private");

$b = new B("b_protected", "ba_private");

$other_a->print_other($a); //印出 a_protected 和 a_private
$other_a->print_other($b); //印出 b_protected 和 ba_private

$b->print_other($a); //印出 a_protected 和 a_private
?>
kostya at eltexsoft dot com
3 年前
我發現我們可以在子類別中重新宣告私有屬性
<?php
類別 A{
private
int $private_prop = 4;
protected
int $protected_prop = 8;
}

類別
B 繼承 A{
private
int $private_prop = 7; // 我們可以重新宣告私有屬性!!!
public function printAll() {
echo
$this->private_prop;
echo
$this->protected_prop;
}
}

$b = new B;
$b->printAll(); // 顯示 78
}
?>
To Top