Урок 04

Видимость: 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.

Дальше — наследование и переопределение методов.