Реализация доступа к свойствам через методы
Зачем в объектно-ориентированном PHP нужны геттеры и сеттеры?
Геттеры и сеттеры это методы, которые позволяют получать и изменять значения приватных или защищённых свойств объекта. Они реализуют принцип инкапсуляции: скрывают внутреннюю реализацию и дают возможность контролировать доступ к данным (валидация, логирование, вычисление производных значений).
Основной подход: явные методы get и set
Как организовать контролируемый доступ к приватным свойствам?
Самый эффективный и явный способ — определить для каждого свойства два публичных метода: getName() и setName() (или setValue()/getValue()). Это даёт полный контроль над тем, какие данные попадают в объект и как они возвращаются.
class User {
private string $name;
private int $age;
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$name = trim($name);
if ($name === '') {
throw new InvalidArgumentException('Имя не может быть пустым');
}
$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;
}
}классы в php файлы (определение классов в php-файлах)
В примере сеттер setName() обрезает пробелы и проверяет, что имя не пустое. Сеттер setAge() ограничивает диапазон допустимых значений. Геттер getName() может возвращать имя в определённом формате (например, с заглавной буквы).
Типичная ошибка:
Свойство объявлено как public, и к нему можно обратиться напрямую, минуя методы. Решение: всегда объявлять свойство с модификатором private или protected.
Другая проблема: забывают вызывать родительский сеттер при наследовании. Решение: в дочернем классе явно вызывать parent::setName().
Вариант 1: Магические методы __get и __set
Как обращаться к приватным свойствам как к публичным, но с автоматической обработкой?
Магические методы __get($name) и __set($name, $value) перехватывают обращение к недоступным (или несуществующим) свойствам. Это позволяет писать компактный код, но ценой производительности и автодополнения.
class MagicUser {
private array $data = [];
public function __get(string $name): mixed {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
throw new OutOfBoundsException("Свойство $name не определено");
}
public function __set(string $name, mixed $value): void {
if ($name === 'age' && ($value < 0 || $value > 150)) {
throw new InvalidArgumentException('Некорректный возраст');
}
$this->data[$name] = $value;
}
}
$user = new MagicUser();
$user->name = 'Анна'; // вызывает __set
echo $user->name; // вызывает __getPhp interface файл (файл с php-интерфейсом (interface))
Возникающие проблемы:
- Снижение производительности из-за дополнительного вызова магического метода.
- Отсутствие подсказок в IDE и автодополнения.
- Сложность отладки: ошибка может возникнуть в любом месте обращения к свойству.
Решение: использовать только когда количество свойств динамическое или когда нужна единая точка доступа, например, для ORM. Для большинства классов предпочтительны явные методы.
Вариант 2: Типизированные свойства с сеттерами
Как гарантировать тип свойства и добавить дополнительную валидацию?
Начиная с PHP 7.4 можно указывать тип свойства. Если свойство публичное, тип проверяется при прямом присваивании, но дополнительная логика (диапазон, формат) не применяется. Лучше сделать свойство приватным и использовать сеттер с типизированным параметром.
class Product {
private float $price;
public function setPrice(float $price): void {
if ($price < 0) {
throw new InvalidArgumentException('Цена не может быть отрицательной');
}
$this->price = round($price, 2);
}
public function getPrice(): float {
return $this->price;
}
}Php public var (public свойства класса в php)
Тип float в сеттере гарантирует, что будет передано число с плавающей точкой. Дополнительная проверка на отрицательность и округление до двух знаков обеспечивает бизнес-логику.
Типичная ошибка:
Объявление свойства как public float и присвоение напрямую ($product->price = -10) не вызовет ошибку типа, но отрицательное значение пройдёт. Решение: делать свойство приватным.
Вариант 3: Readonly свойства (PHP 8.1+)
Как создать неизменяемое свойство после инициализации?
Модификатор readonly запрещает изменение свойства после завершения конструктора. Это упрощает иммутабельные объекты и исключает необходимость в сеттерах для свойств, которые должны быть установлены только один раз.
class Order {
public readonly string $id;
public readonly DateTimeImmutable $createdAt;
public function __construct(string $id) {
$this->id = $id;
$this->createdAt = new DateTimeImmutable();
}
}
$order = new Order('ORD-001');
echo $order->id; // ORD-001
// $order->id = 'NEW'; // Ошибка: Cannot modify readonly propertyPhp get set (геттеры и сеттеры в php)
Readonly свойства не могут быть изменены после вызова конструктора, даже внутри того же класса. Для получения значения используется геттер, но он не нужен, так как свойство публичное (но только для чтения).
Возникающая проблема:
Если требуется изменить readonly свойство в другом методе (например, при обновлении заказа), это невозможно. Решение: не делать свойство readonly, если нужна изменяемость. Либо создать новый объект с обновлёнными данными.
Вариант 4: Fluent сеттеры (цепочка вызовов)
Как реализовать последовательную установку нескольких свойств?
Сеттеры могут возвращать $this, что позволяет вызывать их цепочкой. Это улучшает читаемость при инициализации объекта.
class Builder {
private array $options = [];
public function setOption(string $key, mixed $value): static {
$this->options[$key] = $value;
return $this;
}
public function getOptions(): array {
return $this->options;
}
}
$config = (new Builder())
->setOption('host', 'localhost')
->setOption('port', 8080)
->getOptions();Сложность:
При цепочке методов сложнее отлаживать, какое именно присваивание вызвало ошибку. Решение: на каждый сеттер ставить проверку и выбрасывать исключение с понятным сообщением.
Какой вариант выбрать?
Для большинства проектов оптимальны явные методы get/set с валидацией. Readonly свойства хороши для значений, которые не изменяются после создания. Магические методы стоит применять только в случаях, когда необходима динамическая структура свойств. Fluent-сеттеры удобны для построителей.
Расширенные примеры работы с геттерами и сеттерами
Пример 1: Валидация email с помощью filter_var
class User {
private string $email;
public function getEmail(): string {
return $this->email;
}
public function setEmail(string $email): void {
$email = trim(strtolower($email));
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Некорректный email: $email");
}
$this->email = $email;
}
}
$user = new User();
try {
$user->setEmail('test@example.com');
echo $user->getEmail(); // test@example.com
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}
// Попытка установить неверный email
try {
$user->setEmail('not-email');
} catch (InvalidArgumentException $e) {
echo $e->getMessage(); // Некорректный email: not-email
}test@example.com Некорректный email: not-email
Пример 2: Вычисляемое свойство fullName
class Person {
private string $firstName;
private string $lastName;
public function __construct(string $firstName, string $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public function getFirstName(): string {
return $this->firstName;
}
public function getLastName(): string {
return $this->lastName;
}
public function getFullName(): string {
return $this->firstName . ' ' . $this->lastName;
}
public function setFirstName(string $firstName): void {
$this->firstName = $firstName;
}
public function setLastName(string $lastName): void {
$this->lastName = $lastName;
}
}
$person = new Person('Иван', 'Петров');
echo $person->getFullName(); // Иван Петров
$person->setLastName('Сидоров');
echo $person->getFullName(); // Иван СидоровИван Петров Иван Сидоров
Пример 3: Магические методы с логированием
class LoggerProperty {
private array $properties = [];
private array $log = [];
public function __set(string $name, mixed $value): void {
$this->log[] = [
'action' => 'set',
'property' => $name,
'value' => $value,
'time' => microtime(true)
];
$this->properties[$name] = $value;
}
public function __get(string $name): mixed {
if (array_key_exists($name, $this->properties)) {
return $this->properties[$name];
}
return null;
}
public function getLog(): array {
return $this->log;
}
}
$obj = new LoggerProperty();
$obj->username = 'admin';
$obj->role = 'editor';
print_r($obj->getLog());Array
(
[0] => Array
(
[action] => set
[property] => username
[value] => admin
[time] => 1234567890.1234
)
[1] => Array
(
[action] => set
[property] => role
[value] => editor
[time] => 1234567890.1235
)
)Пример 4: Readonly свойство в наследовании
abstract class Entity {
public readonly string $uuid;
public function __construct(string $uuid) {
$this->uuid = $uuid;
}
}
class UserEntity extends Entity {
public string $name;
public function __construct(string $uuid, string $name) {
parent::__construct($uuid);
$this->name = $name;
}
}
$user = new UserEntity('123e4567-e89b-12d3-a456-426614174000', 'Alice');
echo $user->uuid; // 123e4567-e89b-12d3-a456-426614174000
echo $user->name; // Alice
// Попытка изменить uuid вызовет ошибку
// $user->uuid = 'new';123e4567-e89b-12d3-a456-426614174000 Alice
Пример 5: Комбинирование типизированного свойства, сеттера и геттера с преобразованием
class Temperature {
private float $celsius;
public function setCelsius(float $celsius): void {
if ($celsius < -273.15) {
throw new InvalidArgumentException('Температура ниже абсолютного нуля');
}
$this->celsius = $celsius;
}
public function getCelsius(): float {
return round($this->celsius, 1);
}
public function getFahrenheit(): float {
return round($this->celsius * 9/5 + 32, 1);
}
public function setFahrenheit(float $fahrenheit): void {
$this->setCelsius(($fahrenheit - 32) * 5/9);
}
}
$temp = new Temperature();
$temp->setFahrenheit(100);
echo $temp->getCelsius(); // 37.8
echo $temp->getFahrenheit(); // 100.037.8 100.0