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

Область видимости свойства, метода или константы (начиная 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) "привет"
Доступ к закрытому методу.