PHP orientado a objetos
ООП в PHP: классы, трейты и интерфейсы для новичков
Объектно-ориентированное программирование — способ организации кода вокруг объектов. Объект — как ученик: есть характеристики (имя, возраст) и он умеет делать вещи (учиться, сдавать тесты).
Класс и объект
<?php
class Student {
public string $name;
public int $age;
public string $grade;
private float $gpa;
public function __construct(string $name, int $age, string $grade, float $gpa) {
$this->name = $name;
$this->age = $age;
$this->grade = $grade;
$this->gpa = $gpa;
}
public function introduce(): string {
return "Привет, я {$this->name} из класса {$this->grade}.";
}
public function getGpa(): float { return $this->gpa; }
public function setGpa(float $gpa): void {
if ($gpa < 0 || $gpa > 5) {
throw new InvalidArgumentException("GPA должен быть 0-5");
}
$this->gpa = $gpa;
}
}
$alice = new Student("Алиса", 12, "6А", 4.8);
echo $alice->introduce() . "\n";
echo "GPA: " . $alice->getGpa() . "\n";
$alice->setGpa(5.0);
echo "Новый GPA: " . $alice->getGpa() . "\n";
echo get_class($alice) . "\n"; // Student
echo ($alice instanceof Student) ? "Да\n" : "Нет\n";
?>
Visibility: public, protected, private
<?php
class BankAccount {
public string $owner; // везде
protected float $balance; // класс + подклассы
private string $pin; // только в этом классе
public function __construct(string $owner, float $initial, string $pin) {
$this->owner = $owner;
$this->balance = $initial;
$this->pin = $pin;
}
public function deposit(float $amount): void {
if ($amount <= 0) throw new InvalidArgumentException("Сумма > 0");
$this->balance += $amount;
echo "Пополнено {$amount}. Баланс: {$this->balance}\n";
}
public function withdraw(float $amount, string $pin): bool {
if ($pin !== $this->pin) { echo "Неверный PIN\n"; return false; }
if ($amount > $this->balance) { echo "Недостаточно средств\n"; return false; }
$this->balance -= $amount;
echo "Снято {$amount}. Баланс: {$this->balance}\n";
return true;
}
public function getBalance(): float { return $this->balance; }
}
$acc = new BankAccount("Алиса", 1000.0, "1234");
$acc->deposit(500);
$acc->withdraw(200, "1234"); // Успех
$acc->withdraw(200, "0000"); // Неверный PIN
?>
Наследование
<?php
class Animal {
protected string $name;
public function __construct(string $name) { $this->name = $name; }
public function speak(): string { return "..."; }
public function describe(): string { return $this->name; }
}
class Dog extends Animal {
private string $breed;
public function __construct(string $name, string $breed) {
parent::__construct($name);
$this->breed = $breed;
}
public function speak(): string { return "Гав!"; }
public function fetch(): string { return "{$this->name} принёс мячик!"; }
public function describe(): string {
return parent::describe() . " ({$this->breed})";
}
}
class Cat extends Animal {
public function speak(): string { return "Мяу!"; }
public function purr(): string { return "{$this->name} мурлычет..."; }
}
$dog = new Dog("Шарик", "Лабрадор");
$cat = new Cat("Мурка");
echo $dog->describe() . ": " . $dog->speak() . "\n";
echo $cat->describe() . ": " . $cat->speak() . "\n";
echo $dog->fetch() . "\n";
echo $cat->purr() . "\n";
// Полиморфизм
$animals = [$dog, $cat, new Dog("Бобик", "Дворняга")];
foreach ($animals as $a) {
echo "{$a->describe()}: " . $a->speak() . "\n";
}
?>
Static, Abstract, Interfaces
<?php
// Static — принадлежит классу, не объекту
class Counter {
private static int $count = 0;
public function __construct() { self::$count++; }
public static function getCount(): int { return self::$count; }
}
new Counter(); new Counter(); new Counter();
echo Counter::getCount() . "\n"; // 3
// Abstract — нельзя создать экземпляр
abstract class Shape {
abstract public function area(): float;
public function describe(): string {
return get_class($this) . ": S=" . number_format($this->area(), 2);
}
}
class Circle extends Shape {
public function __construct(private float $r) {}
public function area(): float { return M_PI * $this->r ** 2; }
}
class Rectangle extends Shape {
public function __construct(private float $w, private float $h) {}
public function area(): float { return $this->w * $this->h; }
}
$shapes = [new Circle(5.0), new Rectangle(4.0, 6.0)];
foreach ($shapes as $s) echo $s->describe() . "\n";
// Interface — контракт без реализации
interface Serializable2 {
public function serialize2(): string;
}
interface Cacheable {
public function getCacheKey(): string;
public function getTtl(): int;
}
// Класс реализует несколько интерфейсов!
class UserCache implements Serializable2, Cacheable {
public function __construct(private string $name) {}
public function serialize2(): string { return json_encode(["name" => $this->name]); }
public function getCacheKey(): string { return "user_{$this->name}"; }
public function getTtl(): int { return 3600; }
}
$uc = new UserCache("Алиса");
echo $uc->serialize2() . "\n";
echo $uc->getCacheKey() . "\n";
?>
Traits — множественное наследование
<?php
trait Timestampable {
private ?DateTime $created_at = null;
public function setCreatedAt(): void { $this->created_at = new DateTime(); }
public function getCreatedAt(): ?string {
return $this->created_at?->format("Y-m-d H:i:s");
}
}
trait SoftDeletable {
private bool $deleted = false;
public function softDelete(): void { $this->deleted = true; }
public function restore(): void { $this->deleted = false; }
public function isDeleted(): bool { return $this->deleted; }
}
trait Validatable {
private array $errors = [];
public function addError(string $f, string $m): void { $this->errors[$f][] = $m; }
public function isValid(): bool { return empty($this->errors); }
public function getErrors(): array { return $this->errors; }
}
class Post {
use Timestampable, SoftDeletable, Validatable; // несколько трейтов!
public function __construct(public string $title, public string $content) {
$this->setCreatedAt();
if (empty($title)) $this->addError("title", "Заголовок обязателен");
if (strlen($content) < 10) $this->addError("content", "Слишком короткий");
}
}
$post = new Post("Привет PHP!", "Это мой первый пост о PHP!");
echo "Создан: " . $post->getCreatedAt() . "\n";
echo "Валидный: " . ($post->isValid() ? "Да" : "Нет") . "\n";
$post->softDelete();
echo "Удалён: " . ($post->isDeleted() ? "Да" : "Нет") . "\n";
$post->restore();
echo "Восстановлен!\n";
?>
Readonly и Constructor Promotion (PHP 8)
<?php
// Constructor Promotion — краткий синтаксис
class Point {
public function __construct(
public readonly float $x,
public readonly float $y,
public string $label = "P",
) {}
public function distance(Point $other): float {
return sqrt(($this->x - $other->x)**2 + ($this->y - $other->y)**2);
}
}
$p1 = new Point(0, 0, "O");
$p2 = new Point(3, 4, "A");
echo "Расстояние: " . $p1->distance($p2) . "\n"; // 5.0
// $p1->x = 5; // Error: readonly!
// Enum (PHP 8.1) — перечисления
enum Status {
case Active;
case Inactive;
case Pending;
}
enum Color: string {
case Red = 'red';
case Green = 'green';
case Blue = 'blue';
}
$s = Status::Active;
echo match($s) {
Status::Active => "Активен",
Status::Inactive => "Неактивен",
Status::Pending => "Ожидает",
} . "\n";
echo Color::Red->value . "\n"; // red
?>
Composer — менеджер пакетов
# Установка
php -r "copy('https://getcomposer.org/installer', 'c.php');"
php c.php
# Создание проекта
composer init
# Установка пакетов
composer require monolog/monolog
# composer.json:
# {
# "require": { "monolog/monolog": "^3.0" },
# "autoload": { "psr-4": { "App\\": "src/" } }
# }
<?php
require __DIR__ . '/vendor/autoload.php'; // автозагрузка классов
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(new StreamHandler('app.log', Logger::DEBUG));
$log->info('Запуск приложения');
$log->error('Что-то пошло не так!');
?>
Типичные ошибки
❌ Забыл $this->
class Foo {
public string $name;
public function greet() {
echo $name; // Notice: Undefined variable
echo $this->name; // ✅
}
}
❌ Обращение к private в подклассе
class A { private $x = 1; }
class B extends A {
public function show() { echo $this->x; } // Fatal Error!
}
// Используй protected вместо private
Итого
Классы: свойства + методы. $this — текущий объект. __construct — конструктор. public/protected/private. Наследование: extends, parent::. Abstract: нельзя создать экземпляр, обязывает реализовать методы. Interfaces: контракт, класс реализует несколько. Traits: переиспользуемый код, множественное наследование. Static: принадлежит классу. Readonly (PHP 8.1): нельзя изменить после создания. Enum (PHP 8.1): перечисления. Composer: менеджер пакетов, автозагрузка.
Дополнительные примеры и задачи
Пример 1: Работа с датами и временем
<?php
// PHP имеет богатые встроенные функции для работы с датами
$now = new DateTime();
echo $now->format('d.m.Y H:i:s') . "\n";
// Создание конкретной даты
$birthday = new DateTime('2012-03-15');
$today = new DateTime();
$diff = $today->diff($birthday);
echo "Прошло {$diff->y} лет {$diff->m} месяцев {$diff->d} дней\n";
// Добавление/вычитание времени
$next_week = new DateTime();
$next_week->modify('+7 days');
echo "Через неделю: " . $next_week->format('d.m.Y') . "\n";
$past = new DateTime();
$past->modify('-30 days');
echo "Месяц назад: " . $past->format('d.m.Y') . "\n";
// Форматы даты
$date = new DateTime('2024-01-15 14:30:00');
echo $date->format('d MMMM Y') . "\n"; // нет: это ICU формат
echo $date->format('j F Y') . "\n"; // 15 January 2024
echo $date->format('U') . "\n"; // Unix timestamp
// strtotime — удобное создание дат
$ts = strtotime('next Monday');
echo date('d.m.Y', $ts) . "\n";
$ts2 = strtotime('+2 months');
echo date('d.m.Y', $ts2) . "\n";
?>
Пример 2: Работа с файлами
<?php
// Запись в файл
$data = "Привет, PHP!\nВторая строка\n";
file_put_contents('/tmp/test.txt', $data);
echo "Записано: " . strlen($data) . " байт\n";
// Добавление к файлу
file_put_contents('/tmp/test.txt', "Третья строка\n", FILE_APPEND);
// Чтение файла
$content = file_get_contents('/tmp/test.txt');
echo $content;
// Чтение построчно
$lines = file('/tmp/test.txt', FILE_IGNORE_NEW_LINES);
foreach ($lines as $i => $line) {
echo "$i: $line\n";
}
// Проверка существования
if (file_exists('/tmp/test.txt')) {
echo "Файл существует\n";
echo "Размер: " . filesize('/tmp/test.txt') . " байт\n";
}
// Удаление
unlink('/tmp/test.txt');
echo "Файл удалён\n";
?>
Пример 3: Регулярные выражения
<?php
// Валидация email
function validateEmail(string $email): bool {
return (bool) preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email);
}
$emails = ["user@example.com", "invalid-email", "test@test.ru", "no_at_sign"];
foreach ($emails as $email) {
echo "$email: " . (validateEmail($email) ? "✅" : "❌") . "\n";
}
// Извлечение данных
$text = "Телефоны: +7(495)123-45-67 и 8-800-555-35-35";
preg_match_all('/[+7|8][\d\s\-()]+/', $text, $matches);
print_r($matches[0]);
// Замена
$html = "<p>Hello <b>World</b></p>";
$clean = preg_replace('/<[^>]+>/', '', $html);
echo $clean . "\n"; // Hello World
// Разбивка по паттерну
$str = "яблоко, банан;вишня дыня";
$fruits = preg_split('/[,;\s]+/', trim($str));
print_r(array_filter($fruits));
?>
Практическое задание: калькулятор оценок
<?php
class GradeCalculator {
private array $subjects = [];
public function addGrade(string $subject, float $grade): void {
if ($grade < 1 || $grade > 5) {
throw new InvalidArgumentException("Оценка должна быть от 1 до 5");
}
$this->subjects[$subject][] = $grade;
}
public function getAverage(string $subject): float {
if (!isset($this->subjects[$subject])) {
throw new RuntimeException("Предмет не найден: $subject");
}
return array_sum($this->subjects[$subject]) / count($this->subjects[$subject]);
}
public function getOverallAverage(): float {
$all = [];
foreach ($this->subjects as $grades) {
$all = array_merge($all, $grades);
}
return empty($all) ? 0.0 : array_sum($all) / count($all);
}
public function getReport(): string {
$lines = ["=== Отчёт об успеваемости ==="];
foreach ($this->subjects as $subject => $grades) {
$avg = $this->getAverage($subject);
$lines[] = sprintf("%-15s: %.2f (оценок: %d)", $subject, $avg, count($grades));
}
$lines[] = str_repeat("-", 35);
$lines[] = sprintf("%-15s: %.2f", "ИТОГО", $this->getOverallAverage());
return implode("\n", $lines);
}
}
$calc = new GradeCalculator();
$calc->addGrade("Математика", 5);
$calc->addGrade("Математика", 4);
$calc->addGrade("Математика", 5);
$calc->addGrade("Русский язык", 4);
$calc->addGrade("Русский язык", 5);
$calc->addGrade("Английский", 3);
$calc->addGrade("Английский", 4);
echo $calc->getReport() . "\n";
?>
Полезные PHP-паттерны
Паттерн Repository (хранилище данных)
<?php
interface UserRepositoryInterface {
public function findById(int $id): ?array;
public function findAll(): array;
public function save(array $user): int;
public function delete(int $id): bool;
}
class InMemoryUserRepository implements UserRepositoryInterface {
private array $users = [];
private int $nextId = 1;
public function findById(int $id): ?array {
return $this->users[$id] ?? null;
}
public function findAll(): array {
return array_values($this->users);
}
public function save(array $user): int {
$id = $user['id'] ?? $this->nextId++;
$this->users[$id] = array_merge($user, ['id' => $id]);
return $id;
}
public function delete(int $id): bool {
if (!isset($this->users[$id])) return false;
unset($this->users[$id]);
return true;
}
}
$repo = new InMemoryUserRepository();
$id1 = $repo->save(['name' => 'Алиса', 'email' => 'alice@test.com']);
$id2 = $repo->save(['name' => 'Боря', 'email' => 'bob@test.com']);
print_r($repo->findAll());
print_r($repo->findById($id1));
$repo->delete($id2);
echo "Осталось: " . count($repo->findAll()) . "\n";
?>