Глава 4.1: Классы и объекты — создание, свойства, методы, $this, конструктор, инкапсуляция
🎯 Что ты узнаешь
- Что такое ООП и зачем оно нужно
- Как создавать классы и объекты
- Что такое свойства и методы
- Как работает
$this - Зачем нужен конструктор
- Три уровня инкапсуляции: public, private, protected
🤔 Зачем вообще ООП?
До этого ты писал процедурный код: функции, переменные, условия. Это работает, но когда приложение растёт, код становится сложным:
// Процедурный стиль - всё разрозненно
$user_name = "John";
$user_email = "john@example.com";
$user_balance = 1000;
function getUserInfo($name, $email, $balance) {
return "$name ($email) - Balance: $$balance";
}
function addMoney($balance, $amount) {
return $balance + $amount;
}
// Легко запутаться, что с чем связаноООП (объектно-ориентированное программирование) позволяет группировать данные и функции, которые работают с этими данными, в одну сущность — класс.
// ООП - данные и поведение вместе
class User {
public $name;
public $email;
public $balance;
public function getInfo() {
return "$this->name ($this->email) - Balance: $$this->balance";
}
public function addMoney($amount) {
$this->balance += $amount;
}
}Преимущества:
- Код организован логически
- Данные защищены от случайного изменения
- Легко создавать несколько пользователей с одинаковым поведением
- Код переиспользуется и тестируется проще
📦 Класс и объект — в чём разница?
Класс — это чертёж
Класс — это шаблон, описание того, как должен выглядеть объект.
class Car {
public $brand;
public $color;
public $speed = 0;
public function accelerate() {
$this->speed += 10;
echo "Скорость: {$this->speed} км/ч\n";
}
}Сам по себе класс Car ничего не делает — это просто описание того, что такое машина.
Объект — это конкретный экземпляр
Объект — это конкретная машина, созданная по чертежу класса.
// Создаём объект (экземпляр класса)
$tesla = new Car();
$tesla->brand = "Tesla";
$tesla->color = "red";
$tesla->accelerate(); // Скорость: 10 км/ч
$bmw = new Car();
$bmw->brand = "BMW";
$bmw->color = "black";
$bmw->accelerate(); // Скорость: 10 км/ч
// Два разных объекта одного класса
var_dump($tesla === $bmw); // bool(false)Аналогия:
- Класс = чертёж дома
- Объект = реальный дом, построенный по чертежу
🏗️ Создание класса
Базовый синтаксис
class ИмяКласса {
// Свойства (данные)
public $свойство1;
public $свойство2;
// Методы (поведение)
public function метод() {
// код
}
}Правила именования
- Имя класса начинается с заглавной буквы
- Используй PascalCase (каждое слово с большой буквы)
- Один класс = один файл (хорошая практика)
// ✅ Хорошо
class User { }
class ProductRepository { }
class EmailService { }
// ❌ Плохо
class user { } // строчная буква
class product_repo { } // snake_case🎨 Свойства — данные объекта
Свойства (properties) — это переменные внутри класса.
Объявление свойств
class Product {
public $name;
public $price;
public $inStock = true; // Значение по умолчанию
}
$laptop = new Product();
$laptop->name = "MacBook Pro";
$laptop->price = 2500;
echo $laptop->name; // MacBook Pro
echo $laptop->inStock; // 1 (true)Типизация свойств (PHP 7.4+)
class Book {
public string $title;
public int $pages;
public float $price;
public bool $available = true;
}
$book = new Book();
$book->title = "1984";
$book->pages = 328;
$book->price = 15.99;⚠️ Ошибка типа:
$book->pages = "триста"; // TypeError: Cannot assign string to property Book::$pages of type int🔧 Методы — функции объекта
Методы (methods) — это функции внутри класса, которые определяют поведение объекта.
Создание методов
class BankAccount {
public $balance = 0;
public function deposit($amount) {
$this->balance += $amount;
echo "Пополнено на $$amount. Баланс: $$this->balance\n";
}
public function withdraw($amount) {
if ($amount > $this->balance) {
echo "Недостаточно средств!\n";
return false;
}
$this->balance -= $amount;
echo "Снято $$amount. Баланс: $$this->balance\n";
return true;
}
public function getBalance() {
return $this->balance;
}
}
$account = new BankAccount();
$account->deposit(1000); // Пополнено на $1000. Баланс: $1000
$account->withdraw(300); // Снято $300. Баланс: $700
$account->withdraw(1000); // Недостаточно средств!Типизация методов
class Calculator {
// Указываем типы параметров и возвращаемого значения
public function add(int $a, int $b): int {
return $a + $b;
}
public function divide(float $a, float $b): float {
if ($b === 0.0) {
throw new Exception("Деление на ноль!");
}
return $a / $b;
}
}
$calc = new Calculator();
echo $calc->add(5, 3); // 8
echo $calc->divide(10, 2.5); // 4.0🎯 $this — ссылка на текущий объект
$this — это специальная переменная, которая указывает на текущий объект внутри методов класса.
Как это работает?
class Person {
public $name;
public function introduce() {
// $this указывает на объект, который вызвал метод
echo "Привет, я {$this->name}!\n";
}
public function setName($name) {
// $this->name - свойство объекта
// $name - параметр метода
$this->name = $name;
}
}
$person1 = new Person();
$person1->name = "Алиса";
$person1->introduce(); // Привет, я Алиса!
$person2 = new Person();
$person2->name = "Боб";
$person2->introduce(); // Привет, я Боб!
// $this внутри каждого объекта указывает на СВОИ данные$this для вызова других методов
class Logger {
private $logs = [];
public function log($message) {
$this->logs[] = [
'message' => $message,
'time' => $this->getCurrentTime() // Вызов другого метода
];
}
private function getCurrentTime() {
return date('Y-m-d H:i:s');
}
public function getLogs() {
return $this->logs;
}
}
$logger = new Logger();
$logger->log("Пользователь вошёл");
$logger->log("Данные сохранены");
print_r($logger->getLogs());Важно:
$thisдоступен только внутри методов$thisне нужен в статических методах (об этом позже)- Используй
$this->для доступа к свойствам и методам объекта
🏗️ Конструктор — инициализация объекта
Конструктор (__construct) — это специальный метод, который автоматически вызывается при создании объекта.
Зачем нужен конструктор?
Без конструктора приходится вручную устанавливать все значения:
class User {
public $name;
public $email;
}
$user = new User();
$user->name = "John"; // Приходится делать вручную
$user->email = "john@example.com";С конструктором это происходит автоматически:
class User {
public $name;
public $email;
// Конструктор вызывается при new User(...)
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
echo "Пользователь $name создан!\n";
}
}
$user = new User("John", "john@example.com");
// Пользователь John создан!
echo $user->name; // JohnКонструктор с валидацией
class Product {
public $name;
public $price;
public function __construct($name, $price) {
if ($price < 0) {
throw new Exception("Цена не может быть отрицательной!");
}
$this->name = $name;
$this->price = $price;
}
}
$laptop = new Product("Laptop", 1500); // OK
$phone = new Product("Phone", -100); // Exception!Конструктор с PDO-подключением
Частый паттерн — создание подключения к БД в конструкторе:
class Database {
private $pdo;
public function __construct($host, $dbname, $user, $password) {
$dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
$this->pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
echo "Подключение к БД установлено\n";
}
public function query($sql) {
return $this->pdo->query($sql);
}
}
$db = new Database('localhost', 'myapp', 'root', '');
// Подключение к БД установленоProperty Promotion (PHP 8.0+)
В PHP 8+ можно объявлять свойства прямо в конструкторе:
// PHP 7.4
class User {
public $name;
public $email;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
}
// PHP 8.0+ - короче и удобнее!
class User {
public function __construct(
public string $name,
public string $email
) {
// Свойства создаются и инициализируются автоматически
}
}
$user = new User("Alice", "alice@example.com");
echo $user->name; // Alice🔒 Инкапсуляция — контроль доступа
Инкапсуляция — это сокрытие внутренних данных объекта и предоставление доступа только через методы.
Три уровня доступа
class Example {
public $publicProp; // Доступно везде
protected $protectedProp; // Доступно только в классе и наследниках
private $privateProp; // Доступно только внутри этого класса
}| Модификатор | Внутри класса | В наследниках | Снаружи |
|---|---|---|---|
public | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ❌ |
private | ✅ | ❌ | ❌ |
Public — открытый доступ
class Counter {
public $count = 0;
public function increment() {
$this->count++;
}
}
$counter = new Counter();
$counter->count = 100; // Можно менять напрямую
$counter->increment();
echo $counter->count; // 101Проблема: любой может изменить count на что угодно, даже на отрицательное число или строку.
Private — приватный доступ
class BankAccount {
private $balance = 0; // Никто не может изменить напрямую!
public function deposit($amount) {
if ($amount > 0) {
$this->balance += $amount;
}
}
public function withdraw($amount) {
if ($amount > 0 && $amount <= $this->balance) {
$this->balance -= $amount;
return true;
}
return false;
}
public function getBalance() {
return $this->balance;
}
}
$account = new BankAccount();
$account->deposit(1000);
// ❌ Ошибка! Нельзя получить доступ к приватному свойству
// echo $account->balance;
// ✅ Правильно - через публичный метод
echo $account->getBalance(); // 1000Преимущества:
- Данные защищены от случайного изменения
- Можно добавить валидацию в методы
- Легко изменить внутреннюю реализацию, не ломая код
Геттеры и сеттеры
Геттеры (getters) и сеттеры (setters) — методы для чтения и изменения приватных свойств:
class User {
private $email;
private $age;
// Геттер - читаем значение
public function getEmail() {
return $this->email;
}
// Сеттер - устанавливаем значение с валидацией
public function setEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new Exception("Некорректный email!");
}
$this->email = $email;
}
public function getAge() {
return $this->age;
}
public function setAge($age) {
if ($age < 0 || $age > 120) {
throw new Exception("Некорректный возраст!");
}
$this->age = $age;
}
}
$user = new User();
$user->setEmail("john@example.com"); // ✅ OK
$user->setAge(25); // ✅ OK
$user->setEmail("invalid"); // ❌ Exception!
$user->setAge(-5); // ❌ Exception!
echo $user->getEmail(); // john@example.comProtected — для наследников
protected используется, когда нужно скрыть данные от внешнего мира, но дать доступ классам-наследникам (об этом в следующей главе):
class Animal {
protected $name;
public function __construct($name) {
$this->name = $name;
}
}
class Dog extends Animal {
public function bark() {
// Можем использовать $name, т.к. он protected
echo "$this->name говорит: Гав!\n";
}
}
$dog = new Dog("Рекс");
$dog->bark(); // Рекс говорит: Гав!
// ❌ Ошибка! protected недоступен снаружи
// echo $dog->name;🛠️ Практические примеры
Пример 1: Класс Task (задача)
class Task {
private $title;
private $description;
private $completed = false;
private $createdAt;
public function __construct($title, $description) {
$this->title = $title;
$this->description = $description;
$this->createdAt = new DateTime();
}
public function complete() {
$this->completed = true;
echo "Задача '{$this->title}' выполнена!\n";
}
public function isCompleted() {
return $this->completed;
}
public function getInfo() {
$status = $this->completed ? 'выполнена' : 'в процессе';
return "{$this->title} - {$status}";
}
}
$task = new Task("Изучить ООП", "Прочитать главу 4.1");
echo $task->getInfo(); // Изучить ООП - в процессе
$task->complete(); // Задача 'Изучить ООП' выполнена!
echo $task->getInfo(); // Изучить ООП - выполненаПример 2: Класс ShoppingCart (корзина)
class ShoppingCart {
private $items = [];
public function addItem($name, $price, $quantity = 1) {
$this->items[] = [
'name' => $name,
'price' => $price,
'quantity' => $quantity
];
echo "Добавлено: $name (x$quantity) - $$price\n";
}
public function getTotal() {
$total = 0;
foreach ($this->items as $item) {
$total += $item['price'] * $item['quantity'];
}
return $total;
}
public function getItemCount() {
return count($this->items);
}
public function clear() {
$this->items = [];
echo "Корзина очищена\n";
}
}
$cart = new ShoppingCart();
$cart->addItem("Ноутбук", 1500, 1);
$cart->addItem("Мышь", 25, 2);
echo "Товаров: " . $cart->getItemCount() . "\n"; // 2
echo "Итого: $" . $cart->getTotal() . "\n"; // $1550
$cart->clear();Пример 3: Класс Validator
class Validator {
private $errors = [];
public function validateEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->errors[] = "Некорректный email: $email";
return false;
}
return true;
}
public function validateLength($string, $min, $max) {
$length = strlen($string);
if ($length < $min || $length > $max) {
$this->errors[] = "Длина должна быть от $min до $max символов";
return false;
}
return true;
}
public function hasErrors() {
return !empty($this->errors);
}
public function getErrors() {
return $this->errors;
}
}
$validator = new Validator();
$validator->validateEmail("test@example.com"); // true
$validator->validateEmail("invalid"); // false
$validator->validateLength("abc", 5, 10); // false
if ($validator->hasErrors()) {
print_r($validator->getErrors());
}⚠️ Частые ошибки
1. Забыл $this->
class Product {
public $price = 100;
public function getPrice() {
return price; // ❌ Ошибка! Undefined variable
}
}
// ✅ Правильно
public function getPrice() {
return $this->price;
}2. Используешь $this-> для вызова свойства, а не $this->
class User {
public $name = "John";
public function getName() {
return $this->$name; // ❌ Попытка обратиться к переменной $name
}
}
// ✅ Правильно
return $this->name;3. Доступ к приватным свойствам извне
class Account {
private $balance = 1000;
}
$acc = new Account();
echo $acc->balance; // ❌ Fatal error: Cannot access private property4. Забыл указать public, private или protected
class User {
$name; // ❌ Синтаксическая ошибка!
}
// ✅ Правильно
class User {
public $name;
}📝 Упражнения
Упражнение 1: Класс Rectangle
Создай класс Rectangle (прямоугольник) с:
- Приватными свойствами
$widthи$height - Конструктором, который принимает ширину и высоту
- Методом
getArea()— возвращает площадь - Методом
getPerimeter()— возвращает периметр - Методом
isSquare()— проверяет, является ли прямоугольник квадратом
$rect = new Rectangle(5, 10);
echo $rect->getArea(); // 50
echo $rect->getPerimeter(); // 30
echo $rect->isSquare(); // false
$square = new Rectangle(5, 5);
echo $square->isSquare(); // true💡 Решение
class Rectangle {
private $width;
private $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
public function getPerimeter() {
return 2 * ($this->width + $this->height);
}
public function isSquare() {
return $this->width === $this->height;
}
}Упражнение 2: Класс BankAccount с историей
Расширь класс BankAccount:
- Добавь приватный массив
$transactionsдля хранения истории операций - В методах
deposit()иwithdraw()записывай операции в историю - Добавь метод
getHistory(), который возвращает все транзакции
$account = new BankAccount(1000);
$account->deposit(500);
$account->withdraw(200);
$account->deposit(100);
print_r($account->getHistory());
// [
// ['type' => 'deposit', 'amount' => 500, 'balance' => 1500],
// ['type' => 'withdraw', 'amount' => 200, 'balance' => 1300],
// ['type' => 'deposit', 'amount' => 100, 'balance' => 1400]
// ]💡 Решение
class BankAccount {
private $balance;
private $transactions = [];
public function __construct($initialBalance = 0) {
$this->balance = $initialBalance;
}
public function deposit($amount) {
$this->balance += $amount;
$this->transactions[] = [
'type' => 'deposit',
'amount' => $amount,
'balance' => $this->balance
];
}
public function withdraw($amount) {
if ($amount <= $this->balance) {
$this->balance -= $amount;
$this->transactions[] = [
'type' => 'withdraw',
'amount' => $amount,
'balance' => $this->balance
];
return true;
}
return false;
}
public function getHistory() {
return $this->transactions;
}
public function getBalance() {
return $this->balance;
}
}Упражнение 3: Класс TodoList
Создай класс TodoList для управления списком задач:
- Приватный массив
$tasks - Метод
addTask($title)— добавляет задачу - Метод
completeTask($index)— помечает задачу выполненной - Метод
getTasks()— возвращает все задачи - Метод
getCompletedCount()— возвращает количество выполненных задач
Каждая задача должна быть массивом: ['title' => '...', 'completed' => false]
$todo = new TodoList();
$todo->addTask("Купить молоко");
$todo->addTask("Выучить PHP");
$todo->completeTask(0);
echo $todo->getCompletedCount(); // 1🎓 Что ты должен запомнить
✅ Класс — это шаблон, объект — конкретный экземпляр
✅ Создаём объект через new ИмяКласса()
✅ Свойства — данные объекта, методы — его поведение
✅ $this указывает на текущий объект внутри методов
✅ Конструктор __construct() вызывается автоматически при создании объекта
✅ Инкапсуляция — сокрытие данных через private и доступ через геттеры/сеттеры
✅ public — доступно везде, private — только внутри класса, protected — в классе и наследниках
🚀 Что дальше?
Ты научился создавать классы, объекты и защищать данные через инкапсуляцию. Это основа ООП!
В следующей главе:
- Наследование (
extends) - Полиморфизм
- Абстрактные классы
- Как один класс может расширять другой
Готов копать глубже? 🔥