Построение модели в PHP для архитектуры MVC

Раздел: PHP -> Архитектура MVC

Основы модели в MVC

Наиболее эффективное решение: абстрактная модель на PDO

Для построения модели в PHP в рамках MVC рекомендуется использовать абстрактный класс, инкапсулирующий подключение к базе данных через PDO и базовые CRUD-операции. Это обеспечивает единый механизм работы с данными, предотвращает дублирование кода и повышает безопасность за счет подготовленных запросов.

// Model.php
abstract class Model {
protected static $pdo;

public static function init() {
if (!static::$pdo) {
static::$pdo = new PDO('mysql:host=localhost;dbname=mvc_db', 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
}

public static function all() {
static::init();
$table = static::getTableName();
$stmt = static::$pdo->query("SELECT * FROM {$table}");
return $stmt->fetchAll();
}

public static function find($id) {
static::init();
$table = static::getTableName();
$stmt = static::$pdo->prepare("SELECT * FROM {$table} WHERE id = :id");
$stmt->execute(['id' => $id]);
return $stmt->fetch();
}

public static function create($data) {
static::init();
$table = static::getTableName();
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$stmt = static::$pdo->prepare("INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})");
$stmt->execute($data);
return static::$pdo->lastInsertId();
}

abstract protected static function getTableName();
}

Index php app forums controller (контроллер форума в php)

// User.php (конкретная модель)
class User extends Model {
protected static function getTableName() {
return 'users';
}
}

Model index php (модель в php (mvc))

// Использование в контроллере
$users = User::all();
$user = User::find(1);
$newId = User::create(['name' => 'Иван', 'email' => 'ivan@example.com']);

Php controllers index (индексный контроллер php)

Проблема: дублирование подключения в каждом классе

Решение через абстрактный класс и статическую инициализацию. Типичная ошибка: забыть вызвать init() – приведет к ошибке Null. Рекомендуется сделать init() автоматическим через конструктор или магический метод __callStatic.

Вариант 1. Простая модель с прямыми SQL-запросами

Вопрос: как выполнить простой запрос к базе без лишних абстракций?

Этот подход оптимален для небольших проектов или единичных запросов. Пример:

// models/index.php
function getAllUsers() {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->query('SELECT * FROM users');
return $stmt->fetchAll();
}

Проблема: SQL-инъекции при конкатенации строк

Использование query() с динамическими данными опасно. Решение: применять подготовленные запросы prepare()/execute().

// Безопасный вариант
function getUserById($id) {
$pdo = new PDO(...);
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
return $stmt->fetch();
}

Вариант 2. Использование ORM (например, Eloquent)

Вопрос: как упростить работу с базой и добавить отношения между моделями?

ORM автоматизирует маппинг таблиц в объекты. Пример с Eloquent вне Laravel:

// composer require illuminate/database
// config.php
use Illuminate\Database\Capsule\Manager as Capsule;
$capsule = new Capsule;
$capsule->addConnection(['driver' => 'mysql', 'host' => 'localhost', 'database' => 'test', 'username' => 'root', 'password' => '']);
$capsule->bootEloquent();

// User.php
use Illuminate\Database\Eloquent\Model;
class User extends Model {
protected $table = 'users';
}
// Использование
$users = User::where('active', 1)->get();
$user = User::find(1);
$user->posts; // отношение hasMany

Проблема: избыточность для простых проектов

ORM добавляет сложность конфигурации и может снижать производительность при неоптимальных запросах (N+1). Решение: использовать with() для жадной загрузки отношений.

Вариант 3. Паттерн Repository

Вопрос: как отделить логику запросов от модели и облегчить тестирование?

Repository выступает прослойкой между контроллером и моделью, сосредотачивая все запросы. Пример:

// UserRepository.php
class UserRepository {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function findActiveUsers() {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE active = :active');
$stmt->execute(['active' => 1]);
return $stmt->fetchAll();
}
}
// Контроллер
$pdo = new PDO(...);
$repo = new UserRepository($pdo);
$users = $repo->findActiveUsers();

Проблема: увеличение количества классов

Для каждой модели может потребоваться свой репозиторий, что увеличивает код. Решение: создать общий абстрактный репозиторий.

Расширенные примеры работы с моделью

Модель с отношениями (User hasMany Post)

Пример
// Model.php (дополненный)
abstract class Model {
// ... базовая часть
public static function belongsTo($related, $foreignKey, $localKey) {
// реализация связи BelongsTo
}
public static function hasMany($related, $foreignKey, $localKey) {
// реализация связи HasMany
}
}

// User.php
class User extends Model {
protected static function getTableName() { return 'users'; }
public function posts() {
return $this->hasMany('Post', 'user_id', 'id');
}
}

// Post.php
class Post extends Model {
protected static function getTableName() { return 'posts'; }
}
// Получение пользователя с постами (жадная загрузка)
$user = User::find(1);
$posts = $user->posts(); // возвращает массив постов (если реализовано)

Реализация метода findOrFail

Пример
public static function findOrFail($id) {
$result = static::find($id);
if (!$result) {
throw new \Exception('Record not found');
}
return $result;
}

Транзакции в модели

Пример
public static function transferMoney($fromId, $toId, $amount) {
static::init();
static::$pdo->beginTransaction();
try {
// списываем
$stmt = static::$pdo->prepare('UPDATE accounts SET balance = balance - :amount WHERE id = :id');
$stmt->execute(['amount' => $amount, 'id' => $fromId]);
// зачисляем
$stmt = static::$pdo->prepare('UPDATE accounts SET balance = balance + :amount WHERE id = :id');
$stmt->execute(['amount' => $amount, 'id' => $toId]);
static::$pdo->commit();
} catch (\Exception $e) {
static::$pdo->rollBack();
throw $e;
}
}

Мягкое удаление (Soft Delete)

Пример
public static function delete($id) {
static::init();
$table = static::getTableName();
$stmt = static::$pdo->prepare("UPDATE {$table} SET deleted_at = NOW() WHERE id = :id");
$stmt->execute(['id' => $id]);
}

public static function withTrashed() {
// переопределить all() для включения удаленных
}

Использование Query Builder (простая реализация)

Пример
class QueryBuilder {
private $pdo;
private $query = '';
private $params = [];
public function select($columns) {
$this->query = "SELECT {$columns} ";
return $this;
}
public function from($table) {
$this->query .= "FROM {$table} ";
return $this;
}
public function where($column, $operator, $value) {
$this->query .= "WHERE {$column} {$operator} :{$column} ";
$this->params[$column] = $value;
return $this;
}
public function execute() {
$stmt = $this->pdo->prepare($this->query);
$stmt->execute($this->params);
return $stmt->fetchAll();
}
}
// Пример использования
$builder = new QueryBuilder($pdo);
$users = $builder->select('*')->from('users')->where('active', '=', 1)->execute();

Модель в PHP (MVC) - comments

En
Model index php (php)