Объектно-ориентированное программирование: методы классов в PHP

Раздел: PHP разработка -> Объектно-ориентированное программирование в PHP

Основы методов класса в PHP

Метод класса представляет собой функцию, объявленную внутри класса. Он определяет поведение объектов и может работать с их свойствами. Основной синтаксис включает модификатор доступа (public, private, protected), ключевое слово function и имя метода. Вызов метода выполняется через оператор -> для экземпляра или :: для статического контекста.


class User {
    public string $name;

    public function greet(): string {
        return "Привет, " . $this->name . "!";
    }
}

$user = new User();
$user->name = "Анна";
echo $user->greet();
Привет, Анна!

Пояснение: метод greet() использует псевдопеременную $this для доступа к свойству объекта. Модификатор public делает метод доступным извне.

Типичные ошибки:

  • Попытка обратиться к $this в статическом методе - вызовет фатальную ошибку.
  • Использование необъявленного свойства без магического метода __get приводит к замечанию или исключению.
  • Отсутствие модификатора доступа - метод считается public, что может нарушить инкапсуляцию.

Как создать метод, вызываемый без создания объекта?

Для вызова метода без инстанциирования класса используется ключевое слово static. Статические методы принадлежат классу, а не экземпляру.


class MathHelper {
    public static function sum(int $a, int $b): int {
        return $a + $b;
    }
}

echo MathHelper::sum(3, 5); // 8

Проблемы и решения: В статическом методе недоступна $this. Если требуется доступ к свойствам экземпляра, метод должен быть нестатическим. Для позднего статического связывания используйте static:: вместо self::.

Цель: Реализация утилитарных функций или фабричных методов.

Какие специальные методы автоматически вызываются PHP?

Магические методы начинаются с двойного подчёркивания (__). Наиболее часто используемые: __construct, __destruct, __get, __set, __call, __callStatic, __toString, __invoke.


class MagicClass {
    private array $data = [];

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

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

    public function __call(string $name, array $arguments) {
        echo "Вызван метод $name с аргументами " . implode(', ', $arguments);
    }

    public static function __callStatic(string $name, array $arguments) {
        echo "Статически вызван $name";
    }

    public function __toString(): string {
        return "Объект класса " . __CLASS__;
    }
}

$obj = new MagicClass();
$obj->foo = "bar";
echo $obj->foo; // bar
$obj->unknownMethod(1, 2); // Вызван метод unknownMethod с аргументами 1, 2
MagicClass::staticMethod(); // Статически вызван staticMethod
echo $obj; // Объект класса MagicClass

Ошибки: Неправильное использование магических методов может снизить производительность и сделать код менее предсказуемым. Например, __get вызывается для каждого обращения к неопределённому свойству, что маскирует опечатки.

Цель: Перехват доступа к необъявленным свойствам/методам, автоматическая инициализация, сериализация, создание «перегрузки» свойств.

Как обеспечить типовую безопасность в методах?

PHP поддерживает объявление типов параметров и возвращаемого значения. Начиная с PHP 7, можно указать скалярные типы, а также классы, интерфейсы, callable, array и iterable. Для строгой проверки используется declare(strict_types=1) на уровне файла.


declare(strict_types=1);

class Calculator {
    public function divide(float $a, float $b): float {
        if ($b === 0.0) {
            throw new \InvalidArgumentException("Деление на ноль");
        }
        return $a / $b;
    }
}

$calc = new Calculator();
echo $calc->divide(10, 2); // 5
// echo $calc->divide(10, 0); // Исключение

Проблемы: Без strict_types PHP может неявно преобразовывать типы (например, строку '5' в число). Это приводит к неожиданному поведению. Рекомендуется всегда включать строгую типизацию.

Цель: Уменьшение ошибок во время выполнения, самодокументируемый код, улучшение поддержки IDE.

Как определить обязательные методы для классов?

Абстрактные методы объявляются в абстрактном классе или интерфейсе. Классы, наследующие абстрактный класс или реализующие интерфейс, обязаны определить эти методы.


interface Drawable {
    public function draw(): string;
}

abstract class Shape implements Drawable {
    abstract protected function area(): float;
}

class Circle extends Shape {
    private float $radius;

    public function __construct(float $radius) {
        $this->radius = $radius;
    }

    public function draw(): string {
        return "Рисуем круг радиусом {$this->radius}";
    }

    protected function area(): float {
        return M_PI * $this->radius ** 2;
    }
}

Ошибки: Если класс не реализует хотя бы один объявленный в интерфейсе метод, возникает фатальная ошибка. Абстрактные классы не могут быть инстанциированы.

Цель: Унификация API, контрактное программирование, облегчение расширения.

Как повторно использовать методы в разных классах?

Трейты позволяют группировать методы и включать их в классы с помощью оператора use. Трейты не могут быть инстанциированы сами по себе, но их методы становятся методами класса.


trait Timestampable {
    public function getTimestamp(): string {
        return date('Y-m-d H:i:s');
    }
}

class Post {
    use Timestampable;
}

class Comment {
    use Timestampable;
}

$post = new Post();
echo $post->getTimestamp(); // 2025-03-18 12:34:56

Конфликты: Если два трейта или класс имеют методы с одинаковым именем, возникает фатальная ошибка. Для разрешения конфликта используется insteadof или as.

Цель: Горзонтальное повторное использование кода, избежание наследования.

Как передать функцию или метод в качестве аргумента?

Тип callable позволяет принимать любую вызываемую сущность: имя функции, замыкание, статический метод или метод объекта. Также можно использовать __invoke для объектов.


function hello(callable $formatter): string {
    return $formatter("Мир");
}

$closure = function(string $subject): string {
    return "Привет, $subject";
};

echo hello($closure); // Привет, Мир

class Greeter {
    public function __invoke(string $subject): string {
        return "Здравствуй, $subject";
    }
}

echo hello(new Greeter()); // Здравствуй, Мир

Ошибки: Передача невызываемого значения (например, строки, не являющейся именем функции) вызывает исключение TypeError. Для методов объекта используйте массив [$object, 'methodName'].

Цель: Стратегии, коллбэки, функции высшего порядка, внедрение зависимостей.

Расширенные примеры методов класса

Fluent интерфейс (цепочка вызовов)

Пример

class QueryBuilder {
    private array $select = ['*'];
    private string $from = '';
    private array $where = [];

    public function select(string ...$columns): self {
        $this->select = $columns;
        return $this;
    }

    public function from(string $table): self {
        $this->from = $table;
        return $this;
    }

    public function where(string $condition): self {
        $this->where[] = $condition;
        return $this;
    }

    public function build(): string {
        $sql = "SELECT " . implode(', ', $this->select);
        $sql .= " FROM " . $this->from;
        if (!empty($this->where)) {
            $sql .= " WHERE " . implode(' AND ', $this->where);
        }
        return $sql;
    }
}

$query = (new QueryBuilder())
    ->select('id', 'name', 'email')
    ->from('users')
    ->where('status = 1')
    ->where('created_at > NOW()')
    ->build();

echo $query;
SELECT id, name, email FROM users WHERE status = 1 AND created_at > NOW()

Методы с вариадическими аргументами (...)

Пример

class Logger {
    public static function log(string $level, string ...$messages): void {
        foreach ($messages as $msg) {
            echo "[$level] $msg\n";
        }
    }
}

Logger::log('INFO', 'Запуск', 'Обработка запроса', 'Завершение');
[INFO] Запуск
[INFO] Обработка запроса
[INFO] Завершение

Метод-генератор (yield)

Пример

class Fibonacci {
    public static function generate(int $limit): Generator {
        $a = 0;
        $b = 1;
        for ($i = 0; $i < $limit; $i++) {
            yield $a;
            [$a, $b] = [$b, $a + $b];
        }
    }
}

foreach (Fibonacci::generate(10) as $number) {
    echo "$number ";
}
0 1 1 2 3 5 8 13 21 34

Динамические методы через __call (прокси)

Пример

class Repository {
    private array $data;

    public function __construct(array $data) {
        $this->data = $data;
    }

    public function __call(string $name, array $arguments) {
        if (preg_match('/^findBy(\w+)$/', $name, $matches)) {
            $property = lcfirst($matches[1]);
            $value = $arguments[0] ?? null;
            return array_filter($this->data, fn($item) => ($item[$property] ?? null) === $value);
        }
        throw new BadMethodCallException("Метод $name не найден");
    }
}

$repo = new Repository([
    ['id' => 1, 'name' => 'Alice'],
    ['id' => 2, 'name' => 'Bob'],
]);

print_r($repo->findByName('Bob'));
Array
(
    [1] => Array
        (
            [id] => 2
            [name] => Bob
        )
)

Использование Reflection для вызова метода

Пример

class Secret {
    private function hiddenMethod(int $x, int $y): int {
        return $x * $y;
    }
}

$method = new ReflectionMethod(Secret::class, 'hiddenMethod');
$method->setAccessible(true);
$obj = new Secret();
echo $method->invoke($obj, 7, 6);
42

Методы с Union Type (PHP 8+)

Пример

class Response {
    public static function from(mixed $data): string|array|int {
        if (is_string($data)) {
            return "Получена строка";
        }
        if (is_array($data)) {
            return ['type' => 'array', 'count' => count($data)];
        }
        return (int) $data;
    }
}

print_r(Response::from([1,2,3]));
echo Response::from("hello");
echo Response::from(100);
Array
(
    [type] => array
    [count] => 3
)
Получена строка100

Методы с named arguments (PHP 8+)

Пример

class Point {
    public function __construct(private float $x, private float $y, private float $z = 0) {}
    public function translate(float $dx, float $dy, float $dz = 0): self {
        $this->x += $dx;
        $this->y += $dy;
        $this->z += $dz;
        return $this;
    }
}

$point = new Point(x: 1, y: 2);
$point->translate(dx: 5, dy: 10, dz: 3);
var_dump($point);
object(Point)#1 (3) {
  ["x":"Point":private]=>
  float(6)
  ["y":"Point":private]=>
  float(12)
  ["z":"Point":private]=>
  float(3)
}

Метод класса PHP - comments

En
Php class method (php)