快速说明一下,可以通过逗号分隔多个属性来同时声明它们的可见性。
例如
<?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、Undefined
?>
从 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
可见性,因为这始终是更严格的。
注意: 在 set-visibility 声明中不允许使用空格。
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
/**
* 定义 MyClass
*/
class MyClass
{
// 声明一个公共构造函数
public function __construct() { }
// 声明一个公共方法
public function MyPublic() { }
// 声明一个受保护的方法
protected function MyProtected() { }
// 声明一个私有方法
private function MyPrivate() { }
// 这是公共的
function Foo()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate();
}
}
$myclass = new MyClass;
$myclass->MyPublic(); // 可行
$myclass->MyProtected(); // 严重错误
$myclass->MyPrivate(); // 严重错误
$myclass->Foo(); // 公共、受保护和私有均可行
/**
* 定义 MyClass2
*/
class MyClass2 extends MyClass
{
// 这是公共的
function Foo2()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate(); // 严重错误
}
}
$myclass2 = new MyClass2;
$myclass2->MyPublic(); // 可行
$myclass2->Foo2(); // 公共和受保护可行,私有不允许
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
类 Test
{
private $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
private function bar()
{
echo '访问了私有方法。';
}
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;
}
?>
动态属性是“public”的。
<?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 的成员只能由定义该成员的类访问。
>
这并不完全正确。对象外部的代码可以获取和设置私有和受保护的成员
<?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 的文档中说
> 如果给定一个对象,则将使用该对象的类型
> 代替。这决定了绑定对象的受保护和
> 私有方法的可见性。
因此,实际上,我们正在为 $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'
?>
您可能不应该依赖此功能进行生产质量代码,但拥有此功能进行调试和测试非常方便。
我找不到任何地方对此进行记录,但是您可以访问同一类的不同实例中的受保护和私有成员变量,就像您期望的那样
即
<?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 extends 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 extends A{
private int $private_prop = 7; // 我们可以重新声明私有属性!!!
public function printAll() {
echo $this->private_prop;
echo $this->protected_prop;
}
}
$b = new B;
$b->printAll(); // 显示 78
}
?>