PHP 8.4.1 Released!

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

Область видимости свойства, метода или начиная c PHP 7.1.0 константы определяют путём добавления перед объявлением ключевого слова: public, protected или private. Доступ к общедоступным членам класса, — которые объявили с ключевым словом public, — разрешается из любой области видимости. Доступ к защищённым членам класса, которые объявили с ключевым словом protected, — возможен только внутри самого класса, и в производных классах-наследниках или родительских классах. Доступ к закрытым членам класса, — которые объявили с ключевым словом private, — открывается только для самого класса, в котором их определили.

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

Свойства класса разрешается определять как открытые — public, защищённые — protected или закрытые — private. Свойства, которые объявили без явного ключевого слова области видимости, определяются как открытые, как будто свойство объявили с ключевым словом 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->protected; // Фатальная ошибка
echo $obj2->private; // Предупреждение о неопределённом свойстве
$obj2->printHello(); // Выводит 'Public2', 'Protected2' и предупреждение о неопределённом свойстве

?>

Асимметричная область видимости свойств

Начиная с PHP 8.4 область видимости свойств разрешили также устанавливать асимметрично — отдельно для чтения и записи. Видимость свойства для записи определяют инструкцией (set), которую указывают сразу после модификатора видимости, тогда как инструкцию (get) после модификатора видимости для чтения не указывают, а подразумевают. Область видимости 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, защищённые — protected или закрытые — private. Методы, которые объявили без явного ключевого слова области видимости, определяются как открытые, как будто метод объявили с ключевым словом 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, защищённые — protected или закрытые — private. Константы, которые объявили без явного ключевого слова области видимости, определяются как открытые, как будто константу объявили с ключевым словом 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
?>

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

Объектам одного и того же типа доступны защищённые и закрытые члены класса друг друга, даже если это разные экземпляры. Это связано с тем, что внутри таких объектов уже известны конкретные детали реализации.

Пример #6 Доступ к закрытым членам объекта того же типа

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