Урок 10. ООП: классы и объекты
Когда программа растёт, функций и массивов становится много, и уже трудно понять, какие данные к каким функциям относятся. Объектно-ориентированное программирование (ООП) собирает данные и действия над ними вместе. Класс — чертёж: какие у вещи есть данные и что она умеет. Объект — конкретная вещь, сделанная по чертежу. Большинство современных библиотек и фреймворков на PHP построены на классах, поэтому читать такой код придётся в любом случае.
Класс и объект
<?php
class Product
{
public string $name;
public int $price;
public function __construct(string $name, int $price)
{
$this->name = $name;
$this->price = $price;
}
public function label(): string
{
return "{$this->name} — {$this->price} руб.";
}
}
$bread = new Product('Хлеб', 2);
echo $bread->label() . "\n";
$bread->price = 3;
echo $bread->label() . "\n";Хлеб — 2 руб.
Хлеб — 3 руб.public string $name;— свойство: переменная внутри объекта, с типом.function label()внутри класса — метод: функция, которая работает с данными объекта.__construct— конструктор: вызывается сам при создании объекта черезnewи получает его аргументы.$this— «этот объект»: внутри метода через него обращаются к своим свойствам и методам.->— доступ к свойству или методу объекта:$bread->price,$bread->label().
Имена классов по обычаю пишут с заглавной буквы: Product, UserRepository.
Короткий конструктор, private и readonly
С PHP 8.0 свойства можно объявить прямо в параметрах конструктора, а с PHP 8.1 — сделать неизменяемыми (readonly):
<?php
class BankAccount
{
private int $balance = 0;
public function __construct(public readonly string $owner)
{
}
public function deposit(int $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('Сумма должна быть больше нуля');
}
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$account = new BankAccount('Анна');
$account->deposit(100);
$account->deposit(50);
echo $account->owner . ': ' . $account->getBalance() . "\n";
try {
$account->deposit(-5);
} catch (InvalidArgumentException $e) {
echo 'Ошибка: ' . $e->getMessage() . "\n";
}Анна: 150
Ошибка: Сумма должна быть больше нуляЗдесь три важные идеи.
Видимость. Свойство $balance объявлено private: снаружи класса его не прочитать и не изменить, запись $account->balance = 1000000 остановит программу ошибкой Cannot access private property. Менять баланс можно только методом deposit, а он проверяет сумму. Так класс сам следит, чтобы его данные оставались правильными.
| Слово | Кому доступно |
|---|---|
public | всем |
protected | самому классу и его наследникам |
private | только самому классу |
readonly. Свойство $owner задаётся один раз в конструкторе. Попытка изменить его позже даст ошибку Cannot modify readonly property.
Исключения. throw сообщает об ошибке и прерывает метод. Код, который может её вызвать, заключают в try, а в catch решают, что делать. Если исключение никто не поймал, программа остановится с сообщением Uncaught InvalidArgumentException. В PHP много готовых классов исключений: InvalidArgumentException, RuntimeException, PDOException и другие.
Наследование
Класс может расширить другой класс словом extends: получить все его свойства и методы и что-то поменять.
<?php
class Animal
{
public function __construct(protected string $name)
{
}
public function speak(): string
{
return "{$this->name} издаёт звук";
}
}
class Dog extends Animal
{
public function speak(): string
{
return "{$this->name} лает";
}
}
$animals = [new Animal('Ёжик'), new Dog('Шарик')];
foreach ($animals as $animal) {
echo $animal->speak() . "\n";
}Ёжик издаёт звук
Шарик лаетDog получил от Animal конструктор и свойство $name (оно protected, поэтому наследнику доступно) и заменил метод speak своим. Вызвать метод родителя из наследника можно через parent::speak().
Интерфейсы
Интерфейс — договор: список методов, которые класс обязан иметь. Код, который работает с интерфейсом, не интересуется, какой именно перед ним класс:
<?php
interface Notifier
{
public function send(string $to, string $text): void;
}
class EmailNotifier implements Notifier
{
public function send(string $to, string $text): void
{
echo "Письмо для $to: $text\n";
}
}
function remind(Notifier $notifier, string $to): void
{
$notifier->send($to, 'Завтра собрание в 10:00');
}
remind(new EmailNotifier(), 'anna@example.com');Письмо для anna@example.com: Завтра собрание в 10:00Завтра можно добавить класс SmsNotifier implements Notifier, и функция remind будет работать с ним без единой правки.
Статические свойства и методы
static принадлежит классу целиком, а не отдельному объекту. Обращаются к нему через :::
<?php
class Temperature
{
public static function toFahrenheit(float $celsius): float
{
return $celsius * 9 / 5 + 32;
}
}
echo Temperature::toFahrenheit(25) . "\n";77Внутри класса к своим статическим членам обращаются через self::, к константам класса — так же: self::MAX_SIZE.
Перечисления: enum
С PHP 8.1 для набора фиксированных значений есть enum:
<?php
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
}
$status = Status::from('published');
echo $status->name . "\n";
var_dump($status === Status::Published);Published
bool(true)Status::from превращает строку из базы в значение перечисления, ->name и ->value дают имя и значение. Опечатка вроде Status::Publised остановит программу ошибкой, как только PHP дойдёт до этой строки, а с простыми строками 'publised' такая опечатка жила бы незамеченной.
Классы по файлам
Обычно каждый класс лежит в своём файле с тем же именем: класс Product — в Product.php. Чтобы не писать require для каждого, используют автозагрузку (PHP сам подключает файл класса, когда класс понадобился) и пространства имён (namespace — приставки к именам классов, чтобы классы разных библиотек не путались). Их удобнее всего настроить через Composer (справка Composer).
Частые ошибки
Undefined variable $nameвнутри метода — к свойству обращаются через$this->name, а не просто$name.Call to a member function label() on null— метод вызван у переменной, в которой нет объекта: её не создали черезnewили функция вернулаnull.Class "Product" not found— файл с классом не подключён: нетrequireили не настроена автозагрузка.Cannot access private property— свойствоprivateчитают снаружи класса. Нужен метод, который его отдаёт.
Коротко
- Класс — чертёж, объект — экземпляр, созданный через
new;$this— текущий объект,->— доступ к его членам. - Конструктор
__constructвызывается при создании; в PHP 8 свойства можно объявить прямо в его параметрах. public,protected,privateзадают, кому доступно свойство или метод;readonlyзапрещает менять значение после конструктора.extends— наследование,interfaceиimplements— договор о методах.- Ошибки сообщают через
throw, ловят вtry … catch. enum(PHP 8.1) — набор фиксированных значений.