簡要說明一下,可以同時宣告多個屬性的可見性,只需用逗號分隔即可。
例如
<?php
class a
{
protected $a, $b;
public $c, $d;
private $e, $f;
}
?>
屬性、方法或(從 PHP 7.1.0 起)常數的可見性,可以透過在宣告前加上關鍵字 public
、protected
或 private
來定義。宣告為 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
。這是因為參考可以用來修改屬性值。
get
和 set
操作,因此將遵循 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.
簡要說明一下,可以同時宣告多個屬性的可見性,只需用逗號分隔即可。
例如
<?php
class a
{
protected $a, $b;
public $c, $d;
private $e, $f;
}
?>
動態屬性是「公開的」。
<?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"
?>
如果沒有被覆寫,子類別中的 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
?>
> 宣告為 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'
?>
您或許不應該在正式產品的程式碼中依賴此功能,但將其用於除錯和測試則相當方便。
我在任何地方都找不到相關文件,但您可以如同預期般在同一個類別的不同實例中存取 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
?>
我發現我們可以在子類別中重新宣告私有屬性
<?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
}
?>