PHP rocks! wünscht allen Mitgliedern einen guten Rutsch ins neue Jahr 2017 !!!
Hinweis: Das Forum zieht um! Um keine Datenverluste zu haben, schalten wir zwecks Übernahme der Daten das Forum am Sonntag, den 24.04.2016 um ca. 21:00 Uhr offline und passen anschliessend die DNS-Einträge an.
www.php-rocks.de wird euch dann nach den Aktualisierungen der DNS-Server wieder wie gewohnt uneingeschränkt zur Verfügung stehen.
Danke für euer Verständnis!

Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Best Practice für Response-Objekte
#1
Moin Moin

normalerweise setze ich meine Projekte mit Laravel um, aber jetzt steht bei mir auf Arbeit ein Projekt an, für welches Laravel schlicht zu groß / zu umfangreich ist. Für solche Fälle habe ich schon vor geraumer Zeit angefangen mir quasi eine Mikro-Version von Laravel zu basteln (jaja - ich hätte auch gleich ein existierendes Micro-Framework oder Lumen nutzen können Big Grin). Gleichzeitig will ich - so zumindest mein Plan - mittels dieses Mikro-Laravels meine Kollegen, die sich beharrlich weigern für Großprojekte ein anständiges PHP-Framework zu nutzen, gewissermaßen auf die sanfte Tour an Laravel ranführen.

Die ganze Geschichte die ich mir zurecht geschrieben habe funktioniert auch wunderbar, nur bräuchte ich an einer Stelle mal andere Meinungen als meine eigene. Konkret geht's um mein Response-Objekt, welches ich mir ab Framework-Start erzeuge. Meine Controller können, je nachdem was in den Methoden steht, entweder ein View-Objekt, ein Response-Objekt, ein Redirect-Objekt oder im ganz simpelstem Fall einen String zurückgeben. Sieht ganz ähnlich wie in Laravel aus:

PHP-Code:
// Controller gibt ein View-Objekt zurück
return view('test')->with([
 
   'foo' => 'bar'
]);

// Controller gibt ein Response-Objekt zurück
return response()->json([
 
   'foo' => 'bar'
]);

// Controller gibt ein Redirect-Objekt zurück
return redirect('foo')->with([
 
   'bar' => 'barfoo'
]);

// Controller gibt einen String zurück
return 'FooBar'


Diese Rückgabewerte wandern dann in die am Framework-Start erstellte Response-Instanz, die letztlich entscheidet was dem Client zurückgegeben wird und genau zu diesem Schnipsel hätte ich gerne ein paar Meinungen, denn so ganz zufrieden bin ich damit nicht. Nebenbei bemerkt: Es funktioniert alles so wie angedacht. Von daher besteht in der Hinsicht momentan kein Handlungsbedarf. Mir geht's in dem Punkt um die - wie im Titel schon erwähnten - Best Practices in dem Fall.

PHP-Code:
public function __destruct() {

 
   // Wenn Controller ein View-Objekt zurückgegeben hat
 
   if($this->response instanceof View) {
        $this->responseContent $this->response->render();
        echo $this->responseContent;
        session()->forget('_flashed');
    }

 
   // Wenn Controller ein Redirect-Objekt zurückgegeben hat
    if($this->response instanceof Redirect) {
        header('Location: ' config('app.url') . '/' $this->response->getTo());
    }

 
   // Wenn Controller ein Response-Objekt zurückgegeben hat
    if($this->response instanceof Response) {
        header(sprintf('HTTP/%s %s %s''1.1'$this->httpStatusCode''), true$this->httpStatusCode);

        foreach($this->headers as $flag => $value) {
            header($flag.':'.$valuetrue$this->httpStatusCode);
        }

        echo $this->responseContent;
        session()->forget('_flashed');
    }

 
   // Wenn Controller kein Objekt (zb. einen String) zurückgegeben hat
    if(!is_object($this->response)) {
        echo $this->response;
        session()->forget('_flashed');
    }


Grüße
Antworten
#2
Ich würde so was nicht über __destruct() laufen lassen, weil der Anwender dann zum Beispiel keine Chance hat, zu verhindern, dass der Code läuft. Ich würde eine explizite send()-Methode oder dergleichen einführen.

Wie wäre es mal mit einem elseif?

Das…

Code:
if($this->response instanceof View)
if($this->response instanceof Response)

…finde ich persönlich mit der Dopplung des Response-Begriffs etwas unschön. Eine Instanzvariable „response“ ist nur unter Umständen tatsächlich eine Response? Ich würde die Instanzvariable generischer benennen. Semantisch eher in der Richtung von „controllerReturnValue“.

Response ist der Name der Klasse selbst, die den Destruktor enthält? Falls ja, würde ich auch darüber noch mal nachdenken. Das würde mir dann etwas wirr erscheinen.

Das…

PHP-Code:
session()->forget('_flashed'); 

…ist mir etwas zu sehr hardcodet, aber eigentlich gar nicht dumm.

(Ich habe die Anforderung, Informationen einen Redirect überstehen zu lassen, in einem Projekt neulich nicht so zentralisiert abgehandelt.)
Antworten
#3
Moin mermshaus und danke für deinen Input.


Zitat:Ich würde so was nicht über __destruct() laufen lassen, weil der Anwender dann zum Beispiel keine Chance hat, zu verhindern, dass der Code läuft. Ich würde eine explizite send()-Methode oder dergleichen einführen.
Guter Einwand. Es wäre zwar kein Problem das nachzurüsten, nur ist derweil nicht vorgesehen, dass der Anwender an x-beliebiger Stelle ein $response->send() absetzen kann. Es erschien mir logisch den Kram über __destruct() zu lösen, da gerade dies im Moment das Letzte ist, was in meinem Konstrukt passiert und andererseits noch keine Application-Events in Form von zb. beforeRequest() / afterResponse() vorgesehen sind.


Zitat:…finde ich persönlich mit der Dopplung des Response-Begriffs etwas unschön. Eine Instanzvariable „response“ ist nur unter Umständen tatsächlich eine Response? Ich würde die Instanzvariable generischer benennen. Semantisch eher in der Richtung von „controllerReturnValue“.
Das ist mir im Nachgang auch aufgefallen und wurde schon ausgebessert.


Zitat:Das… …ist mir etwas zu sehr hardcodet, aber eigentlich gar nicht dumm.
Ich hab auch recht lange überlegt wie ich das am dümmsten löse, nur in der heutigen Diskussion mit mir selbst bin ich da nicht wirklich auf einen grünen Zweig gekommen. Big Grin

Nach einem kurzem Überflug über die entsprechenden Symfony / Laravel Komponenten könnte ich die Geschichte auch mit 2 Arrays in Form von flashOld und flashNew lösen, wobei flashNew an einer bestimmten Stelle eben flashOld überschreibt, aber da muss ich sehen wie ich das weiterstricke.

Grüße
Antworten
#4
(10.02.2016, 03:09)Scarabaeus schrieb: Guter Einwand. Es wäre zwar kein Problem das nachzurüsten, nur ist derweil nicht vorgesehen, dass der Anwender an x-beliebiger Stelle ein $response->send() absetzen kann. Es erschien mir logisch den Kram über __destruct() zu lösen, da gerade dies im Moment das Letzte ist, was in meinem Konstrukt passiert und andererseits noch keine Application-Events in Form von zb. beforeRequest() / afterResponse() vorgesehen sind.

Kann der Anwender nicht so oder so an allen Stellen, an denen er eine send-Methode aufrufen könnte, auch die Instanz überschreiben und dadurch den Destruktor auslösen?
Antworten
#5
Moin

Zitat:Kann der Anwender nicht so oder so an allen Stellen, an denen er eine send-Methode aufrufen könnte, auch die Instanz überschreiben und dadurch den Destruktor auslösen?

Sicher kann er das. Alle wichtigen Instanzen werden in einem IoC-Container abgelegt und das schließt hinzufügen, überschreiben und löschen von Instanzen ein. Das wäre zwar ein nicht ganz unerheblicher Grund eine send() Methode einzubauen, nur kann ich den Anwender nicht daran hindern, dass dieser mit den gegebenen Mitteln eventuell Mist baut. Sprich selbst wenn ich eine send() Methode einbaue, hindert es den Anwender nicht zb. alles aus dem IoC zu löschen. Aber das ist nunmal als Möglichkeit eben gegeben und bisher hat es meines Wissens nach kein Framework / Programmiersprache / Whatever geschafft, wissentlich herbeigeführte Fehler oder Unwissenheit des Anwenders automatisch zu kompensieren Wink

Grüße
Antworten
#6
Kurzes Update hierzu:

Ich habe für mich doch noch einen Anwendungsfall gefunden bei dem ich eine Response->send() Methode brauchte. Konkret gings darum, dass ich im Konstruktor eines Controllers prüfen wollte, ob jener Controller überhaupt von nicht authentifizierten Benutzern aufgrufen werden darf. Wenn dies nicht der Fall war, sollte der Benutzer entsprechend weitergeleitet werden. Und naja...das hieß halt vor der eigentlich vorgesehenen Response eine entsprechende RedirectResponse abzufeuern Big Grin

Grüße und schönes WE
Antworten
#7
Kannst du nicht so was im Code der Action machen?

PHP-Code:
if ($notLoggedIn) {
    return new 
RedirectResponse($targetUrl);
}

// Code if logged in
// ... 

Dann bleibt es hübsch schematisch:

PHP-Code:
$application = new Application();
$request = new Request();
$response $application->dispatch($request);
//$response->send(); 
Antworten
#8
Moinsen

Zitat:Kannst du nicht so was im Code der Action machen?



Code:
if ($notLoggedIn) {
    return new RedirectResponse($targetUrl);
}

// Code if logged in
// ...

Das könnte ich auch machen, aber das würde - je nachdem wieviele Actions geprüft werden müssen - zu Codewiederholungen führen. Ich würde zwar - wie du sagtest - damit den regulären Ablauf nicht übergehen, aber ich sehe in meiner derzeitigen Vorgehensweise jetzt keine direkte Verletzung des EVA-Prinzipes. Ich hab auch vorher überlegt ob es legitim ist, aus einem Konstruktor heraus letztlich ein header('Location') abzusetzen. Am Ende war ich dann der Meinung, dass dies ja keine Rückgabe im Sinne von return ist.

Aber davon abgesehen habe ich jenen Check bereits in eine separate Auth-Klasse ausgelagert. Jene Check-Methode müsste ich nur um einen Parameter erweitern, um entweder entsprechende Actions des Controllers vom Check auszuklammern oder einzuschließen.

PHP-Code:
public function __construct() {
 
   auth()->check(['except' => ['getIndex''getLogin''postLogin']]);

 
   // bzw.

 
   auth()->check(['only' => ['postEdit''postDelete']]);


Grüße
Antworten
#9
Ich habe bei meinem letzten Post auch an Testbarkeit gedacht. Wenn du auch bei Redirects eine Response-Instanz von der Application erhältst, kannst du dazu gut Tests schreiben, die einfach prüfen, ob in der Response-Instanz das drin ist, was drin sein sollte (Location-Header etwa), ohne den Redirect dann tatsächlich auszuführen.

Aber das nur noch so als Gedanke am Rande. Letztlich kann man eh nichts Konkretes sagen, wenn man die genaue Arbeitsweise deines Codes nicht kennt. (Was aber okay ist.)
Antworten


Gehe zu: