Budowa własnego systemu middleware na przykładzie Koseven Framework
Middleware to wzorzec programistyczny używany głównie w aplikacjach webowych, który pozwala na przechwycenie i przetworzenie żądania HTTP przed (lub czasem po) jego obsłużeniem przez kontroler.
Co to dokładnie znaczy?
Middleware działa jak warstwa pośrednia między użytkownikiem a aplikacją. Zanim żądanie trafi do kontrolera (czyli np. logiki, która zwraca stronę), middleware może:
- analizować to żądanie,
- zmieniać je,
- zablokować,
- przekierować,
- albo dodać coś do odpowiedzi.
Do czego służy middleware?
Typowe zastosowania middleware:
Zastosowanie | Opis |
---|---|
Autoryzacja | Sprawdza, czy użytkownik jest zalogowany lub ma odpowiednie uprawnienia. |
Logowanie żądań | Zapisuje logi z każdego żądania (np. do pliku, bazy danych). |
Obsługa CORS | Ustawia nagłówki, które pozwalają na żądania z innych domen. |
Ochrona przed CSRF | Sprawdza tokeny bezpieczeństwa w formularzach. |
Rate limiting | Ogranicza liczbę żądań od danego użytkownika (np. 100/h). |
Czyszczenie danych | Modyfikuje dane wejściowe (np. trim, sanitize). |
Przykład działania (schemat)
[Przeglądarka] --> [Middleware: Auth] --> [Middleware: Log] --> [Kontroler] --> [Odpowiedź]
Każda warstwa może:
- przepuścić dalej żądanie,
- zmienić je,
- albo je przerwać (np. zwracając redirect do logowania).
W praktyce
- W Laravelu:
$this->middleware('auth')
- W Symfony: event subscribers/listeners
- W Express.js (Node.js):
app.use(middlewareFunction)
- W Koseven/Kohana – trzeba middleware zbudować samemu
Globalny system middleware
W Koseven (fork Kohana 3.3), system middleware nie jest domyślnie wspierany w taki sposób jak np. w Laravelu czy Symfony, ale można go łatwo zaimplementować poprzez modyfikację procesu żądań w pliku bootstrap.php
. Poniżej znajdziesz przykład prostego systemu middleware, który działa globalnie i jest zdefiniowany w bootstrap.php
.
Krok 1: Utwórz klasę Middleware
Utwórz katalog np. classes/Middleware
i stwórz tam pliki klas middleware.
Przykład: classes/Middleware/Auth.php
<?php defined('SYSPATH') or die('No direct script access.');
class Middleware_Auth {
public static function handle(Request $request)
{
// Przykład: prosty auth check
if ($request->uri() === 'admin' && !Auth::instance()->logged_in()) {
// Przekieruj nieautoryzowanego użytkownika
HTTP::redirect('/login');
}
}
}
Możesz dodać wiele klas, np. Middleware/Log.php
, Middleware/Cors.php
, itp.
Krok 2: Zdefiniuj i uruchom middleware w bootstrap.php
W pliku application/bootstrap.php
, zaraz przed wywołaniem $response = Request::factory(...)->execute()->send_headers()->body();
, dodaj logikę do ładowania middleware:
// application/bootstrap.php
// ...
// Create the request instance
$request = Request::factory();
// Lista middleware do wykonania (kolejność ma znaczenie)
$middlewares = [
'Middleware_Auth',
// 'Middleware_Log',
// 'Middleware_Cors',
];
foreach ($middlewares as $middleware) {
if (class_exists($middleware) && method_exists($middleware, 'handle')) {
$middleware::handle($request);
}
}
// Kontynuuj jak zwykle
$response = $request->execute()
->send_headers()
->body();
Co to daje?
- Middleware są definiowane centralnie w jednym miejscu.
- Można łatwo dodawać/usuwać middleware.
- Middleware działa niezależnie od kontrolerów.
- Middleware mogą modyfikować żądanie lub odpowiedź (zwracać redirect, ustawiać nagłówki itp.).
Opcjonalnie: Middleware z możliwością zatrzymania wykonania
Jeśli chcesz, by middleware mogło przerwać dalsze wykonywanie, możesz zmodyfikować handle()
tak, by zwracało np. Response
, np.:
$response = null;
foreach ($middlewares as $middleware) {
$result = $middleware::handle($request);
if ($result instanceof Response) {
// Middleware zakończyło przetwarzanie
$response = $result;
break;
}
}
if (!$response) {
$response = $request->execute();
}
$response->send_headers()->body();
Obsługa routingu
W Koseven (index.php
) tworzony jest obiekt Request::factory()
, a bootstrap.php
służy głównie do konfiguracji (modułów, logów, routingu, itp.). Jeśli chcesz mieć middleware przypisane do konkretnych route’ów (np. tylko do panelu admina), to najlepiej będzie rozszerzyć system routingu tak, by pozwalał na przypisanie middleware do wybranych tras – czyli coś w stylu:
Route::set('admin', 'admin(/<controller>(/<action>(/<id>)))')
->middleware(['auth'])
->defaults([
'directory' => 'Admin',
'controller' => 'Dashboard',
'action' => 'index',
]);
Kohana/Koseven nie wspiera ->middleware(...)
domyślnie, więc trzeba to dodać samemu.
Rozbudowa systemu routingu o middleware
1. Rozszerz klasę Route
Utwórz plik:
classes/Route.php
<?php defined('SYSPATH') or die('No direct script access.');
class Route extends Kohana_Route {
protected $_middleware = [];
public function middleware(array $middleware)
{
$this->_middleware = $middleware;
return $this;
}
public function get_middleware()
{
return $this->_middleware;
}
}
Dzięki temu możesz przypisać middleware do każdej trasy.
2. Zmień index.php
, by uruchamiał middleware dla dopasowanej trasy
W index.php
, po Request::factory()
ale przed ->execute()
, dodaj uruchamianie middleware:
<?php
require SYSPATH.'classes/Kohana/Core'.EXT;
require APPPATH.'classes/Kohana'.EXT;
// Load the request instance
$request = Request::factory();
// Znajdź dopasowaną trasę
$route = Route::all();
$matched = null;
foreach ($route as $r) {
if ($r->matches($request)) {
$matched = $r;
break;
}
}
if ($matched && method_exists($matched, 'get_middleware')) {
foreach ($matched->get_middleware() as $middleware) {
$class = 'Middleware_' . ucfirst($middleware);
if (class_exists($class) && method_exists($class, 'handle')) {
$response = $class::handle($request);
if ($response instanceof Response) {
// Middleware może przerwać dalsze przetwarzanie
$response->send_headers()->body();
exit;
}
}
}
}
// Wykonaj żądanie jak zwykle
$request->execute()
->send_headers()
->body();
3. Przykład Middleware
classes/Middleware/Auth.php
<?php defined('SYSPATH') or die('No direct script access.');
class Middleware_Auth {
public static function handle(Request $request)
{
if (!Auth::instance()->logged_in()) {
return Response::factory()
->redirect('/login');
}
return null;
}
}
Użycie: Definicja route’a z middleware
W bootstrap.php
:
Route::set('admin', 'admin(/<controller>(/<action>(/<id>)))')
->middleware(['auth']) // <-- to działa teraz!
->defaults([
'directory' => 'Admin',
'controller' => 'Dashboard',
'action' => 'index',
]);
Możliwości rozszerzeń
- Możesz wprowadzić aliasy (
->middleware(['auth', 'log'])
) - Możesz dodać kontekst do middleware, np.
auth:admin
,auth:user
- Możesz cache’ować dopasowane middleware dla danej trasy
Obsługa middleware w konstruktorze kontrolera
Jeśli chcesz, by middleware (np. auth) był wykonywany na poziomie kontrolera, to realizacja go w konstruktorze kontrolera bazowego to proste i idiomatyczne podejście w Koseven. Poniżej znajdziesz kompletną, czystą i rozszerzalną propozycję dla middleware auth
, który działa na poziomie konstruktorów kontrolerów.
Założenia
- Middleware
auth
powinien działać tylko dla kontrolerów wAdmin_
. - Kontrolery dziedziczą po wspólnej klasie
Controller_Admin_Base
. - Sprawdzenie logowania użytkownika odbywa się w konstruktorze.
Struktura plików
application/
classes/
Controller/
Admin/
Base.php <-- wspólny kontroler z middleware
Dashboard.php <-- konkretny kontroler
Middleware/
Auth.php <-- logika auth
1. Middleware/Auth.php
– logika middleware
<?php defined('SYSPATH') or die('No direct script access.');
class Middleware_Auth {
public static function check()
{
if (!Auth::instance()->logged_in()) {
HTTP::redirect('/login');
}
}
}
Możesz tu rozszerzyć logikę, np. sprawdzać role, tokeny itp.
2. Controller_Admin_Base
– kontroler bazowy z auth middleware
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Admin_Base extends Controller_Template {
public function before()
{
parent::before();
// Middleware auth
Middleware_Auth::check();
}
}
Jeśli chcesz, możesz dołożyć inne middleware jako osobne klasy.
3. Przykład: Controller_Admin_Dashboard
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Admin_Dashboard extends Controller_Admin_Base {
public function action_index()
{
$this->response->body('Panel administracyjny – witaj!');
}
}
Alternatywa: Middleware z warunkami
Jeśli chcesz kontrolować middleware np. tylko dla wybranych akcji:
public function before()
{
parent::before();
if ($this->request->action() !== 'public') {
Middleware_Auth::check();
}
}
Zalety tego podejścia
- Nie trzeba zmieniać
index.php
. - Przejrzysta struktura.
- Middleware działa tylko dla kontrolerów dziedziczących po
Controller_Admin_Base
. - Można łatwo rozbudować o kolejne middleware (
Log
,ACL
,Throttle
itp.).
Middleware w stylu Laravela
Jeśli chcesz uzyskać coś w stylu:
$this->middleware('auth');
czyli dynamiczne przypisywanie middleware do konkretnego kontrolera (lub akcji) – tak jak w Laravelu. W Koseven nie ma tego mechanizmu domyślnie, ale możemy go łatwo zasymulować i zaimplementować.
Plan działania
- Tworzymy metodę
middleware()
(np. w klasie bazowejController_Base
), która rejestruje middleware do wykonania. - W
before()
wywołujemy zarejestrowane middleware. -
Middleware może być przypisany do:
- wszystkich akcji (
global
) - tylko wybranych (
only
) - z wykluczeniami (
except
)
- wszystkich akcji (
1. Klasa bazowa Controller_Base
z obsługą middleware()
classes/Controller/Base.php
:
<?php defined('SYSPATH') or die('No direct script access.');
abstract class Controller_Base extends Controller_Template {
protected $_middleware = [];
public function middleware($name, array $options = [])
{
$this->_middleware[] = [
'name' => $name,
'only' => $options['only'] ?? [],
'except' => $options['except'] ?? [],
];
}
public function before()
{
parent::before();
foreach ($this->_middleware as $entry) {
$name = $entry['name'];
$only = $entry['only'];
$except = $entry['except'];
$action = $this->request->action();
if (!empty($only) && !in_array($action, $only)) {
continue;
}
if (!empty($except) && in_array($action, $except)) {
continue;
}
$class = 'Middleware_' . ucfirst($name);
if (class_exists($class) && method_exists($class, 'handle')) {
$response = $class::handle($this->request);
if ($response instanceof Response) {
// Przerwij i wyślij odpowiedź natychmiast
$response->send_headers()->body();
exit;
}
}
}
}
}
2. Middleware: Middleware/Auth.php
<?php defined('SYSPATH') or die('No direct script access.');
class Middleware_Auth {
public static function handle(Request $request)
{
if (!Auth::instance()->logged_in()) {
return Response::factory()->redirect('/login');
}
return null;
}
}
3. Użycie w kontrolerze
classes/Controller/Admin/Dashboard.php
:
<?php defined('SYSPATH') or die('No direct script access.');
class Controller_Admin_Dashboard extends Controller_Base {
public function before()
{
// Middleware 'auth' dla wszystkich akcji
$this->middleware('auth');
// Middleware tylko dla konkretnych akcji:
// $this->middleware('auth', ['only' => ['index', 'edit']]);
parent::before();
}
public function action_index()
{
$this->response->body('Panel admina: index');
}
public function action_public()
{
$this->response->body('Publiczna akcja bez autoryzacji');
}
}
Bonus: Kolejne middleware
Możesz tworzyć kolejne middleware w katalogu classes/Middleware/
, np.:
Middleware_Log
Middleware_Cors
Middleware_Throttle
i rejestrować je podobnie:
$this->middleware('log');
$this->middleware('auth', ['except' => ['login']]);
Zalety tego podejścia
- Zachowujesz znajomy sposób z Laravela.
- Middleware są rejestrowane na poziomie kontrolera (więc bardziej granularnie niż w
bootstrap.php
). - Obsługa
only
iexcept
jak w Laravelu. - Bez zmian w rdzeniu frameworka.
Podsumowanie
Middleware to kod, który wykonuje się przed lub po kontrolerze, aby wykonać operacje na żądaniu lub odpowiedzi. Ułatwia oddzielenie logiki technicznej (np. auth, logi, rate-limit) od logiki biznesowej.