CodeBlanks Logo Icon CodeBlanks

Meydan okuma - Mini framework

PHP kursu · İleri seviye · Ders 92/92

Middleware, routing ve PDO'yla basit bir mini framework çekirdeği yazarsın.

Mini framework: middleware

Son derste tüm parçaları bir mini framework çekirdeğiyle birleştirirsin. Middleware bir istek işleme katmanıdır; process(array $request, callable $next): string imzasını izler. $next zincirin sonraki adımıdır; çağrıldığında istek bir sonraki middleware'e veya işleyiciye geçer.

AuthMiddleware kullanıcı yoksa yapısal veri hata yanıtı döndürür; varsa $next($request) ile zinciri sürdürür. Router ise addRoute ile rotaları kaydeder, use ile middleware ekler ve dispatch ile eşleşen işleyiciyi çağırır. Middleware zinciri array_reverse ile ters çevrilip her adımda bir closure sarmalanarak kurulur.

Middleware zinciri

Birden çok middleware'i zincirlemek için Router::dispatch bir closure yığını kurar. İşleyici en içtektedir; her middleware onu bir function(array $req) use ($m, $stack) { return $m->process($req, $stack); } closure'ıyla sarar. Böylece istek dıştan içe doğru akar — soğan modeli.

Middleware listesini array_reverse ile ters çevirirsin ki her adımda bir önceki yığın sarmalansın. AuthMiddleware en dışta olduğu için önce kullanıcıyı denetler; geçerse $next($request) ile işleyici çağrılır. Bu düzen, yeni middleware eklemeyi ($router->use(...)) tek satıra indirir.

Uygulama AuthMiddleware ve Router yaz

Başlangıç kodunda tüm önceki parçalar ve interface Middleware hazır. Sen AuthMiddleware ve Router sınıflarını yazacaksın.

AuthMiddleware implements Middleware sınıfı process(array $request, callable $next): string yöntemiyle kullanıcıyı denetler: $request["user"] null ise json_encode(["error" => "yetkisiz"], JSON_UNESCAPED_UNICODE) döndürür; değilse $next($request) döndürür. Router sınıfı özel array $routes ve array $middleware tutsun; addRoute(string $method, string $path, callable $handler): void rota ekler; use(Middleware $m): void middleware ekler; dispatch(array $request): string eşleşen rotayı bulur, bulunamazsa json_encode(["error" => "bulunamadı"], JSON_UNESCAPED_UNICODE) döndürür, bulunursa middleware zincirini array_reverse ile kurup $stack($request) döndürür.

Örnek çözümü göster
string { if (($request["user"] ?? null) === null) { return json_encode(["error" => "yetkisiz"], JSON_UNESCAPED_UNICODE); } return $next($request); } } yaz. class Router { private array $routes = []; private array $middleware = []; public function addRoute(string $method, string $path, callable $handler): void { ... } public function use(Middleware $m): void { ... } public function dispatch(array $request): string { ... } } yaz. dispatch içinde rotayı foreach ile bul; $stack = $handler; foreach (array_reverse($this->middleware) as $m) { $stack = function(array $req) use ($m, $stack) { return $m->process($req, $stack); }; } return $stack($request); Sonra demoyu çalıştır.
Örnek çözüm:
<?php
// Mini framework: AuthMiddleware ve Router yaz

class ProductRepository {
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
        $this->pdo->exec("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)");
        $stmt = $this->pdo->prepare("INSERT INTO products (name, price) VALUES (?, ?)");
        $stmt->execute(["Kale", 5]);
        $stmt->execute(["Defter", 15]);
    }

    public function all(): array {
        $rows = $this->pdo->query("SELECT id, name, price FROM products ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
        return array_map(fn($r) => [
            "id" => (int)$r["id"],
            "name" => $r["name"],
            "price" => (int)$r["price"]
        ], $rows);
    }
}

function handleGet(PDO $pdo): string {
    $repo = new ProductRepository($pdo);
    return json_encode(["products" => $repo->all()], JSON_UNESCAPED_UNICODE);
}

class AuthService {
    private array $users = [];
    private string $csrf = "";

    public function register(string $user, string $pass): void {
        $this->users[$user] = password_hash($pass, PASSWORD_DEFAULT);
    }

    public function login(string $user, string $pass): bool {
        if (!isset($this->users[$user])) return false;
        return password_verify($pass, $this->users[$user]);
    }

    public function csrfToken(): string {
        if ($this->csrf === "") {
            $this->csrf = bin2hex(random_bytes(8));
        }
        return $this->csrf;
    }

    public function applyHeaders(): void {
        header("X-Frame-Options: DENY");
        header("X-Content-Type-Options: nosniff");
    }
}

interface NotificationChannel {
    public function send(string $message): string;
}

class EmailChannel implements NotificationChannel {
    public function send(string $message): string {
        return "E-posta: " . $message;
    }
}

class SmsChannel implements NotificationChannel {
    public function send(string $message): string {
        return "SMS: " . $message;
    }
}

interface NotificationObserver {
    public function onSent(string $log): void;
}

class LogObserver implements NotificationObserver {
    public array $logs = [];
    public function onSent(string $log): void {
        $this->logs[] = $log;
    }
}

class NotificationFactory {
    public static function make(string $type): NotificationChannel {
        return match ($type) {
            "email" => new EmailChannel(),
            "sms" => new SmsChannel(),
            default => throw new InvalidArgumentException("Bilinmeyen kanal"),
        };
    }
}

class NotificationService {
    private array $observers = [];

    public function attach(NotificationObserver $o): void {
        $this->observers[] = $o;
    }

    public function notify(string $type, string $message): string {
        $channel = NotificationFactory::make($type);
        $result = $channel->send($message);
        foreach ($this->observers as $o) {
            $o->onSent($result);
        }
        return $result;
    }
}

class TestCase {
    public int $passed = 0;
    public int $failed = 0;

    public function assertTrue(bool $cond, string $msg = ""): void {
        if ($cond) { $this->passed++; } else { $this->failed++; echo "FAIL: $msg\n"; }
    }

    public function assertEqual($a, $b, string $msg = ""): void {
        $this->assertTrue($a === $b, $msg);
    }
}

class OrderService {
    private array $orders = [];

    public function placeOrder(string $product, int $qty): array {
        $order = ["product" => $product, "qty" => $qty, "total" => $qty * 10];
        $this->orders[] = $order;
        return $order;
    }

    public function count(): int {
        return count($this->orders);
    }
}

class MockLogger {
    public array $calls = [];
    public function log(string $msg): void {
        $this->calls[] = $msg;
    }
}

class OrderServiceTest extends TestCase {
    public function run(): void {
        $this->testPlaceOrder();
        $this->testCount();
    }

    public function testPlaceOrder(): void {
        // Arrange
        $svc = new OrderService();
        // Act
        $order = $svc->placeOrder("Kale", 3);
        // Assert
        $this->assertEqual($order["total"], 30, "toplam hesap");
    }

    public function testCount(): void {
        // Arrange
        $svc = new OrderService();
        $logger = new MockLogger();
        // Act
        $svc->placeOrder("Defter", 1);
        $logger->log("sipariş eklendi");
        // Assert
        $this->assertEqual($svc->count(), 1, "sayı arttı");
        $this->assertEqual(count($logger->calls), 1, "log çağrıldı");
    }
}

interface Middleware {
    public function process(array $request, callable $next): string;
}

class AuthMiddleware implements Middleware {
    public function process(array $request, callable $next): string {
        if (($request["user"] ?? null) === null) {
            return json_encode(["error" => "yetkisiz"], JSON_UNESCAPED_UNICODE);
        }
        return $next($request);
    }
}

class Router {
    private array $routes = [];
    private array $middleware = [];

    public function addRoute(string $method, string $path, callable $handler): void {
        $this->routes[] = ["method" => $method, "path" => $path, "handler" => $handler];
    }

    public function use(Middleware $m): void {
        $this->middleware[] = $m;
    }

    public function dispatch(array $request): string {
        $handler = null;
        foreach ($this->routes as $route) {
            if ($route["method"] === $request["method"] && $route["path"] === $request["path"]) {
                $handler = $route["handler"];
                break;
            }
        }
        if ($handler === null) {
            return json_encode(["error" => "bulunamadı"], JSON_UNESCAPED_UNICODE);
        }
        $stack = $handler;
        foreach (array_reverse($this->middleware) as $m) {
            $stack = function(array $req) use ($m, $stack) {
                return $m->process($req, $stack);
            };
        }
        return $stack($request);
    }
}

// Demo:
$router = new Router();
$router->use(new AuthMiddleware());
$router->addRoute("GET", "/products", function(array $req) {
    return json_encode(["products" => []], JSON_UNESCAPED_UNICODE);
});

$req1 = ["method" => "GET", "path" => "/products", "user" => "ayse"];
echo $router->dispatch($req1) . "\n";

$req2 = ["method" => "GET", "path" => "/products", "user" => null];
echo $router->dispatch($req2) . "\n";

Kendini test et

  1. Middleware işlevi hangi imzaya uyar?

    • function handle(array $req): string
    • function run(): void
    • function process(array $request, callable $next): string
    • function dispatch(string $path): array
    Cevabı göster

    function process(array $request, callable $next): string — Doğru. Middleware process yöntemi isteği alır ve callable olarak $next ile zinciri sürdürür.

  2. Framework kavramlarını eşleştir

    Middleware
    İstek öncesi/sonrası katman
    Router
    Yola göre işleyici bulur
    dispatch
    Eşleşen işleyiciyi çağırır
    callable
    Çağrılabilir geri çağırma
  3. Middleware zincirini sürdür

    return ____;
    Cevabı göster
    return $next($request);

İleri seviye bölümündeki tüm dersler