PHP 8.4.0 RC4 available for testing

Область видимости

Область видимости свойства, метода или константы (начиная c 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 $public = 'Public2';
protected
$protected = 'Protected2';

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

$obj2 = new MyClass2();
echo
$obj2->public; // Работает
echo $obj2->private; // Неопределён
echo $obj2->protected; // Неисправимая ошибка
$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; // Всё хорошо
$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, поскольку она всегда является более строгой.

При наследовании класса, дочерний класс может переопределить любое свойство, которое не является окончательным (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) являются окончательными.
}
?>

Область видимости метода

Методы класса могут быть определены как 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(); // Выводятся константы public, protected и private


/**
* Объявление класса 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(); // Выводятся константы public и protected, но не private
?>

Видимость из других объектов

Объекты, которые имеют общий тип (наследуются от одного класса), имеют доступ к элементам с модификаторами private и protected друг друга, даже если не являются одним и тем же экземпляром. Это объясняется тем, что реализация видимости элементов известна внутри этих объектов.

Пример #6 Доступ к элементам с модификатором private из объектов одного типа

<?php
class Test
{
private
$foo;

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

private function
bar()
{
echo
'Доступ к закрытому методу.';
}

public function
baz(Test $other)
{
// Мы можем изменить закрытое свойство:
$other->foo = 'привет';
var_dump($other->foo);

// Мы также можем вызвать закрытый метод:
$other->bar();
}
}

$test = new Test('test');

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

Результат выполнения приведённого примера:

string(6) "привет"
Доступ к закрытому методу.
Добавить

Примечания пользователей 6 notes

up
62
pgl at yoyo dot org
9 years ago
Just a quick note that it's possible to declare visibility for multiple properties at the same time, by separating them by commas.

eg:

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

public
$c, $d;

private
$e, $f;
}
?>
up
4
alperenberatdurmus at gmail dot com
1 year ago
Dynamic properties are "public".
<?php
class MyClass {
public function
setProperty($value) {
$this->dynamicProperty = $value;
}
}
$obj = new MyClass();
$obj->setProperty('Hello World');
echo
$obj->dynamicProperty; // Outputs "Hello World"
?>

This usage is the same as well:
<?php
class MyClass {
}
$obj = new MyClass();
$obj->dynamicProperty = 'Hello World';
echo
$obj->dynamicProperty; // Outputs "Hello World"
?>
up
13
jc dot flash at gmail dot com
12 years ago
if not overwritten, self::$foo in a subclass actually refers to parent's 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
?>
up
7
bishop at php dot net
8 years ago
> Members declared protected can be accessed only within
> the class itself and by inherited classes. Members declared
> as private may only be accessed by the class that defines
> the member.

This is not strictly true. Code outside the object can get and set private and protected members:

<?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"

?>

The magic lay in \Closure::bind, which allows an anonymous function to bind to a particular class scope. The documentation on \Closure::bind says:

> If an object is given, the type of the object will be used
> instead. This determines the visibility of protected and
> private methods of the bound object.

So, effectively, we're adding a run-time setter to $sealed, then calling that setter. This can be elaborated to generic functions that can force set and force get object members:

<?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'

?>

You should probably not rely on this ability for production quality code, but having this ability for debugging and testing is handy.
up
8
Joshua Watt
17 years ago
I couldn't find this documented anywhere, but you can access protected and private member varaibles in different instance of the same class, just as you would expect

i.e.

<?php
class 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;
}
}

class
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); //echoes a_protected and a_private
$other_a->print_other($b); //echoes b_protected and ba_private

$b->print_other($a); //echoes a_protected and a_private
?>
up
1
kostya at eltexsoft dot com
3 years ago
I see we can redeclare private properties into child class
<?php
class A{
private
int $private_prop = 4;
protected
int $protected_prop = 8;
}

class
B extends A{
private
int $private_prop = 7; // we can redeclare private property!!!
public function printAll() {
echo
$this->private_prop;
echo
$this->protected_prop;
}
}

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