it-swarm.com.de

MVC (Laravel) wo man Logik hinzufügt

Sagen wir, wann immer ich eine CRUD-Operation durchführe oder eine Beziehung auf eine bestimmte Weise ändere, möchte ich auch etwas anderes tun. Zum Beispiel, wenn jemand einen Beitrag veröffentlicht, möchte ich auch etwas für die Analyse in einer Tabelle speichern. Vielleicht nicht das beste Beispiel, aber im Allgemeinen gibt es viele dieser "gruppierten" Funktionen.

Normalerweise sehe ich diese Art von Logik in Steuerungen. Das ist alles in Ordnung, bis Sie diese Funktionalität an vielen Stellen reproduzieren möchten. Wenn Sie anfangen, Teilbereiche zu bearbeiten, eine API zu erstellen und Dummy-Inhalte zu generieren, wird es zu einem Problem, die Dinge trocken zu halten.

Ich habe gesehen, wie dies verwaltet wird: Ereignisse, Repositorys, Bibliotheken und das Hinzufügen zu Modellen. Hier ist mein Verständnis von jedem:

Services: Hier würden wahrscheinlich die meisten Leute diesen Code ablegen. Mein Hauptproblem bei Diensten ist, dass es manchmal schwierig ist, bestimmte Funktionen in ihnen zu finden, und ich habe das Gefühl, dass sie vergessen werden, wenn Leute sich auf die Verwendung von Eloquent konzentrieren. Woher weiß ich, dass ich eine Methode publishPost() in einer Bibliothek aufrufen muss, wenn ich nur $post->is_published = 1?

Die einzige Bedingung, unter der ich sehe, dass dies gut funktioniert, ist, dass Sie NUR Dienste nutzen (und Eloquent im Idealfall von allen Controllern aus unzugänglich machen).

Letztendlich scheint dies nur eine Menge unnötiger Dateien zu erzeugen, wenn Ihre Anforderungen im Allgemeinen Ihrer Modellstruktur entsprechen.

Repositories: Soweit ich weiß, ist dies im Grunde ein Service, aber es gibt eine Schnittstelle, über die Sie zwischen ORMs wechseln können, die ich nicht benötige.

Events: Ich sehe dies in gewisser Hinsicht als das eleganteste System an, da Sie wissen, dass Ihre Modellereignisse immer mit eloquenten Methoden aufgerufen werden, sodass Sie Ihre Controller so schreiben können, wie Sie es normalerweise tun würden. Ich kann jedoch sehen, dass diese chaotisch werden, und wenn jemand Beispiele für große Projekte hat, die Ereignisse für die kritische Kopplung verwenden, würde ich es gerne sehen.

Models: Herkömmlicherweise hatte ich Klassen, die CRUD ausführten und auch kritisches Koppeln behandelten. Dies machte die Sache tatsächlich einfach, da Sie alle Funktionen rund um CRUD + kannten und alles, was damit zu tun war, vorhanden war.

Einfach, aber in der MVC-Architektur ist dies normalerweise nicht das, was ich sehe. In gewisser Hinsicht bevorzuge ich dies gegenüber Diensten, da es ein bisschen einfacher zu finden ist und weniger Dateien zu verwalten sind. Es kann allerdings etwas unorganisiert werden. Ich würde gerne die Nachteile dieser Methode hören und wissen, warum die meisten Leute das nicht tun.

Was sind die Vor-/Nachteile jeder Methode? Vermisse ich etwas?

115
Sabrina Leggett

Ich denke, alle Muster/Architekturen, die Sie präsentieren, sind sehr nützlich, solange Sie den Grundsätzen SOLID folgen.

Für das wo man Logik hinzufügt halte ich es für wichtig, auf das Prinzip der Einzelverantwortung zu verweisen. Meine Antwort geht auch davon aus, dass Sie an einem mittleren/großen Projekt arbeiten. Wenn es sich um ein Etwas auf eine Seite werfen Projekt handelt, vergessen Sie diese Antwort und fügen Sie sie allen Controllern oder Modellen hinzu.

Die kurze Antwort lautet: Wo es für Sie Sinn macht (mit Dienstleistungen) .

Die lange Antwort:

Controller : Was ist die Verantwortung von Controllern? Sicher, Sie können all Ihre Logik in einen Controller einbauen, aber liegt das in der Verantwortung des Controllers? Ich glaube nicht.

Für mich muss der Controller eine Anfrage erhalten und Daten zurücksenden, und dies ist nicht der richtige Ort, um Validierungen vorzunehmen, DB-Methoden aufzurufen usw.

Models : Ist dies ein guter Ort, um Logik hinzuzufügen, wie das Senden einer Begrüßungs-E-Mail, wenn sich ein Benutzer registriert oder die Stimmenzahl eines Posts aktualisiert? Was ist, wenn Sie dieselbe E-Mail von einem anderen Ort in Ihrem Code senden müssen? Erstellen Sie eine statische Methode? Was ist, wenn diese E-Mails Informationen von einem anderen Modell benötigen?

Ich denke, das Modell sollte eine Einheit darstellen. In Laravel benutze ich die Modellklasse nur, um Dinge wie fillable, guarded, table und die Beziehungen hinzuzufügen (dies liegt daran, dass ich das Repository-Muster verwende, andernfalls würde das Modell haben auch die Methoden save, update, find usw.).

Repositories (Repository Pattern) : Am Anfang war ich sehr verwirrt. Und wie Sie dachte ich: "Nun, ich benutze MySQL und das ist es.".

Ich habe jedoch die Vor- und Nachteile der Verwendung des Repository-Musters abgewogen und verwende es jetzt. Ich denke, dass jetzt in diesem Moment nur MySQL verwenden muss. Aber wenn ich in drei Jahren zu etwas wie MongoDB wechseln muss, ist der größte Teil der Arbeit erledigt. Alles auf Kosten einer zusätzlichen Schnittstelle und einer $app->bind(«interface», «repository»).

Ereignisse ( Observer Pattern ): Ereignisse sind nützlich für Dinge, die zu jeder Zeit in jeder Klasse geworfen werden können. Denken Sie beispielsweise daran, Benachrichtigungen an einen Benutzer zu senden. Bei Bedarf lösen Sie das Ereignis aus, um eine Benachrichtigung an eine beliebige Klasse Ihrer Anwendung zu senden. Dann können Sie eine Klasse wie UserNotificationEvents haben, die alle Ihre ausgelösten Ereignisse für Benutzerbenachrichtigungen behandelt.

Dienste : Bisher haben Sie die Wahl, Steuerungen oder Modellen Logik hinzuzufügen. Für mich ist es sinnvoll, die Logik in Services einzufügen. Seien wir ehrlich, Services ist ein ausgefallener Name für Klassen. Und Sie können so viele Klassen haben, wie es für Sie in Ihrer Anwendung sinnvoll ist.

Nehmen Sie folgendes Beispiel: Vor kurzem habe ich so etwas wie Google Forms entwickelt. Ich begann mit einem CustomFormService und endete mit CustomFormService, CustomFormRender, CustomFieldService, CustomFieldRender, CustomAnswerService und CustomAnswerRender. Warum? Weil es für mich Sinn machte. Wenn Sie mit einem Team arbeiten, sollten Sie Ihre Logik dort einsetzen, wo es für das Team sinnvoll ist.

Der Vorteil der Verwendung von Diensten gegenüber Controllern/Modellen besteht darin, dass Sie nicht an einen einzelnen Controller oder ein einzelnes Modell gebunden sind. Sie können je nach Design und Anforderungen Ihrer Anwendung beliebig viele Services erstellen. Hinzu kommt der Vorteil, dass Sie einen Service in einer beliebigen Klasse Ihrer Anwendung aufrufen können.

Das dauert lange, aber ich möchte Ihnen zeigen, wie ich meine Bewerbung strukturiert habe:

app/
    controllers/
    MyCompany/
        Composers/
        Exceptions/
        Models/
        Observers/
        Sanitizers/
        ServiceProviders/
        Services/
        Validators/
    views
    (...)

Ich benutze jeden Ordner für eine bestimmte Funktion. Beispielsweise enthält das Verzeichnis Validators eine Klasse BaseValidator, die für die Verarbeitung der Validierung verantwortlich ist und auf den $rules Und $messages Der spezifischen Validatoren basiert (normalerweise eine für jedes Modell) ). Ich könnte diesen Code genauso einfach in einen Dienst einfügen, aber es ist für mich sinnvoll, einen bestimmten Ordner dafür zu haben, auch wenn er (vorerst) nur innerhalb des Dienstes verwendet wird.

Ich empfehle Ihnen, die folgenden Artikel zu lesen, da sie Ihnen die Dinge ein wenig besser erklären könnten:

Breaking the Mold von Dayle Rees (Autor von CodeBright): Hier habe ich alles zusammengestellt, obwohl ich ein paar Dinge geändert habe, um sie meinen Bedürfnissen anzupassen.

Entkoppeln Sie Ihren Code in Laravel using Repositories and Services von Chris Goosey: In diesem Beitrag wird genau erklärt, was ein Service und das Repository-Muster sind und wie sie zusammenpassen.

Laracasts haben auch die Repositories Simplified und Single Responsibility , die gute Ressourcen mit praktischen Beispielen sind (obwohl Sie bezahlen müssen).

142
Luís Cruz

Ich wollte eine Antwort auf meine eigene Frage posten. Ich könnte tagelang darüber reden, aber ich werde versuchen, dies schnell bekannt zu machen, um sicherzugehen, dass ich es hinbekomme.

Am Ende habe ich die vorhandene Struktur verwendet, die Laravel zur Verfügung stellt, was bedeutet, dass ich meine Dateien hauptsächlich als Modell, Ansicht und Controller gespeichert habe. Ich habe auch einen Bibliotheksordner für wiederverwendbare Komponenten, die nicht wirklich Modelle sind .

I DID MEINE MODELLE NICHT IN DIENSTLEISTUNGEN/BIBLIOTHEKEN VERPACKT . Alle angegebenen Gründe haben mich nicht zu 100% überzeugt Auch wenn ich mich irre, führen sie, soweit ich sehe, zu einer Menge zusätzlicher, fast leerer Dateien, die ich beim Arbeiten mit Modellen erstellen und wechseln muss, und verringern den Nutzen der Verwendung von eloquent ( insbesondere, wenn es darum geht, Modelle wiederzugewinnen, z. B. durch Paginierung, Gültigkeitsbereiche usw.).

Ich setze die Geschäftslogik in die MODELLE und greife direkt von meinen Controllern aus auf beredte Fragen zu. Ich benutze eine Reihe von Ansätzen, um sicherzustellen, dass die Geschäftslogik nicht umgangen wird:

  • Accessoren und Mutatoren: Laravel hat großartige Accessoren und Mutatoren. Wenn ich eine Aktion ausführen möchte, wenn ein Beitrag verschoben wird Vom Entwurf bis zur Veröffentlichung kann ich dies aufrufen, indem ich die Funktion setIsPublishedAttribute erstelle und die Logik darin einbinde
  • Überschreiben von "Erstellen/Aktualisieren" usw.: Sie können in Ihren Modellen immer eloquente Methoden überschreiben, um benutzerdefinierte Funktionen einzuschließen. Auf diese Weise können Sie die Funktionalität für jede CRUD-Operation aufrufen. Edit: Ich denke, es gibt einen Fehler mit überschreibendem Erstellen in neueren Laravel Versionen (also verwende ich Ereignisse, die jetzt im Boot registriert sind)
  • Validierung: Ich binde meine Validierung auf die gleiche Weise ein, z. B. indem ich CRUD-Funktionen und bei Bedarf auch Accessoren/Mutatoren überschreibe. Weitere Informationen finden Sie unter Esensi oder dwightwatson/validating.
  • Magische Methoden: Ich verwende die Methoden __get und __set meiner Modelle, um mich gegebenenfalls in die Funktionalität einzuklinken
  • Erweitern von Eloquent: Wenn Sie eine Aktion ausführen möchten, die alle Aktualisierungen/Erstellungsvorgänge betrifft, können Sie Eloquent sogar erweitern und auf mehrere Modelle anwenden.
  • Ereignisse: Dies ist ein unkomplizierter und allgemein vereinbarter Ort, um dies ebenfalls zu tun. Der größte Nachteil bei Ereignissen ist meines Erachtens, dass Ausnahmen schwer nachzuvollziehen sind (möglicherweise nicht der neue Fall bei Laravels neuem Ereignissystem). Ich gruppiere meine Ereignisse auch gerne nach dem, was sie tun, anstatt nach dem Zeitpunkt, zu dem sie aufgerufen werden ... z.
  • Hinzufügen von Pivot/BelongsToMany-Ereignissen: Eines der Dinge, mit denen ich am längsten zu kämpfen hatte, war, wie man Verhalten mit der Änderung von belongToMany-Beziehungen verbindet. ZB Ausführen einer Aktion, wenn ein Benutzer einer Gruppe beitritt. Ich bin fast fertig damit, eine benutzerdefinierte Bibliothek dafür aufzupolieren. Ich habe es noch nicht veröffentlicht, aber es funktioniert! Ich werde versuchen, bald einen Link zu posten. [~ # ~] edit [~ # ~] Am Ende habe ich alle meine Pivots zu normalen Modellen gemacht und mein Leben war so viel einfacher ...

Berücksichtigung der Bedenken der Menschen hinsichtlich der Verwendung von Modellen:

  • Organisation: Ja, wenn Sie mehr Logik in Modelle einfügen, können diese länger sein, aber ich habe festgestellt, dass 75% meiner Modelle immer noch ziemlich klein sind . Wenn ich mich dazu entschlossen habe, die größeren zu organisieren, kann ich dazu Merkmale verwenden (z. B. einen Ordner für das Modell mit einigen weiteren Dateien wie PostScopes, PostAccessors, PostValidation usw. nach Bedarf erstellen). Ich weiß, das ist nicht unbedingt das, wofür Eigenschaften sind, aber dieses System funktioniert ohne Probleme.

Zusätzlicher Hinweis: Ich habe das Gefühl, Ihre Modelle in Dienstleistungen einzupacken, als hätte man ein Schweizer Taschenmesser mit vielen Werkzeugen und baue im Grunde genommen ein anderes Messer darum macht das selbe Ja, manchmal möchten Sie vielleicht eine Klinge abkleben oder sicherstellen, dass zwei Klingen zusammen verwendet werden. Aber es gibt normalerweise andere Möglichkeiten, dies zu tun.

WENN DIENSTLEISTUNGEN VERWENDET WERDEN : Dieser Artikel enthält sehr gute Beispiele für die Verwendung von Diensten ( oft ). Er sagt im Grunde, wenn Ihr Objekt verwendet mehrere Modelle oder Modelle an seltsamen Stellen ihres Lebenszyklus ist es sinnvoll. http://www.justinweiss.com/articles/where-do-you-put-your-code/

19
Sabrina Leggett

Was ich zum Erstellen der Logik zwischen Controllern und Modellen verwende, ist das Erstellen einer Serviceebene . Grundsätzlich ist dies mein Ablauf für jede Aktion in meiner App:

  1. Der Controller erhält die vom Benutzer angeforderte Aktion und sendet Parameter und delegiert alles an eine Serviceklasse.
  2. Die Serviceklasse führt die gesamte mit der Operation verbundene Logik aus: Eingabevalidierung, Ereignisprotokollierung, Datenbankoperationen usw.
  3. Das Modell enthält Informationen zu Feldern, Datentransformationen und Definitionen von Attributvalidierungen.

So mache ich es:

Dies ist die Methode eines Controllers, um etwas zu erstellen:

public function processCreateCongregation()
{
    // Get input data.
    $congregation                 = new Congregation;
    $congregation->name           = Input::get('name');
    $congregation->address        = Input::get('address');
    $congregation->pm_day_of_week = Input::get('pm_day_of_week');
    $pmHours                      = Input::get('pm_datetime_hours');
    $pmMinutes                    = Input::get('pm_datetime_minutes');
    $congregation->pm_datetime    = Carbon::createFromTime($pmHours, $pmMinutes, 0);

    // Delegates actual operation to service.
    try
    {
        CongregationService::createCongregation($congregation);
        $this->success(trans('messages.congregationCreated'));
        return Redirect::route('congregations.list');
    }
    catch (ValidationException $e)
    {
        // Catch validation errors thrown by service operation.
        return Redirect::route('congregations.create')
            ->withInput(Input::all())
            ->withErrors($e->getValidator());
    }
    catch (Exception $e)
    {
        // Catch any unexpected exception.
        return $this->unexpected($e);
    }
}

Dies ist die Serviceklasse, die die mit der Operation verbundene Logik ausführt:

public static function createCongregation(Congregation $congregation)
{
    // Log the operation.
    Log::info('Create congregation.', compact('congregation'));

    // Validate data.
    $validator = $congregation->getValidator();

    if ($validator->fails())
    {
        throw new ValidationException($validator);
    }

    // Save to the database.
    $congregation->created_by = Auth::user()->id;
    $congregation->updated_by = Auth::user()->id;

    $congregation->save();
}

Und das ist mein Modell:

class Congregation extends Eloquent
{
    protected $table = 'congregations';

    public function getValidator()
    {
        $data = array(
            'name' => $this->name,
            'address' => $this->address,
            'pm_day_of_week' => $this->pm_day_of_week,
            'pm_datetime' => $this->pm_datetime,
        );

        $rules = array(
            'name' => ['required', 'unique:congregations'],
            'address' => ['required'],
            'pm_day_of_week' => ['required', 'integer', 'between:0,6'],
            'pm_datetime' => ['required', 'regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/'],
        );

        return Validator::make($data, $rules);
    }

    public function getDates()
    {
        return array_merge_recursive(parent::getDates(), array(
            'pm_datetime',
            'cbs_datetime',
        ));
    }
}

Für weitere Informationen über diese Art verwende ich, um meinen Code für eine Laravel App zu organisieren: https://github.com/rmariuzzo/Pitimi

17
Rubens Mariuzzo

Meiner Meinung nach hat Laravel bereits viele Optionen zum Speichern Ihrer Geschäftslogik.

Kurze Antwort:

  • Verwenden Sie die Request -Objekte von laravel, um Ihre Eingabe automatisch zu validieren, und speichern Sie dann die Daten in der Anforderung (erstellen Sie das Modell). Da alle Benutzereingaben direkt verfügbar sind in die Anfrage, halte ich es für sinnvoll, dies hier durchzuführen.
  • Verwenden Sie die Job -Objekte von laravel, um Aufgaben auszuführen, für die einzelne Komponenten erforderlich sind, und versenden Sie sie dann einfach. Ich denke, dass Job Serviceklassen umfasst. Sie führen eine Aufgabe wie Geschäftslogik aus.

Lange (er) Antwort:

Verwenden Sie bei Bedarf die Option "Repositorys verwenden": Repositorys sind mit Sicherheit überlastet und werden die meiste Zeit nur als accessor für das Modell verwendet. Ich habe das Gefühl, dass sie definitiv einen Nutzen haben, aber es sei denn, Sie entwickeln eine massive Anwendung, die erfordert diese Flexibilität, damit Sie laravel ganz aufgeben können, Halten Sie sich von Repositories fern. Sie werden sich später bei Ihnen bedanken und Ihr Code wird viel einfacher.

Fragen Sie sich, ob die Möglichkeit besteht, dass Sie PHP frameworks or zu einem Datenbanktyp ändern, der laravel doesn ' t unterstützen.

Wenn Ihre Antwort "Wahrscheinlich nicht" lautet, implementieren Sie das Repository-Muster nicht.

Darüber hinaus sollten Sie ein Muster nicht auf ein hervorragendes ORM wie Eloquent legen. Sie erhöhen lediglich die Komplexität, die nicht erforderlich ist, und Sie profitieren überhaupt nicht davon.

Verwenden Sie Services sparsam: Serviceklassen sind für mich nur ein Ort zum Speichern von Geschäftslogik, um eine bestimmte Aufgabe mit den angegebenen Abhängigkeiten auszuführen. Laravel hat diese "Jobs" genannt, und sie haben viel mehr Flexibilität als eine benutzerdefinierte Serviceklasse.

Ich denke, Laravel hat eine umfassende Lösung für das logische Problem MVC. Es ist nur eine Sache oder Organisation.

Beispiel:

Anfrage:

namespace App\Http\Requests;

use App\Post;
use App\Jobs\PostNotifier;
use App\Events\PostWasCreated;
use App\Http\Requests\Request;

class PostRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title'       => 'required',
            'description' => 'required'
        ];
    }

    /**
     * Save the post.
     *
     * @param Post $post
     *
     * @return bool
     */
    public function persist(Post $post)
    {
        if (!$post->exists) {
            // If the post doesn't exist, we'll assign the
            // post as created by the current user.
            $post->user_id = auth()->id();
        }

        $post->title = $this->title;
        $post->description = $this->description;

        // Perform other tasks, maybe fire an event, dispatch a job.

        if ($post->save()) {
            // Maybe we'll fire an event here that we can catch somewhere else that
            // needs to know when a post was created.
            event(new PostWasCreated($post));

            // Maybe we'll notify some users of the new post as well.
            dispatch(new PostNotifier($post));

            return true;
        }

        return false;
    }
}

Controller:

namespace App\Http\Controllers;

use App\Post;
use App\Http\Requests\PostRequest;

class PostController extends Controller
{

   /**
    * Creates a new post.
    *
    * @return string
    */
    public function store(PostRequest $request)
    {
        if ($request->persist(new Post())) {
            flash()->success('Successfully created new post!');
        } else {
            flash()->error('There was an issue creating a post. Please try again.');
        }

        return redirect()->back();
    }

   /**
    * Updates a post.
    *
    * @return string
    */
    public function update(PostRequest $request, $id)
    {
        $post = Post::findOrFail($id);

        if ($request->persist($post)) {
            flash()->success('Successfully updated post!');
        } else {
            flash()->error('There was an issue updating this post. Please try again.');
        }

        return redirect()->back();
    }
}

Im obigen Beispiel wird die Anforderungseingabe automatisch validiert, und wir müssen nur die persist-Methode aufrufen und einen neuen Beitrag übergeben. Ich denke, Lesbarkeit und Wartbarkeit sollten immer komplexere und nicht benötigte Designmuster in den Schatten stellen.

Sie können dann die exakt gleiche Methode zum Aktualisieren von Posts verwenden, da wir prüfen können, ob der Post bereits vorhanden ist oder nicht, und bei Bedarf alternierende Logik ausführen.

9
Steve Bauman