OOP: static::class vs self::class
Glossary overview

OOP: static::class vs self::class

::class — это специальная константа PHP, которая возвращает полное имя класса в виде строки. Но результат зависит от того, с чем она используется — self или static.

self::classstatic::class
ВозвращаетИмя класса, где написан кодИмя класса, на котором вызван метод
Учитывает наследованиеНетДа
Тип связыванияРаннее (compile-time)Позднее (runtime)

Базовый пример

class Animal
{
    public static function whoAmI(): array
    {
        return [
            'self'   => self::class,
            'static' => static::class,
        ];
    }
}

class Dog extends Animal {}
class Cat extends Animal {}

Результаты:

Animal::whoAmI();
// ['self' => 'Animal', 'static' => 'Animal']

Dog::whoAmI();
// ['self' => 'Animal', 'static' => 'Dog']

Cat::whoAmI();
// ['self' => 'Animal', 'static' => 'Cat']

self::class всегда возвращает "Animal", потому что код написан в классе Animal. А static::class подстраивается под то, какой класс вызвал метод.

Где это используется в Laravel

1. Morph-типы в Eloquent

Полиморфные связи определяют тип модели через static::class:

// Illuminate\Database\Eloquent\Model
public function getMorphClass()
{
    return static::class;
}

Благодаря этому User::find(1)->getMorphClass() вернёт "App\Models\User", а не "Illuminate\Database\Eloquent\Model".

2. Имя таблицы по умолчанию

Laravel генерирует имя таблицы из имени класса модели:

public function getTable()
{
    return $this->table ?? Str::snake(
        Str::pluralStudly(class_basename(static::class))
    );
}

Userusers, BlogPostblog_posts. Если бы здесь стоял self::class, все модели пытались бы работать с одной таблицей.

3. События модели

class User extends Model
{
    protected static function booted(): void
    {
        static::creating(function (User $user) {
            // ...
        });
    }
}

Здесь static::creating() привязывает событие именно к User, а не к базовому Model.

4. Фасады

// Illuminate\Support\Facades\Facade
protected static function getFacadeAccessor()
{
    return static::class;
}

Каждый фасад возвращает своё имя, а не имя базового класса Facade.

Практические паттерны

Логирование с именем класса

class BaseService
{
    protected function log(string $message): void
    {
        Log::info(static::class . ': ' . $message);
    }
}

class PaymentService extends BaseService
{
    public function charge(): void
    {
        $this->log('Начинаю списание');
        // Лог: "App\Services\PaymentService: Начинаю списание"
        // А не: "App\Services\BaseService: ..."
    }
}

Исключения с контекстом

class BaseRepository
{
    public function findOrFail(int $id): Model
    {
        $result = $this->find($id);

        if (!$result) {
            throw new ModelNotFoundException(
                'Запись не найдена в ' . static::class
            );
            // "Запись не найдена в App\Repositories\UserRepository"
        }

        return $result;
    }
}

Кэширование с уникальным ключом

class BaseService
{
    protected function cacheKey(string $suffix): string
    {
        return Str::snake(class_basename(static::class)) . ':' . $suffix;
    }
}

class ProductService extends BaseService
{
    public function getPopular(): Collection
    {
        return Cache::remember(
            $this->cacheKey('popular'), // "product_service:popular"
            3600,
            fn () => Product::popular()->get()
        );
    }
}

Конфигурация по конвенции

class BaseNotification extends Notification
{
    public function getTemplate(): string
    {
        // App\Notifications\OrderShipped → "order_shipped"
        $name = Str::snake(class_basename(static::class));
        return "notifications.{$name}";
    }
}

Когда использовать что

Используйте static::class, когда:

  • Пишете базовый класс, от которого будут наследоваться другие
  • Нужно знать «кто я на самом деле» — для логов, кэш-ключей, имён таблиц, morph-типов
  • Работаете с паттернами фабрика, шаблонный метод, стратегия

Используйте self::class, когда:

  • Нужно жёстко указать именно текущий класс, независимо от наследования
  • Регистрируете класс в контейнере или конфиге и хотите указать конкретный класс
  • Наследования не предполагается (финальный класс)

Связь с new static и new self

Это один и тот же механизм позднего статического связывания:

class Base
{
    public static function create(): static
    {
        // Оба используют позднее связывание:
        $className = static::class;     // имя класса (строка)
        $instance  = new static();      // объект класса
        
        return $instance;
    }
}

Итого

  • self::class — всегда возвращает имя класса, где написан код.
  • static::class — возвращает имя класса, на котором вызван метод (учитывает наследование).
  • В Laravel static::class используется в morph-типах, именах таблиц, событиях, фасадах.
  • В своём коде используйте static::class для логов, кэш-ключей, исключений — везде, где важно знать реальный класс.