Особенности статических классов в PHP (статический класс)

Раздел: ООП в PHP -> Статические методы

Статические классы в PHP: возможности и ограничения

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

Наиболее эффективный способ - объявить все методы и свойства статическими, а конструктор сделать приватным. Это превращает класс в статический класс (utility class).


<?php
class MathHelper {
    private function __construct() {}

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

    public static function multiply(int $a, int $b): int {
        return $a * $b;
    }
}

// Использование
echo MathHelper::add(5, 3); // 8
echo MathHelper::multiply(4, 2); // 8
?>
  

Index php static (статические методы и свойства в php)

Пояснение:

  • Приватный конструктор private function __construct() предотвращает создание объектов через new MathHelper().
  • Все методы объявлены static, поэтому вызываются через ClassName::method().
  • Такой класс удобен для группировки вспомогательных функций, не требующих состояния.

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

  • Попытка инстанцировать класс: Fatal error: Uncaught Error: Call to private MathHelper::__construct(). Решение - вызывать методы только статически.
  • Сохранение состояния через статические свойства может привести к неожиданным эффектам при параллельных запросах - используйте только константы или неизменяемые данные.

Цель использования: утилитарные классы (работа с числами, строками, датами), где не нужен экземпляр и контекст.

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

Паттерн Singleton позволяет иметь ровно один объект класса, но методы могут быть как статическими, так и обычными. В отличие от статического класса, здесь конструктор приватный, но есть статический метод getInstance().


<?php
class Logger {
    private static ?Logger $instance = null;
    private array $logs = [];

    private function __construct() {}

    public static function getInstance(): Logger {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function log(string $message): void {
        $this->logs[] = $message;
    }

    public function getLogs(): array {
        return $this->logs;
    }
}

$logger = Logger::getInstance();
$logger->log('Start');
$logger2 = Logger::getInstance();
$logger2->log('End');
print_r($logger->getLogs());
// Array ( [0] => Start [1] => End )
?>
  

Php static class (статические классы в php)

Пояснение:

  • Статическое свойство хранит единственный экземпляр.
  • Метод getInstance() создаёт объект при первом вызове и возвращает его всегда.
  • Методы не статические - работают с состоянием объекта.

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

  • Забыть объявить конструктор приватным - тогда можно создать несколько экземпляров.
  • Синглтон сложно тестировать (глобальное состояние). Решение - внедрение зависимости через интерфейс.
  • При сериализации/десериализации может нарушиться уникальность - нужно реализовать __wakeup() как приватный.

Цель: управление общими ресурсами (логирование, кэш, подключение к БД).

Как использовать статические методы в классе, который также может быть инстанциирован?

PHP позволяет сочетать статические и нестатические методы в одном классе. Это удобно, когда часть функциональности не требует объекта, а другая - работает с экземпляром.


<?php
class User {
    private string $name;
    private static int $count = 0;

    public function __construct(string $name) {
        $this->name = $name;
        self::$count++;
    }

    public static function getCount(): int {
        return self::$count;
    }

    public function getName(): string {
        return $this->name;
    }
}

$user1 = new User('Alice');
$user2 = new User('Bob');
echo User::getCount(); // 2
echo $user1->getName(); // Alice
?>
  

Пояснение: статическое свойство $count хранит общее количество созданных объектов. Метод getCount() вызывается через класс, а не через объект.

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

  • Из статического метода нельзя напрямую обратиться к $this (нет контекста объекта).
  • Если в статическом методе используется self::, он не переопределяется в наследниках - нужно static:: для позднего связывания.

Цель: подсчёт экземпляров, фабричные методы, валидаторы без состояния.

Как в статических методах обратиться к вызывающему классу, а не к классу, где метод определён?

Используется позднее статическое связывание (late static binding) с ключевым словом static:: вместо self::. Это позволяет переопределять статические методы в наследниках.


<?php
class Base {
    protected static string $name = 'Base';

    public static function who(): string {
        return self::$name; // или static::$name
    }

    public static function whoLate(): string {
        return static::$name;
    }
}

class Child extends Base {
    protected static string $name = 'Child';
}

echo Base::who();      // Base
echo Child::who();     // Base (self::) 
echo Base::whoLate();  // Base
echo Child::whoLate(); // Child (static::)
?>
  

Пояснение: при вызове Child::who() внутри метода self::$name ссылается на класс, где метод определён (Base). А static::$name разрешается во время выполнения на основе вызывающего класса (Child).

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

  • Забыть использовать static:: в коде, который предназначен для переопределения в подклассах.
  • Путаница между self, static и parent.

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

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

Пример 1: Статический класс для работы с кэшем (с конфигурацией)

Пример

<?php
class Cache {
    private static array $store = [];
    private static int $ttl = 3600; // время жизни по умолчанию

    private function __construct() {}

    public static function setTtl(int $seconds): void {
        self::$ttl = $seconds;
    }

    public static function set(string $key, mixed $value, ?int $ttl = null): void {
        $expire = time() + ($ttl ?? self::$ttl);
        self::$store[$key] = ['value' => $value, 'expire' => $expire];
    }

    public static function get(string $key): mixed {
        if (!isset(self::$store[$key])) {
            return null;
        }
        $data = self::$store[$key];
        if (time() > $data['expire']) {
            unset(self::$store[$key]);
            return null;
        }
        return $data['value'];
    }

    public static function flush(): void {
        self::$store = [];
    }
}

// Использование
Cache::set('user_1', ['name' => 'Alice']);
Cache::setTtl(60);
$user = Cache::get('user_1');
var_dump($user);
// array(1) { ["name"]=> string(5) "Alice" }
?>

Пояснение: статический класс Cache хранит данные в статическом массиве. Метод setTtl меняет время жизни по умолчанию. Все методы статические, конструктор приватный - экземпляр создать нельзя.

Пример 2: Статический трейт с общей функциональностью

Пример

<?php
trait SingletonTrait {
    private static ?self $instance = null;

    private function __construct() {}
    private function __clone() {}
    private function __wakeup() {}

    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

class Database {
    use SingletonTrait;

    public function query(string $sql): array {
        // эмуляция
        return ['result' => $sql];
    }
}

$db = Database::getInstance();
var_dump($db->query('SELECT 1'));
?>
array(1) {
  ["result"]=>
  string(8) "SELECT 1"
}

Пояснение: трейт реализует синглтон. Класс Database использует его и получает единственный экземпляр. Трейт можно переиспользовать в любом классе.

Пример 3: Позднее связывание в статической фабрике

Пример

<?php
abstract class Animal {
    abstract protected static function getSound(): string;

    public static function speak(): void {
        echo static::getSound() . PHP_EOL;
    }
}

class Dog extends Animal {
    protected static function getSound(): string {
        return 'Woof!';
    }
}

class Cat extends Animal {
    protected static function getSound(): string {
        return 'Meow!';
    }
}

Dog::speak(); // Woof!
Cat::speak(); // Meow!
?>
Woof!
Meow!

Пояснение: абстрактный статический метод getSound определён в наследниках. Метод speak() использует static:: для вызова реализации конкретного класса. Это позволяет строить иерархии с полиморфизмом на уровне классов.

Пример 4: Статический класс с константами и неизменяемыми настройками

Пример

<?php
class AppConfig {
    private function __construct() {}

    public const APP_NAME = 'MyApp';
    public const VERSION = '1.0.0';
    private const SECRET_KEY = 's3cr3t';

    public static function getSecret(): string {
        return self::SECRET_KEY;
    }

    public static function isDebug(): bool {
        return defined('DEBUG') && DEBUG === true;
    }
}

// Использование
echo AppConfig::APP_NAME; // MyApp
echo AppConfig::getSecret(); // s3cr3t
?>

Пояснение: класс предоставляет доступ к конфигурационным константам. Приватная константа доступна только через статический метод. Конструктор приватный - инстанцирование запрещено.

Пример 5: Утилитарный класс для валидации (с проблемами статического состояния)

Пример

<?php
class Validator {
    private static array $errors = [];

    public static function validateEmail(string $email): bool {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            self::$errors[] = 'Invalid email';
            return false;
        }
        return true;
    }

    public static function getErrors(): array {
        return self::$errors;
    }

    public static function clearErrors(): void {
        self::$errors = [];
    }
}

Validator::validateEmail('test@');
Validator::validateEmail('user@example.com');
print_r(Validator::getErrors());
// Array ( [0] => Invalid email )
?>
Array
(
    [0] => Invalid email
)

Проблема: статический массив $errors накапливает ошибки между вызовами. Если не вызвать clearErrors(), предыдущие ошибки останутся. Это типичный недостаток статического состояния - его сложно сбросить и тестировать.

Решение: либо передавать массив ошибок по ссылке, либо использовать экземпляр класса вместо статического.

Пример 6: Использование статического класса как реестра (Registry)

Пример

<?php
class Registry {
    private static array $services = [];

    private function __construct() {}

    public static function set(string $key, object $service): void {
        self::$services[$key] = $service;
    }

    public static function get(string $key): ?object {
        return self::$services[$key] ?? null;
    }

    public static function has(string $key): bool {
        return isset(self::$services[$key]);
    }
}

// Регистрация сервисов
Registry::set('db', new Database());
Registry::set('logger', new Logger());

// Получение
$db = Registry::get('db');
?>

Пояснение: реестр хранит объекты в статическом массиве. Это упрощает доступ к глобальным сервисам, но создаёт те же проблемы, что и синглтон - глобальное состояние.

Статические классы в PHP - comments

En
Php static class (php)