Principe de responsabilité unique (PRS) avec Laravel

Le principe SRP (One Responsibility Principle) est l'un des principes fondamentaux pour l'écriture de code pris en charge. Dans cet article, je vais montrer comment appliquer ce principe en utilisant l'exemple du langage PHP et du framework Laravel.

Souvent, lors de la description du modèle de développement MVC, des tâches déraisonnablement importantes sont affectées au contrôleur. Obtenir les paramètres, la logique métier, l'autorisation et la réponse.

Bien sûr, dans les articles et les livres, cela est décrit comme un exemple, mais est souvent perçu comme un appel à l'action dans les projets de travail. Une telle approche entraînera inévitablement une croissance incontrôlée des classes et compliquera considérablement la prise en charge du code.

Principe de responsabilité partagée


Pour un exemple, nous prendrons un type plutôt grossier, mais souvent rencontré, de contrôleur "épais".

class OrderController extends Controller
{
    public function create(Request $request)
    {
        $rules = [
            'product_id' => 'required|max:10',
            'count' => 'required|max:10',
        ];
        $validator = Validator::make($request->all(), $rules);
        if ($validator->fails()) {
            return response()->json($validator->errors(), 400);
        }

         //  
        $order = Order::create($request->all());

        //   

        //  sms    

        return response()->json($order, 201);
    }
}

Dans cet exemple, on constate que le responsable du traitement en sait trop sur la «passation de commande», il est également chargé de notifier l'acheteur et de réserver la marchandise.

Nous nous efforcerons de nous assurer que le contrôleur ne doit contrôler que l'ensemble du processus et aucune logique métier.

Pour commencer, nous validerons les paramètres dans une classe Request distincte.

class OrderRequest extends Request
{
    public function rules(): array
    {
         return [
             'product_id' => 'required|max:10',
             'count' => 'required|max:10',
         ];    
    }
}

Et déplacez toute la logique métier vers la classe OrderService

public class OrderService
{
    public function create(array $params)
    {
        //  

        //  

        //     sms
        $sms = new RuSms($appId);

        //  ,    
        $sms->send($number, $text);
     }
}

En utilisant le conteneur de service, nous implémenterons notre service dans le contrôleur.

En conséquence, nous avons un contrôleur "mince".

class OrderController extends Controller
{
    protected $service;
	
    public function __construct(OrderService $service)
    {
	$this->service = $service;	
    }

    public function create(OrderRequest $request)
    {
        $this->service->create($request->all());

        return response()->noContent(201);
    }
}

Déjà mieux. Toute la logique métier a été déplacée vers le service. Le contrôleur ne connaît que les classes OrderRequest et OrderService - l'ensemble minimal d'informations nécessaires pour faire son travail.

Mais maintenant, notre service doit également être refactorisé. Prenons la logique de l'envoi de sms à une classe distincte.

class SmsRu
{
    protected $appId;

    public function __constructor(string $appId)
    {
        $this->appId = $appId;
    }

    public function send($number, $text): void
    {
          //     
    }
}    

Et l'implémenter via le constructeur

class OrderService
{
    private $sms;

    public function __construct()
    {
	$this->sms = new SmsRu($appId);	
    }

    public function create(array $params): void
    {
          //  

          //  

          //  ,    
          $this->sms->send($nubmer, $text);
    }
}    

Déjà mieux, mais la classe OrderService en sait encore trop sur l'envoi de messages. Nous devrons peut-être remplacer le fournisseur de messagerie à l'avenir ou ajouter des tests unitaires. Nous continuons la refactorisation en ajoutant l'interface SmsSender et spécifions SmsRu via le fournisseur SmsServiceProvider .

interface SmsSenderInterface
{
    public function send($number, $text): void;
}

class SmsServiceProvider implements ServiceProviderInterface
{
    public function register(): void
    {
        $this->app->singleton(SmsSenderInterface::class, function ($app) {
            return new SmsRu($params['app_id']);
        });
    }
}

Maintenant, le service est également libéré des détails inutiles

class OrderService
{
    private $sms;

    public function __construct(SmsSenderInterface $sms)
    {
	$this->sms = $sms;	
    }

    public function create(): void
    {
          //  

          //  

          //  ,    
          $this->sms->send($nubmer, $text);
    }
}    

Architecture événementielle


L'envoi de messages ne fait pas partie du processus de création de commande de base. La commande sera créée quel que soit le message envoyé, il est également possible à l'avenir l'option d'annuler les notifications sms sera ajoutée. Afin de ne pas surcharger OrderService de détails de notification inutiles, vous pouvez utiliser Laravel Observers . Une classe qui suivra les événements avec le comportement spécifique de notre modèle de commande et lui attribuera toute la logique de notification client.

class OrderObserver
{
    private $sms;

    public function __construct(SmsSenderInterface $sms)
    {
	$this->sms = $sms;	
    }

    /**
     * Handle the Order "created" event.
     */
    public function created(Order $order)
    {
        $this->sms->send($nubmer, $text);
    }

N'oubliez pas d'enregistrer OrderObserver dans AppServiceProvider .

Conclusion


Je comprends que je décris des choses plutôt banales dans ce post, mais je voulais montrer comment ce principe est mis en œuvre sur le framework Laravel.

Le principe SPR est l'un des plus importants et ouvre la voie au reste. Il est nécessaire d'utiliser, d'autant plus que le framework Laravel fournit tous les outils nécessaires.

All Articles