Tacsiazuma
Tacsiazuma A letscode.hu alapitója, több, mint egy évtized fejlesztői tapasztalattal. Neovim függő hobbi pilóta.

Controller és middleware a két jóbarát

Most, hogy a routingot tisztába tettük, nézzünk egy kicsit bele abba, hogy azokat a route-okat, amiket belőttünk miféle controllerekbe tudjuk belevezetni? Hiszen nem gondolhatjuk komolyan, hogy a kérések kezelésének logikáját a routes.php-ban írjuk meg, nemde? Főleg, hogy az MVC-ben ez lenne a controllerek dolga. Ezek általánosan az app/Http/Controllers mappába kerülnek.Xbox-360-S-Controller

Megjegyzés: Minden controllernek extendelnie kell a Controller osztályt.

Nézzünk egy egyszerű példát:

1
2
3
4
5
6
7
8
9
10
11
<?php // /app/Http/Controllers/MockController.php követjük a PSR standardot

namespace App\Http\Controllers;

class MockController extends Controller { 

   public function index() { // minden meghívott metódusnak publikusnak kell lennie, hogy a dispatcher elérje azt.
      return view(); // a template fájlokat itt adjuk vissza
   }

}

Na most akkor nézzük, hogy is tudunk hivatkozni erre a controller/action párosra (egy kis ismétlés gyanánt):

1
2
3
4
5
6
7
Route::get("/", [
    "uses" => "MockController@index",  // itt adhatjuk meg a controller/action párost
    "as" => "home" // itt pedig a route nevét, ami néven később tudunk rá hivatkozni
]);

route("home") // az előző route-hoz tartozó URL-t adja vissza
// blade templateben ugyanez: 

Implicit routingparental-advisory-explicit-lyrics-C

Igazából most jövök rá, hogy pár dolog kimaradt a routing szekcióból, de most pótlom őket. Nos, kicsit fárasztó lenne minden egyes metódust lemappelni, nemde? Emiatt laravelben lehetőségünk van arra, hogy csak a controllert adjuk meg a route-hoz, míg a metódusok nevét a Laravel összekapcsolja nekünk.

1
2
3
4
5
6
Route::controller("/posts", "PostController", [ // csak az URL tagot és a controller nevét adjuk meg. 
"getIndex" => "home"] ); // itt is lehetőségünk van az egyes mappelt metódusokhoz nevet rendelni, amit később az URL helperrel használhatunk

// a GET /posts/index url a PostController@getIndex-re mutat
// a POST /posts/test-view a PostController@postTestView-ra mutat. Ez az ún. dash-syntax. A kötőjelet követő betű nagybetűvé lesz alakítva és a kötőjel ki lesz purgálva a névből.

Ha olyan metódust írunk be, amihez tartozó metódus nem létezik, akkor 404-es oldalra leszünk irányítva.

Middleware-ek definiálás a kontrollerben is lehetséges:

1
2
3
4
5
public function __construct() {
  $this->middleware("auth"); // a konstruktorban helyezzük mindezt el, és a sorrend számít
  $this->middleware("log", array("only" => "getIndex")); // beállíthatjuk, hogy csak bizonyos metódusokra legyen érvényes
  $this->middleware("mock" array("except" => "getIndex")); // vagy épp azt, hogy melyekre ne
}

Pihentessük a szemünket!

Laravelben lehetőségünk van arra, hogy ún. RESTful controller route-okat definiáljunk.

1
Route::resource("photo", "PhotoController");

Ahhoz, hogy egy ilyen controllert létrehozzunk, a következőt kell bepötyögni a laravel rootjában állva:

1
php artisan make:controller PhotoController

Ez legenerálja nekünk a controllert, valamint a hozzá tartozó action-öket, persze üresen. Ezeket az action-öket az alábbiak szerint mappeli nekünk ez a route beállítás:

  GET   /photo   index   photo.index  
  GET   /photo/create   create   photo.create  
  POST   /photo   store   photo.store  
  GET   /photo/{photo}   show   photo.show  
  GET   /photo/{photo}/edit   edit   photo.edit  
  PUT/PATCH   /photo/{photo}   update   photo.update  
  DELETE   /photo/{photo}   destroy   photo.destroy  

Persze itt is lehetőségünk nyílik korlátozni, hogy mely action-öket szeretnénk a route-on meghagyni/vagy épp kizárni:

1
2
Route::resource("photo", array("only" => array("index", "show")));

1
2
Route::resource('photo', 'PhotoController',
 ['except' => ['create', 'store', 'update', 'destroy']]);

Lehetőségünk van továbbá egymásba ágyazni ezeket a Resource route-okat. Csupán az ún. dot notation-t kell használni, vagyis pontokkal válasszuk el egymástól őket:

1
Route::resource("photos.comments","PhotoCommentController");

Az ilyen módon regisztrált route-okat így tudjuk elérni: photos/{photos}/comments/{comments}, és a paramétereket hasonlóképpen kapják meg:

1
class PhotoCommentController extends Controller {
1
2
3
4
5
6
7
8
9
10
11
12
 /**
 * Show the specified photo comment.
 *
 * @param int $photoId
 * @param int $commentId
 * @return Response
 */
     public function show($photoId, $commentId)
     {
       //
     }
}

A fenti példában látszik, hogy a két szükséges ID-t megkapjuk paraméterben.

Ha más route-okat is szeretnénk definiálni a Resource controller alapértelmezettjei felül akkor azokat a Route::resource definiálása előtt tegyük meg:

1
2
Route::get('photos/popular', 'PhotoController@method'); // csináltunk egy újabb endpointot
Route::resource('photos', 'PhotoController'); // valamint az alap resource route-okat is fellőttük

Az előző cikkben már használtunk Dependency Injection-t az egyik metódusunknál, ahol egy model osztályt vártunk paraméterként. Ugyanezt megtehetjük a konstruktorunk esetében is. A controllerek kiszolgálását a laravelben az ún. service container végzi, amit egy későbbi cikkben kitárgyalunk. Ilyen formában Modeleket, Request objektumot és még sok mást átadhatunk paraméterként a konstruktornak vagy épp az adott actionnek.

Middleware

A Laravelben a Middleware-ek szolgálnak egyfajta pre-dispatch hooknak, vagyis mielőtt a controllerhez érne egy adott request, előtte ezeken az ún. Middleware-eken át megszűrjük azt. Ilyen middleware az authentikációért felelős, ami továbbenged, ha be vagyunk jelentkezve, vagy épp redirektál egy loginoldalra. Számos ilyet csomagoltak a keretrendszerrel, amik egyből működésre készek. Ezek az osztályok az app/Http/Middleware könyvtárban csücsülnek.

Na de mégis mibe fáj egy ilyet létrehozni?

Ahogy a legtöbb dologban a laravelnél, két út létezik, az egyik a kényelmes megoldás, ami csupán annyi, hogy kiadjuk a következő parancsot:

1
php artisan make:middleware [middleware-neve]

a másik pedig olyan, mintha sajtreszelővel ****nánk amikor kézzel belekulákoljuk a könyvtárba az osztályt

1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace App\Http\Middleware;

class LimitBeer {
    public function handle($request, \Closure $next) { // ezeket a paramétereket kapjuk meg, ezt a sémát követnie kell 
        
        if ($request->input("beer") == 1) { // ha a bejövő paraméterek közül a beer értéke = 1, akkor redirektáljuk a kérést.
            return redirect("egySorAzNemSor");
        }        
        return $next($request); // továbbadjuk a $request objektumot a következő middleware-nek vagy a controllernek
    } 
}

és a Kernel.php-ben is bevezetjük azt, hogy tudjunk rá aliassal hivatkozni:

1
2
3
protected $routeMiddleware = [
    'limit' => \App\Http\Middleware\LimitBeer::class,
];

Ha ez utóbbit nem tesszük meg, akkor a fully qualified classsname-el tudunk rá hivatkozni, úgy is felismeri a rendszer.

A middleware-ek futhatnak a request feldolgozása előtt, vagy éppen utána. Ez csak attól függ, hogy is hoztuk őket létre:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php namespace App\Http\Middleware;

use Closure;

class BeforeMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        // Perform action

        return $next($request); // az előzőekben már látott formában továbbadjuk a request-et a következő felelősnek
    }
}

A fenti módszer ugyanolyan, mint amit legelőször bemutattam, jön a request, moleszteráljuk egy keveset és továbbadjuk azt. Azonban ha a request feldolgozása után szeretnénk valamit, akkor azt a következő módon tudjuk elvégezni:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php namespace App\Http\Middleware;

use Closure;

class AfterMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        $response = $next($request); // itt továbbadjuk a következőnek, az megint a következőnek és így tovább, míg végül a controller is megkapja és a végső feldolgozott állapotot kapjuk vissza, így a Response objektum áll rendelkezésünkre.
 
        // Perform action

        return $response; // majd a választ dobjuk tovább
    }
}

Ha a middleware-jeinket szeretnénk MINDEN route esetében használni, akkor lehetőség nyílik ún. globális middleware-ek definiálására is, szintén a Kernel osztályban:

1
2
3
protected $middleware = [ // ez a tömb szolgál a globális middleware-ek részére
    \App\Http\Middleware\LimitBeer::class, // itt nem kell megadni aliast, mert nem hívhatók meg direktben, maguktól futnak le
];

Terminable Middleware

Vannak esetek, mikor a HTTP response kiküldése után szeretnénk még valamit ügyködni. A “session” middleware, ami a laravellel érkezik pont ezt csinálja, a session adatokat írja be a storage-be, miután ki lett küldve a response. Ahhoz, hogy ezt a feladatot ellássuk, ún. “terminable” middlewaret kell létrehozzunk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
use Closure;
use Illuminate\Contracts\Routing\TerminableMiddleware;

class StartSession implements TerminableMiddleware {

    public function handle($request, Closure $next)
    {
        return $next($request); // ugyanúgy működik, ahogy a többi before middleware
    }
// itt már implementálnunk kell a terminate metódust is
    public function terminate($request, $response) // megkapjuk a request és a response objektumot egyaránt
    {   
        // Store the session data...
    }

}

Ahogy a példán is látszik, itt már nem elég a handle metódust implementáljuk, a terminate metódust is kell, amiben megkapjuk a request és response objektumokat is, hogy azokkal operáljunk. Ezeket a middleware-eket a globális middleware-ek között kell regisztrálnunk a Kernel osztályunkban.

A controllerekről és middleware-ekről ennyit, legközelebb a request és response osztályoknak nézünk a körmére!

comments powered by Disqus