Crud Trait
Le Crud_trait fournit des méthodes réutilisables pour les opérations CRUD standard. Il évite de réécrire les mêmes requêtes SQL dans chaque controller.
protected Database $db et protected Request $request accessibles via $this.
Mise en place
<?php
namespace App\Controllers\Admin;
use River\Controller;
use River\Response;
use River\View;
use River\Request;
use River\Router;
use River\Database;
use River\Traits\Crud_trait;
class Products_ctrl extends Controller {
use Crud_trait;
public function __construct(
View $view,
Request $request,
Router $router,
protected Database $db, // requis par Crud_trait
) {
parent::__construct($view, $request, $router);
}
}
C'est tout. Toutes les méthodes crud_* sont maintenant disponibles via $this.
Référence des méthodes
crud_index — Liste paginée
Retourne une liste paginée avec tri et recherche intégrée.
$result = $this->crud_index('products', [
'per_page' => 20,
'order_by' => 'created_at',
'order_dir' => 'DESC',
'search' => $this->request->query('search'),
'search_columns' => ['name', 'description'],
'base_url' => '/admin/products',
'select' => 'id, name, price, created_at',
]);
// $result contient :
// 'items' => array d'objets (les produits de la page courante)
// 'paginator' => instance de Paginator (pour le rendu des liens)
// 'total' => int (nombre total de résultats)
// 'search' => string|null (terme de recherche actif)
Options disponibles :
| Option | Type | Défaut | Description |
|---|---|---|---|
per_page | int | 15 | Nombre d'items par page |
order_by | string | 'id' | Colonne de tri |
order_dir | string | 'DESC' | 'ASC' ou 'DESC' |
search | string|null | null | Terme de recherche |
search_columns | array | [] | Colonnes où chercher (LIKE %...%) |
where | string|null | null | Clause WHERE additionnelle |
where_params | array | [] | Paramètres pour le WHERE |
select | string | '*' | Colonnes à sélectionner |
base_url | string | '' | URL de base pour les liens de pagination |
$request->input('page') — fonctionne en GET (?page=2) comme en POST (utile avec HTMX).
Exemple avec filtre personnalisé :
// Lister uniquement les produits actifs d'une catégorie
$result = $this->crud_index('products', [
'where' => 'active = ? AND category_id = ?',
'where_params' => [1, $category_id],
'search' => $this->request->query('search'),
'search_columns' => ['name'],
]);
crud_find — Trouver par ID
$product = $this->crud_find('products', $id);
// Retourne un objet stdClass ou null
if (!$product) {
return $this->e404();
}
// $product->name, $product->price...
Avec sélection de colonnes :
$product = $this->crud_find('products', $id, 'id, name, price');
crud_find_by — Trouver par champ
$product = $this->crud_find_by('products', 'slug', 'mon-produit');
// Cherche WHERE slug = 'mon-produit'
$user = $this->crud_find_by('users', 'email', 'flo@example.com');
crud_create — Créer
Insère un enregistrement et retourne l'ID. Ajoute automatiquement created_at si non fourni.
$id = $this->crud_create('products', [
'name' => 'Nouveau produit',
'price' => 29.99,
'slug' => 'nouveau-produit',
]);
// $id = 42 (dernier ID inséré)
// created_at est ajouté automatiquement
crud_update — Mettre à jour
Met à jour un enregistrement par ID. Ajoute automatiquement updated_at si non fourni. Retourne true si au moins une ligne a été modifiée.
$updated = $this->crud_update('products', $id, [
'name' => 'Nom modifié',
'price' => 39.99,
]);
// $updated = true/false
// updated_at est ajouté automatiquement
crud_delete — Supprimer
$deleted = $this->crud_delete('products', $id);
// $deleted = true si supprimé, false sinon
Helpers
crud_exists — Vérifier l'existence
if (!$this->crud_exists('products', $id)) {
return $this->e404();
}
crud_count — Compter
// Compter tout
$total = $this->crud_count('products');
// Compter avec condition
$active = $this->crud_count('products', 'active = ?', [1]);
crud_all — Tout récupérer (sans pagination)
Utile pour les selects, les exports, ou les petites tables.
// Tous les produits triés par nom
$products = $this->crud_all('products', 'name', 'ASC');
// Seulement id et name (pour un select)
$categories = $this->crud_all('categories', 'name', 'ASC', 'id, name');
Validation
crud_filter — Filtrer les champs autorisés
Garde uniquement les clés autorisées dans un tableau de données. Indispensable pour éviter le mass assignment.
private const ALLOWED_FIELDS = ['name', 'email', 'role'];
public function store(): Response {
$data = $this->crud_filter($this->request->all(), self::ALLOWED_FIELDS);
// Si l'utilisateur envoie 'password' ou 'is_admin' dans le POST,
// ces champs sont ignorés
$id = $this->crud_create('users', $data);
}
crud_is_unique — Vérifier l'unicité
// Création : vérifier que l'email n'existe pas
if (!$this->crud_is_unique('users', 'email', $data['email'])) {
// Erreur : email déjà utilisé
}
// Update : exclure l'enregistrement en cours de modification
if (!$this->crud_is_unique('users', 'email', $data['email'], $id)) {
// Erreur : email déjà utilisé par un autre utilisateur
}
Fetch Mode
Par défaut, toutes les méthodes retournent des objets stdClass ($item->name). Pour utiliser des tableaux associatifs ($item['name']), définissez la propriété $crud_fetch_mode :
use PDO;
class Products_ctrl extends Controller {
use Crud_trait;
protected int $crud_fetch_mode = PDO::FETCH_ASSOC;
// Maintenant : $item['name'] au lieu de $item->name
}
Exemple complet
Un controller CRUD typique pour une gestion d'utilisateurs :
<?php
namespace App\Controllers\Admin;
use River\Controller;
use River\Response;
use River\View;
use River\Request;
use River\Router;
use River\Database;
use River\Csrf;
use River\Session;
use River\Traits\Crud_trait;
class Users_ctrl extends Controller {
use Crud_trait;
private const ALLOWED = ['name', 'email', 'role'];
public function __construct(
View $view,
Request $request,
Router $router,
protected Database $db,
private Csrf $csrf,
private Session $session,
) {
parent::__construct($view, $request, $router);
}
public function index(): Response {
$result = $this->crud_index('users', [
'per_page' => 15,
'order_by' => 'created_at',
'order_dir' => 'DESC',
'search' => $this->request->query('search'),
'search_columns' => ['name', 'email'],
'base_url' => '/admin/users',
'select' => 'id, name, email, role, created_at',
]);
return $this->render('admin/users/index', [
'users' => $result['items'],
'paginator' => $result['paginator'],
'search' => $result['search'],
'total' => $result['total'],
]);
}
public function store(): Response {
$data = $this->crud_filter($this->request->all(), self::ALLOWED);
if (!$this->crud_is_unique('users', 'email', $data['email'])) {
$this->session->flash('error', 'Email déjà utilisé');
return Response::redirect('/admin/users/create');
}
$data['password'] = password_hash(
$this->request->post('password'), PASSWORD_DEFAULT
);
$id = $this->crud_create('users', $data);
$this->session->flash('success', 'Utilisateur créé');
return Response::redirect('/admin/users/' . $id);
}
public function show(): Response {
$user = $this->crud_find('users', (int) $this->param('id'));
if (!$user) return $this->e404();
return $this->render('admin/users/show', ['user' => $user]);
}
public function update(): Response {
$id = (int) $this->param('id');
$data = $this->crud_filter($this->request->all(), self::ALLOWED);
if (!$this->crud_is_unique('users', 'email', $data['email'], $id)) {
$this->session->flash('error', 'Email déjà utilisé');
return Response::redirect('/admin/users/' . $id . '/edit');
}
$this->crud_update('users', $id, $data);
$this->session->flash('success', 'Utilisateur mis à jour');
return Response::redirect('/admin/users/' . $id);
}
public function destroy(): Response {
$this->crud_delete('users', (int) $this->param('id'));
$this->session->flash('success', 'Utilisateur supprimé');
return Response::redirect('/admin/users');
}
}
Routes associées :
$router->group(['prefix' => 'admin', 'middleware' => Auth_middleware::class], function($r) {
$r->get('users', 'Admin\Users_ctrl::index')->name('admin.users');
$r->get('users/create', 'Admin\Users_ctrl::create')->name('admin.users.create');
$r->post('users', 'Admin\Users_ctrl::store')->name('admin.users.store');
$r->get('users/{id}', 'Admin\Users_ctrl::show')->where('id', '\d+')->name('admin.users.show');
$r->get('users/{id}/edit', 'Admin\Users_ctrl::edit')->where('id', '\d+')->name('admin.users.edit');
$r->post('users/{id}', 'Admin\Users_ctrl::update')->where('id', '\d+');
$r->post('users/{id}/delete', 'Admin\Users_ctrl::destroy')->where('id', '\d+');
});
Récapitulatif
| Méthode | Retour | Description |
|---|---|---|
crud_index($table, $options) | array | Liste paginée avec recherche |
crud_find($table, $id) | object|null | Un item par ID |
crud_find_by($table, $col, $val) | object|null | Un item par champ |
crud_create($table, $data) | int | Crée, retourne l'ID |
crud_update($table, $id, $data) | bool | Met à jour |
crud_delete($table, $id) | bool | Supprime |
crud_exists($table, $id) | bool | Vérifie l'existence |
crud_count($table, $where?) | int | Compte les items |
crud_all($table, ...) | array | Tout sans pagination |
crud_filter($data, $allowed) | array | Filtre les champs |
crud_is_unique($table, $col, $val) | bool | Vérifie l'unicité |