Видимость: public, protected, private
Три уровня
public— доступно всем.protected— только в классе и наследниках.private— только в текущем классе.
Применяется к: свойствам, методам, константам.
public
Открыто всем:
class User
{
public string $name = '';
public function greet(): string
{
return "Привет, {$this->name}";
}
}
$user = new User();
$user->name = 'Иван'; // ok
$user->greet(); // ok
Правило: используй public только для API класса.
protected
Для класса и наследников:
class Base
{
protected string $name = '';
protected function log(string $msg): void
{
echo $msg;
}
}
class Child extends Base
{
public function setName(string $name): void
{
$this->name = $name; // ok — protected
$this->log('Set'); // ok — protected
}
}
$c = new Child();
$c->name = 'X'; // Error — protected
Извне — недоступно.
private
Только в текущем классе:
class User
{
private string $password = '';
private function hash(string $pass): string
{
return password_hash($pass, PASSWORD_DEFAULT);
}
public function setPassword(string $pass): void
{
$this->password = $this->hash($pass);
}
}
Наследники НЕ видят приватные методы и свойства родителя.
private в наследниках
⚠️ Разные private — разные сущности:
class ParentClass
{
private string $secret = 'parent';
public function getSecret(): string
{
return $this->secret;
}
}
class ChildClass extends ParentClass
{
private string $secret = 'child'; // своё свойство
}
$c = new ChildClass();
echo $c->getSecret(); // 'parent' — метод родителя видит свой секрет
Никакой связи между двумя $secret.
Правило выбора
По умолчанию — private.
Открывай минимум:
private— всё, что не нужно наследникам.protected— только то, что переопределяют наследники.public— только публичный API.
Инкапсуляция
Скрытие внутреннего состояния:
final class BankAccount
{
private int $balance = 0;
private array $transactions = [];
public function deposit(int $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('Сумма должна быть положительной');
}
$this->balance += $amount;
$this->transactions[] = ['type' => 'deposit', 'amount' => $amount];
}
public function withdraw(int $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('Сумма должна быть положительной');
}
if ($amount > $this->balance) {
throw new RuntimeException('Недостаточно средств');
}
$this->balance -= $amount;
$this->transactions[] = ['type' => 'withdraw', 'amount' => $amount];
}
public function getBalance(): int
{
return $this->balance;
}
}
Нельзя изменить $balance напрямую — только через методы с проверками.
Публичные свойства — когда ок
Простые DTO с readonly:
final readonly class CreateUserDto
{
public function __construct(
public string $name,
public string $email,
) {}
}
Readonly защищает от изменений. Геттеры не нужны.
Видимость методов
Публичные — API:
public function save(): void { }
Protected — для наследников:
protected function validate(): void { }
Private — внутренняя логика:
private function normalize(): string { }
Магические методы
__construct — обычно public, может быть private (Singleton).
__destruct — public.
__get/__set — public.
__call — public.
__toString — public.
Константы
Видимость констант:
class Config
{
public const APP_NAME = 'MyApp';
protected const SECRET_KEY = 'xxx';
private const INTERNAL = 'yyy';
}
Правило — то же: минимум открытого.
Наследование видимости
Можно расширить видимость при переопределении:
class ParentClass
{
protected function process(): void { }
}
class ChildClass extends ParentClass
{
public function process(): void { } // ок, расширяем
}
Нельзя сузить:
class ParentClass
{
public function process(): void { }
}
class ChildClass extends ParentClass
{
protected function process(): void { } // Error
}
Финализация
final — запретить наследование:
final class User { } // нельзя наследовать
class Foo
{
final public function bar(): void { } // нельзя переопределить
}
Правило: если не проектировал класс для наследования — делай final.
Readonly — видимость
readonly — модификатор к видимости:
public readonly string $name;
protected readonly int $id;
private readonly array $data;
readonly без видимости — ошибка.
Асимметричная видимость (PHP 8.4)
Разная для чтения и записи:
class User
{
public private(set) string $name = '';
}
$user = new User();
echo $user->name; // ok (чтение)
$user->name = 'Иван'; // Error (запись только внутри класса)
Пока ново — не везде поддерживается.
Практика
<?php
declare(strict_types=1);
final class PasswordHasher
{
private const ALGORITHM = PASSWORD_BCRYPT;
private const COST = 12;
public function hash(string $password): string
{
$this->validate($password);
return password_hash($password, self::ALGORITHM, [
'cost' => self::COST,
]);
}
public function verify(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
private function validate(string $password): void
{
if (strlen($password) < 8) {
throw new InvalidArgumentException('Пароль короче 8 символов');
}
}
}
$hasher = new PasswordHasher();
$hash = $hasher->hash('secret123');
var_dump($hasher->verify('secret123', $hash)); // true
var_dump($hasher->verify('wrong', $hash)); // false
ALGORITHM, COST, validate — приватные. Наружу — только hash и verify.
Антипаттерны
1. Публичные свойства с данными:
class User
{
public string $password; // плохо
}
2. Всё public:
class Service
{
public function doEverything(): void { }
public function helper(): void { } // не нужно снаружи
public function internalCalc(): int { } // не нужно снаружи
}
3. protected вместо private:
class Foo
{
protected function internal(): void { } // зачем наследникам?
}
Итог
public— всем,protected— наследникам,private— только себе.- По умолчанию —
private. - Расширять видимость при наследовании можно, сужать — нет.
final— защита от наследования.readonly— иммутабельность.- Асимметричная видимость — PHP 8.4.
Дальше — наследование и переопределение методов.