Ответ
Active Record — это архитектурный паттерн, в котором объект инкапсулирует строку таблицы базы данных или представление, объединяя данные и поведение для работы с ними. Каждый экземпляр класса соответствует строке в таблице БД, а сам класс предоставляет методы для CRUD-операций.
Ключевые характеристики:
- Объект = запись БД — каждый экземпляр представляет одну строку таблицы
- Инкапсуляция данных и поведения — объект содержит как данные, так и методы для их сохранения/загрузки
- Прямое отображение — структура класса обычно mirrors структуру таблицы
Реализация в PHP (на примере Laravel Eloquent):
Базовое использование:
// Модель User соответствует таблице 'users'
class User extends IlluminateDatabaseEloquentModel
{
// Необязательно - Eloquent сам определит по имени класса
protected $table = 'users';
protected $fillable = ['name', 'email', 'password'];
protected $casts = [
'is_active' => 'boolean',
'settings' => 'array'
];
}
// CRUD операции
// CREATE
$user = new User();
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->save(); // Выполняет INSERT
// READ
$user = User::find(1); // SELECT * FROM users WHERE id = 1
$activeUsers = User::where('is_active', true)->get();
// UPDATE
$user = User::find(1);
$user->name = 'Jane Doe';
$user->save(); // Выполняет UPDATE
// DELETE
$user = User::find(1);
$user->delete(); // DELETE FROM users WHERE id = 1
Отношения (Relationships):
class User extends Model
{
// Один ко многим
public function posts()
{
return $this->hasMany(Post::class);
}
// Многие ко многим
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
// Использование отношений
$user = User::find(1);
$posts = $user->posts; // Автоматическая загрузка связанных постов
$latestPost = $user->posts()->latest()->first();
События (Events) и Observers:
class User extends Model
{
protected static function booted()
{
static::creating(function ($user) {
$user->api_token = Str::random(60);
});
static::created(function ($user) {
// Отправка welcome email
Mail::to($user->email)->send(new WelcomeEmail($user));
});
}
}
Преимущества Active Record:
1. Простота и скорость разработки:
// Быстрое прототипирование
$user = User::create([
'name' => 'Test User',
'email' => 'test@example.com'
]);
// Всего 3 строки для создания и сохранения объекта
2. Интуитивно понятный API:
// Чтение похоже на SQL, но более объектно-ориентированно
$users = User::where('age', '>', 18)
->orderBy('created_at', 'desc')
->paginate(20);
3. Минимальная конфигурация:
// В Laravel достаточно:
class Product extends Model {}
// Автоматически работает с таблицей 'products'
4. Встроенные функции:
// Валидация, события, скоупы, мягкое удаление из коробки
class Post extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function scopePublished($query)
{
return $query->where('published', true);
}
}
// Мягкое удаление
$post->delete(); // Устанавливает deleted_at, не удаляет физически
$post->forceDelete(); // Физическое удаление
Недостатки и критика:
1. Нарушение Single Responsibility Principle:
class User extends Model
{
// Отвечает и за данные, и за бизнес-логику, и за персистентность
public function calculateDiscount() { /* бизнес-логика */ }
public function sendNotification() { /* логика уведомлений */ }
// + все методы ActiveRecord для работы с БД
}
// Класс становится "God Object"
2. Сложность тестирования:
// Трудно тестировать из-за сильной связи с БД
public function testUserLogic(): void
{
$user = new User(['name' => 'Test']);
$user->save(); // Реальная запись в БД - медленный тест
// Нужны моки для Eloquent или тестовая БД
}
3. Проблемы с производительностью:
// N+1 проблема
$users = User::all();
foreach ($users as $user) {
echo $user->profile->bio; // Отдельный запрос для каждого пользователя
}
// Решение: eager loading
$users = User::with('profile')->get(); // Всего 2 запроса
4. Связь с конкретной СУБД:
// Код может стать зависимым от возможностей конкретной БД
$users = User::whereRaw('JSON_CONTAINS(settings, ?)', ['"dark_mode":true'])
->get();
// Использует MySQL-специфичный JSON_CONTAINS
Альтернативы:
Data Mapper (например, Doctrine):
// Отдельная сущность и маппер
class User
{
private $id;
private $name;
// Только данные и бизнес-логика
}
class UserMapper
{
public function save(User $user) { /* сохранение в БД */ }
public function find($id): User { /* загрузка из БД */ }
}
// Более чистая архитектура, но более сложная настройка
Рекомендации по использованию:
- Для простых CRUD-приложений — Active Record отлично подходит
- Для сложной бизнес-логики — рассмотреть Data Mapper или разделить ответственность
- Использовать Repository Pattern для абстракции доступа к данным:
class UserRepository { public function findActiveUsers(): Collection { return User::where('is_active', true)->get(); } } // Контроллеры работают с репозиторием, а не напрямую с Eloquent
Популярные реализации в PHP:
- Laravel Eloquent (наиболее популярная)
- Yii2 ActiveRecord
- CakePHP ORM
- Propel (устаревший)
Active Record идеален для быстрой разработки и проектов средней сложности, но требует осторожности в крупных enterprise-приложениях.