L'architecture MVC classique de CodeIgniter répartit les responsabilités entre les Modèles (données), les Vues (affichage) et les Contrôleurs (interaction). Cependant, à mesure qu'une application gagne en complexité, les contrôleurs ont tendance à devenir trop volumineux en absorbant toute la logique métier. Par exemple, le traitement d'une commande client nécessite souvent la validation des stocks, le calcul des remises, la mise à jour de la base de données et l'envoi de notifications.
Pour éviter la création de "Fat Controllers", il est recommandé d'introduire une couche logicielle intermédiaire : le Service. Cette couche centralise les processus métier et expose des interfaces claires aux contrôleurs.
- Model : Gère l'accès aux données et la persistance.
- Service : Orchestre la logique métier et les règles de gestion.
- Controller : Gère le flux d'exécution et renvoie la réponse à l'utilisateur.
- View : Présente les informations de manière structurée.
1. Création de la classe de base
Pour permettre à nos services d'accéder aux ressources golbales de CodeIgniter (comme les modèles ou les bibliothèques), nous devons créer une classe de base dans application/core/MY_Service.php.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Service
{
public function __construct()
{
log_message('debug', "Base Service Class Initialized");
}
/**
* Surcharge magique pour accéder aux ressources CI via l'instance globale
*/
public function __get($key)
{
$CI =& get_instance();
return $CI->$key;
}
}
2. Exetnsion du Loader de CodeIgniter
Par défaut, CodeIgniter ne sait pas comment charger des services. Nous allons étendre la classe CI_Loader pour ajouter une méthode service(). Créez le fichier application/core/MY_Loader.php :
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Loader extends CI_Loader {
protected $_loaded_services = array();
protected $_services_base_path = APPPATH;
public function __construct() {
parent::__construct();
}
/**
* Chargeur de Service
*
* @param string $service_name Nom du fichier service
* @param mixed $params Paramètres optionnels du constructeur
* @param string $object_name Nom de l'alias pour l'objet
*/
public function service($service_name = '', $params = NULL, $object_name = NULL)
{
if (empty($service_name)) return $this;
if (is_array($service_name)) {
foreach ($service_name as $key => $val) {
is_int($key) ? $this->service($val) : $this->service($key, $val);
}
return $this;
}
$path = '';
if (($last_slash = strrpos($service_name, '/')) !== FALSE) {
$path = substr($service_name, 0, ++$last_slash);
$service_name = substr($service_name, $last_slash);
}
if (empty($object_name)) {
$object_name = strtolower($service_name);
}
if (in_array($object_name, $this->_loaded_services, TRUE)) return $this;
$CI =& get_instance();
if (isset($CI->$object_name)) {
throw new RuntimeException("Le nom de service '$object_name' entre en conflit avec une ressource existante.");
}
// Chargement automatique de la classe parente MY_Service
$base_class = config_item('subclass_prefix') . 'Service';
if (!class_exists($base_class, FALSE)) {
$base_path = APPPATH . 'core/' . $base_class . '.php';
if (file_exists($base_path)) {
require_once($base_path);
}
}
$class_name = ucfirst($service_name);
$service_file = $this->_services_base_path . 'services/' . $path . $class_name . '.php';
if (file_exists($service_file)) {
include_once($service_file);
if (!class_exists($class_name, FALSE)) {
throw new RuntimeException("Le fichier $service_file ne contient pas la classe $class_name.");
}
$CI->$object_name = ($params !== NULL) ? new $class_name($params) : new $class_name();
$this->_loaded_services[] = $object_name;
return $this;
}
throw new RuntimeException("Impossible de trouver le service : " . $service_file);
}
}
3. Exemple d'implémentation d'un Service
Créez un nouveau répertoire application/services/ et ajoutez-y un fichier nommé CalculateurService.php :
<?php
class CalculateurService extends MY_Service {
public function formater_message($nom)
{
return "Traitement effectué pour : " . $nom;
}
}
4. Utilisation au sein d'un Contrôleur
Désormais, vous pouvez appeler votre logique métier directement depuis vos contrôleurs de manière fluide :
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Welcome extends CI_Controller {
public function index()
{
// Chargement du service personnalisé
$this->load->service('CalculateurService');
// Appel de la méthode du service
$resultat = $this->calculateurservice->formater_message('Client_01');
echo $resultat;
}
}
Cette approche permet de garder des contrôleurs légers et facilite la réutilisation du code à travers différents points d'entrée de votre application (API, interface web, tâches CRON).