TROCKENES Prinzip mit Laravel

Stellen Sie sich ein einfaches Modul vor, das für das Hinzufügen neuer Benutzer verantwortlich ist.

Und an seinem Beispiel werden wir sehen, welche Möglichkeiten die Anwendung des DRY-Prinzips eröffnet.

Für mich war das Prinzip von DRY (Don't Repeat Yourself) immer in zwei grundlegenden Definitionen enthalten:

  1. Die Vervielfältigung von Wissen ist immer ein Verstoß gegen das Prinzip
  2. Das Duplizieren von Code ist nicht immer ein Verstoß gegen das Prinzip

Beginnen wir mit den Controllern, die die Mindestmenge an Logik enthalten.

class UserController
{
    public function create(CreateRequest $request)
    {
        $user = User::create($request->all());
        
        return view('user.created', compact('user'));
    }
}

class UserApiController
{
    public function create(CreateRequest $request)
    {
        $user = User::create($request->all());
        
        return response()->noContent(201);
    }
}

In der Anfangsphase erscheint eine solche Wiederholung des Codes ziemlich harmlos.
Aber wir haben bereits doppelte Kenntnisse, und doppelte Kenntnisse sind verboten.
Zu diesem Zweck verallgemeinern wir die Benutzererstellung in der UserService- Klasse

class UserService
{
    public function create(array $data): User
    {
        $user = new User;
        $user->email = $data['email'];
        $user->password = $data['password'];
        $user->save();

        return $user;
    }
    
    public function delete($userId): bool 
    {
        $user = User::findOrFail($userId);
 
        return $user->delete();
    }    
}

Nachdem wir die gesamte Logik der Arbeit mit dem Modell auf den Dienst verlagert haben, werden wir dessen Duplizierung in der Steuerung entfernen. Aber wir haben noch ein anderes Problem. Nehmen wir an, wir müssen den Prozess des Erstellens eines Benutzers etwas komplizieren.

class UserService
{
    protected $blogService;
    
    public function __construct(BlogService $blogService)
    {
        $this->blogService = $blogService;
    }

    public function create(array $data): User
    {
        $user = new User;
        $user->email = $data['email'];
        $user->password = $data['password'];
        $user->save();
        
        $blog = $this->blogService->create();
        $user->blogs()->attach($blog);

        return $user;
    }
    
    //Other methods
}

Allmählich wächst die UserService- Klasse und wir laufen Gefahr, eine Super-Klasse mit einer Vielzahl von Abhängigkeiten zu erhalten.

CreateUser Single Action Class


Um solche Konsequenzen zu vermeiden, können Sie den Dienst in Klassen einer einzelnen Aktion aufteilen.

Grundvoraussetzungen für diese Klasse:

  • Name, der die auszuführende Aktion darstellt
  • Hat eine einzige öffentliche Methode (ich werde die magische Methode __invoke verwenden )
  • Hat alle notwendigen Abhängigkeiten
  • Gewährleistet die Einhaltung aller Geschäftsregeln in sich selbst und generiert eine Ausnahme, wenn sie verletzt werden

class CreateUser
{
    protected $blogService;

    public function __construct(BlogService $blogService)
    {
        $this->blogService = $blogService;
    }

    public function __invoke(array $data): User
    {
        $email = $data['email'];
        
        if (User::whereEmail($email)->first()) {
            throw new EmailNotUniqueException("$email should be unique!");
        }
        
        $user = new User;
        $user->email = $data['email'];
        $user->password = $data['password'];
        $user->save();

        $blog = $this->blogService->create();
        $user->blogs()->attach($blog);

        return $user;
    }
}

Wir haben bereits eine E-Mail-Feldprüfung in der CreateRequet-Klasse, aber es ist logisch, auch hier eine Prüfung hinzuzufügen. Dies spiegelt die Geschäftslogik des Erstellens eines Benutzers genauer wider und vereinfacht auch das Debuggen.

Controller haben die folgende Form

class UserController
{
    public function create(CreateRequest $request, CreateUser $createUser)
    {
        $user = $createUser($request->all());

        return view('user.created', compact('user'));
    }
}

class UserApiController
{
    public function create(CreateRequest $request, CreateUser $createUser)
    {
        $user = $createUser($request->all());

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

Als Ergebnis haben wir eine vollständig isolierte Logik zum Erstellen eines Benutzers. Es ist bequem zu ändern und zu erweitern.

Nun wollen wir sehen, welche Vorteile uns dieser Ansatz bietet.

Beispielsweise gibt es eine Aufgabe zum Importieren von Benutzern.

class ImportUser
{
    protected $createUser;
    
    public function __construct(CreateUser $createUser)
    {
        $this->createUser = $createUser;
    }
    
    public function handle(array $rows): Collection
    {
        return collect($rows)->map(function (array $row) {
            try {
                return $this->createUser($row);
            } catch (EmailNotUniqueException $e) {
                // Deal with duplicate users
            }
        });
    }
}

Wir haben die Möglichkeit, den Code wiederzuverwenden, indem wir ihn in die Collection :: map () -Methode einbetten. Und auch für unsere Bedürfnisse Benutzer verarbeiten, deren E-Mail-Adressen nicht eindeutig sind.

Dressing


Angenommen, wir müssen jeden neuen Benutzer in einer Datei registrieren.

Zu diesem Zweck wird diese Aktion nicht in die CreateUser-Klasse selbst eingebettet, sondern das Decorator-Muster verwendet.

class LogCreateUser extends CreateUser 
{
    public function __invoke(array $data)
    {
        Log::info("A new user has registered: " . $data['email']);
        
        parent::__invoke($data);
    }
}

Mithilfe des Laravel IoC-Containers können wir dann die LogCreateUser- Klasse der CreateUser- Klasse zuordnen. Die erste wird jedes Mal implementiert, wenn wir eine Instanz der zweiten benötigen.

class AppServiceProvider extends ServiceProvider
{

    // ...

    public function register()
    {
        $this->app->bind(CreateUser::class, LogCreateUser::class);
    }

Wir haben auch die Möglichkeit, die Einstellung für die Benutzererstellung mithilfe einer Variablen in der Konfigurationsdatei vorzunehmen.

class AppServiceProvider extends ServiceProvider
{

    // ...

    public function register()
    {
         if (config("users.log_registration")) {
             $this->app->bind(CreateUser::class, LogCreateUser::class);
         }
    }

Fazit


Hier ist ein einfaches Beispiel. Mit zunehmender Komplexität zeigen sich echte Vorteile. Wir wissen immer, dass sich der Code an einem Ort befindet und seine Grenzen klar definiert sind.

Wir erhalten folgende Vorteile: Verhindert Doppelarbeit, vereinfacht das Testen
und eröffnet den Weg für die Anwendung anderer Entwurfsprinzipien und -muster.

All Articles