Implémentation d'une Couche Service dans CodeIgniter pour Découpler la Logique Métier

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).

Étiquettes: CodeIgniter PHP mvc Software-Architecture Refactoring

Publié le 15 juin à 03h49