Single Responsibility Principle (SRP) mit Laravel

Das SRP-Prinzip (One Responsibility Principle) ist eines der Grundprinzipien für das Schreiben von unterstütztem Code. In diesem Artikel werde ich am Beispiel der PHP-Sprache und des Laravel-Frameworks zeigen, wie dieses Prinzip angewendet wird.

Bei der Beschreibung des MVC-Entwicklungsmodells werden dem Controller häufig unangemessen große Aufgaben zugewiesen. Abrufen von Parametern, Geschäftslogik, Autorisierung und Antwort.

Natürlich wird dies in Artikeln und Büchern als Beispiel beschrieben, aber in Arbeitsprojekten oft als Handlungsaufforderung wahrgenommen. Ein solcher Ansatz führt unweigerlich zu einem unkontrollierten Klassenwachstum und erschwert die Codeunterstützung erheblich.

Prinzip der geteilten Verantwortung


Als Beispiel nehmen wir einen ziemlich rauen, aber oft erfüllten Typ des "dicken" Controllers.

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);
    }
}

In diesem Beispiel ist klar, dass der Controller zu viel über das „Aufgeben einer Bestellung“ weiß. Er ist auch mit der Aufgabe betraut, den Käufer zu benachrichtigen und die Waren zu reservieren.

Wir werden uns bemühen, sicherzustellen, dass der Controller nur den gesamten Prozess und keine Geschäftslogik steuern muss.

Zunächst validieren wir die Parameter in einer separaten Request-Klasse.

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

Verschieben Sie die gesamte Geschäftslogik in die OrderService-Klasse

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

        //  

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

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

Mit dem Service Container implementieren wir unseren Service in der Steuerung.

Als Ergebnis haben wir einen "dünnen" Controller.

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);
    }
}

Schon besser. Die gesamte Geschäftslogik wurde in den Dienst gestellt. Der Controller kennt nur die Klassen OrderRequest und OrderService - die Mindestmenge an Informationen, die für seine Arbeit erforderlich sind.

Jetzt muss unser Service aber auch umgestaltet werden. Lassen Sie uns die Logik des Sendens von SMS an eine separate Klasse herausnehmen.

class SmsRu
{
    protected $appId;

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

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

Und implementieren Sie es über den Konstruktor

class OrderService
{
    private $sms;

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

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

          //  

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

Bereits besser, aber die OrderService- Klasse weiß immer noch zu viel über das Senden von Nachrichten. Möglicherweise müssen wir den Messaging-Anbieter in Zukunft ersetzen oder Komponententests hinzufügen. Wir setzen das Refactoring fort, indem wir die SmsSender- Schnittstelle hinzufügen und SmsRu über den SmsServiceProvider- Anbieter angeben .

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']);
        });
    }
}

Jetzt ist der Service auch von unnötigen Details befreit

class OrderService
{
    private $sms;

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

    public function create(): void
    {
          //  

          //  

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

Ereignisgesteuerte Architektur


Das Senden von Nachrichten ist nicht Teil des grundlegenden Auftragserstellungsprozesses. Die Bestellung wird unabhängig von der gesendeten Nachricht erstellt. Es ist auch möglich, dass in Zukunft die Option zum Stornieren von SMS-Benachrichtigungen hinzugefügt wird. Um den OrderService nicht mit unnötigen Benachrichtigungsdetails zu überladen, können Sie Laravel Observers verwenden . Eine Klasse, die Ereignisse mit einem bestimmten Verhalten unseres Bestellmodells verfolgt und ihm die gesamte Logik der Kundenbenachrichtigung zuweist.

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);
    }

Vergessen Sie nicht, OrderObserver in AppServiceProvider zu registrieren .

Fazit


Ich verstehe, dass ich in diesem Beitrag eher triviale Dinge beschreibe, aber ich wollte zeigen, wie dieses Prinzip im Laravel-Framework implementiert wird.

Das SPR-Prinzip ist eines der wichtigsten und ebnet den Weg für den Rest. Es ist erforderlich, um es zu verwenden, zumal das Laravel-Framework alle erforderlichen Tools bereitstellt.

All Articles