Урок 02

Свойства и методы

Свойства

Хранят данные объекта.

class User
{
    public string $name;
    private int $age;
    protected ?string $email = null;
}

Каждый объект имеет свои значения.

Типизация

Обязательна (PHP 7.4+):

public string $name;
public int $age;
public ?string $email = null;
public array $roles = [];
public User $friend;                  // класс
public Status $status;                // enum
public mixed $data;                   // mixed

Union:

public int|string $id;

readonly:

public readonly string $email;

Инициализация

По умолчанию нельзя для типизированных свойств без ?:

class Foo
{
    public string $name;   // uninitialized
}

$foo = new Foo();
echo $foo->name;   // Error: must not be accessed before initialization

Решение:

class Foo
{
    public string $name = '';        // дефолт
    public ?string $bio = null;      // nullable
    public string $email;            // инициализировать в конструкторе

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

Методы

Функции класса:

class Calculator
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }

    private function log(string $msg): void
    {
        error_log($msg);
    }
}

Видимость методов — те же правила.

Геттеры и сеттеры

Классический паттерн:

class User
{
    private string $name;
    private int $age;

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getAge(): int
    {
        return $this->age;
    }

    public function setAge(int $age): void
    {
        if ($age < 0 || $age > 150) {
            throw new InvalidArgumentException('Некорректный возраст');
        }
        $this->age = $age;
    }
}

Плюс: контроль над чтением/записью.

Минус: много кода.

Альтернатива — property hooks (PHP 8.4)

class User
{
    public string $name {
        get => strtoupper($this->name);
        set => trim($value);
    }
}

Пока ново. Используй геттеры/сеттеры.

Публичные свойства — когда ок

Простые DTO:

final readonly class CreateUserDto
{
    public function __construct(
        public string $name,
        public string $email,
    ) {}
}

Здесь геттеры не нужны — readonly гарантирует неизменность.

Fluent interface

Цепочка вызовов:

class QueryBuilder
{
    private array $wheres = [];
    private ?int $limit = null;

    public function where(string $field, mixed $value): static
    {
        $this->wheres[] = [$field, $value];
        return $this;
    }

    public function limit(int $n): static
    {
        $this->limit = $n;
        return $this;
    }

    public function toSql(): string
    {
        // ...
    }
}

$sql = (new QueryBuilder())
    ->where('age', '> 18')
    ->where('active', true)
    ->limit(10)
    ->toSql();

return $this — для цепочки. static — правильно работает с наследованием.

Статические методы

Вызываются без объекта:

class Math
{
    public static function sum(int ...$nums): int
    {
        return array_sum($nums);
    }

    public static function max(int ...$nums): int
    {
        return max($nums);
    }
}

echo Math::sum(1, 2, 3);   // 6

Не имеют $this.

Правило: статика — только для чистых функций, без состояния.

Статические свойства

class Counter
{
    private static int $count = 0;

    public static function increment(): void
    {
        self::$count++;
    }
}

Одно на класс, не на объект.

⚠️ Осторожно: глобальное состояние, тяжело тестировать.

Константы

class Status
{
    public const ACTIVE = 'active';
    public const PENDING = 'pending';
}

echo Status::ACTIVE;

В PHP 8.3+ — типизированные:

public const string ACTIVE = 'active';

Enum предпочтительнее для наборов значений.

self vs static vs parent

self — текущий класс:

class Foo
{
    public static function create(): self
    {
        return new self();
    }
}

static — вызывающий класс (правильно для наследования):

class Base
{
    public static function create(): static
    {
        return new static();
    }
}

class Child extends Base {}

$c = Child::create();   // Child, не Base

parent — родительский класс:

class Child extends Base
{
    public function __construct()
    {
        parent::__construct();
    }
}

$this vs self

$this — объект:

$this->name;
$this->method();

self — класс:

self::$staticProp;
self::CONSTANT;
self::staticMethod();

Магические методы

__construct

Конструктор.

__destruct

Деструктор.

__toString

class User
{
    public function __construct(private string $name) {}

    public function __toString(): string
    {
        return $this->name;
    }
}

$user = new User('Иван');
echo $user;   // Иван

__get, __set

Для несуществующих свойств:

class Dynamic
{
    private array $data = [];

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }

    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }

    public function __isset(string $name): bool
    {
        return isset($this->data[$name]);
    }
}

Используется в Eloquent (Laravel), Active Record.

__call

Для несуществующих методов:

class Proxy
{
    public function __call(string $name, array $args): mixed
    {
        return "Метод $name с аргументами " . implode(', ', $args);
    }
}

$p = new Proxy();
echo $p->foo(1, 2);   // Метод foo с аргументами 1, 2

__invoke

Объект как функция:

class Adder
{
    public function __construct(private int $n) {}

    public function __invoke(int $x): int
    {
        return $x + $this->n;
    }
}

$add5 = new Adder(5);
echo $add5(10);   // 15

Порядок объявления

Соглашение (PSR-12):

  1. Константы.
  2. Свойства.
  3. Конструктор.
  4. Публичные методы.
  5. Protected методы.
  6. Приватные методы.

Практика

<?php

declare(strict_types=1);

final class Money
{
    public function __construct(
        private int $amount,
        private string $currency = 'RUB',
    ) {
        if ($amount < 0) {
            throw new InvalidArgumentException('Сумма не может быть отрицательной');
        }
    }

    public function getAmount(): int
    {
        return $this->amount;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }

    public function add(Money $other): static
    {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException('Валюты разные');
        }

        return new static(
            $this->amount + $other->amount,
            $this->currency,
        );
    }

    public function multiply(int $factor): static
    {
        return new static($this->amount * $factor, $this->currency);
    }

    public function format(): string
    {
        return number_format($this->amount / 100, 2) . ' ' . $this->currency;
    }

    public function equals(Money $other): bool
    {
        return $this->amount === $other->amount
            && $this->currency === $other->currency;
    }
}

$price = new Money(100000);
$total = $price->multiply(3);

echo $total->format();   // 3000.00 RUB

Итог

  • Свойства — данные, методы — поведение.
  • Геттеры/сеттеры — инкапсуляция.
  • Fluent — return $this для цепочек.
  • Статика — self::, static::.
  • Константы — через const.
  • Магические методы — __toString, __get, __call, __invoke.
  • Порядок: константы → свойства → конструктор → методы.

Дальше — конструкторы и деструкторы.