· Admin

Slim PHP: Mikro Framework ile Hızlı ve Esnek Web Uygulamaları

Slim PHP: Mikro Framework ile Hızlı ve Esnek Web Uygulamaları

Laravel her projede dogru secim degil. Bazen daha hafif, daha hizli, daha az soyutlama katmani olan bir yaklasim gerekiyor. Slim PHP tam bu noktada devreye giriyor. Sektorel hazir sitelerden API backend'lerine kadar bircok projemde Slim PHP kullandim. Bu yazida Slim 4'un temellerini, projelerimden gercek orneklerle anlatacagim.

Slim PHP Nedir?

Slim PHP, PSR-7 ve PSR-15 standartlarina uygun bir mikro framework. Laravel'in 80+ service provider'i, Eloquent ORM'i, Blade template engine'i varken Slim size sadece su ucu verir: routing, middleware ve dependency injection. Geri kalani siz secersiniz.

Bu minimalizm dezavantaj degil, avantaj. Bir insaat firmasi sitesi icin Laravel kurmak, sinek oldurmeye bazooka ile gitmek gibi. Slim ile ayni siteyi yarim saatte ayaga kaldirabilirsiniz.

Kurulum ve Temel Yapi

composer require slim/slim slim/psr7 slim/twig-view php-di/php-di vlucas/phpdotenv

Proje yapisi:

project/
├── config/
│   ├── dependencies.php    # DI container tanimlari
│   └── routes.php          # Route tanimlari
├── src/
│   ├── Controllers/
│   ├── Middleware/
│   └── Services/
├── templates/              # Twig template'leri
│   ├── layouts/
│   ├── pages/
│   └── admin/
├── public/
│   └── index.php           # Entry point
├── .env
└── composer.json

Entry Point

// public/index.php
<?php
require __DIR__ . '/../vendor/autoload.php';

use DI\ContainerBuilder;
use Slim\Factory\AppFactory;

// .env yukle
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

// DI Container
$containerBuilder = new ContainerBuilder();
$dependencies = require __DIR__ . '/../config/dependencies.php';
$dependencies($containerBuilder);
$container = $containerBuilder->build();

// Slim App
AppFactory::setContainer($container);
$app = AppFactory::create();

// Middleware
$app->addRoutingMiddleware();
$app->addErrorMiddleware(
    $_ENV['APP_ENV'] === 'development',
    true,
    true
);

// Twig middleware
$app->add(\Slim\Views\TwigMiddleware::createFromContainer($app));

// Route'lari yukle
$routes = require __DIR__ . '/../config/routes.php';
$routes($app);

$app->run();

40 satir kod ile calisan bir web uygulamasi. Laravel'in bootstrap sureci 200+ satir ve onlarca service provider yukler. Slim sade ve hizli.

Dependency Injection: PHP-DI

Slim 4, container secimini size birakir. PHP-DI en populer seceneklerden biri:

// config/dependencies.php
use DI\ContainerBuilder;
use Slim\Views\Twig;
use Psr\Container\ContainerInterface;

return function (ContainerBuilder $containerBuilder) {
    $containerBuilder->addDefinitions([

        // Twig Template Engine
        Twig::class => function (ContainerInterface $c) {
            $twig = Twig::create(__DIR__ . '/../templates', [
                'cache' => $_ENV['APP_ENV'] === 'production'
                    ? __DIR__ . '/../cache/twig'
                    : false,
                'debug' => $_ENV['APP_ENV'] === 'development',
            ]);

            // Global degiskenler
            $twig->getEnvironment()->addGlobal('app', [
                'name' => $_ENV['APP_NAME'] ?? 'Site Adi',
                'url'  => $_ENV['APP_URL'] ?? 'http://localhost',
            ]);

            $twig->getEnvironment()->addGlobal('company', [
                'name'    => $_ENV['COMPANY_NAME'] ?? '',
                'phone'   => $_ENV['COMPANY_PHONE'] ?? '',
                'email'   => $_ENV['COMPANY_EMAIL'] ?? '',
                'address' => $_ENV['COMPANY_ADDRESS'] ?? '',
            ]);

            return $twig;
        },

        // PDO Database
        PDO::class => function () {
            $dsn = sprintf(
                'mysql:host=%s;dbname=%s;charset=utf8mb4',
                $_ENV['DB_HOST'] ?? 'localhost',
                $_ENV['DB_NAME'] ?? 'app_db'
            );

            return new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASS'], [
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES   => false,
            ]);
        },

        // Cloud Storage Service
        'App\Services\CloudStorageService' => function () {
            $provider = $_ENV['STORAGE_PROVIDER'] ?? 'local';

            $config = ['provider' => $provider];

            if ($provider === 'yandex') {
                $config += [
                    'username' => $_ENV['YANDEX_DISK_USERNAME'],
                    'password' => $_ENV['YANDEX_DISK_PASSWORD'],
                    'folder'   => $_ENV['YANDEX_DISK_FOLDER'],
                ];
            } elseif ($provider === 's3') {
                $config += [
                    'access_key' => $_ENV['AWS_ACCESS_KEY_ID'],
                    'secret_key' => $_ENV['AWS_SECRET_ACCESS_KEY'],
                    'region'     => $_ENV['AWS_DEFAULT_REGION'],
                    'bucket'     => $_ENV['AWS_BUCKET'],
                ];
            }

            return new App\Services\CloudStorageService($config);
        },
    ]);
};

Laravel'deki config/app.php + AppServiceProvider + auto-discovery yerine tek dosyada tum dependency'ler tanimli. Acik ve okunakli.

Route Tanimlari

// config/routes.php
use Slim\App;
use App\Controllers\HomeController;
use App\Controllers\ProjectController;
use App\Controllers\ContactController;
use App\Controllers\AdminController;
use App\Controllers\BlogController;

return function (App $app) {

    // Frontend sayfalari
    $app->get('/', [HomeController::class, 'index']);
    $app->get('/hizmetler', [ServiceController::class, 'index']);
    $app->get('/hizmetler/{slug}', [ServiceController::class, 'detail']);
    $app->get('/projeler', [ProjectController::class, 'index']);
    $app->get('/projeler/{slug}', [ProjectController::class, 'detail']);
    $app->get('/hakkimizda', [HomeController::class, 'about']);

    // Iletisim (GET + POST)
    $app->get('/iletisim', [ContactController::class, 'index']);
    $app->post('/iletisim', [ContactController::class, 'submit']);

    // Blog
    $app->get('/blog', [BlogController::class, 'index']);
    $app->get('/blog/{slug}', [BlogController::class, 'detail']);

    // Admin panel (route group)
    $app->group('/admin', function ($group) {
        $group->get('', [AdminController::class, 'dashboard']);
        $group->get('/projeler', [AdminController::class, 'projects']);
        $group->post('/projeler/ekle', [AdminController::class, 'addProject']);
        $group->delete('/projeler/{id}', [AdminController::class, 'deleteProject']);
        $group->post('/upload-media', [AdminController::class, 'uploadMedia']);
        $group->get('/media-files', [AdminController::class, 'getMediaFiles']);
    });

    // API
    $app->group('/api', function ($group) {
        $group->post('/contact', [ContactController::class, 'apiSubmit']);
        $group->get('/projects', [ProjectController::class, 'apiList']);
    });
};

Laravel'deki web.php ve api.php'nin birlesmis hali. $app->group() ile route gruplama, middleware atama ayni mantik.

Controller Yapisi

Slim controller'lari saf PHP siniflari. Base class yok, trait yok, magic method yok:

// src/Controllers/HomeController.php
namespace App\Controllers;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;

class HomeController
{
    public function __construct(
        private Twig $twig
    ) {}

    public function index(Request $request, Response $response): Response
    {
        return $this->twig->render($response, 'pages/home.twig', [
            'title'    => 'Ana Sayfa',
            'projects' => $this->getRecentProjects(),
            'services' => $this->getServices(),
        ]);
    }

    public function about(Request $request, Response $response): Response
    {
        return $this->twig->render($response, 'pages/about.twig', [
            'title' => 'Hakkimizda',
        ]);
    }

    private function getRecentProjects(): array
    {
        // DB sorgusu veya statik veri
        return [
            ['title' => 'Villa Projesi', 'slug' => 'villa-projesi', 'image' => '/img/villa.jpg'],
            ['title' => 'Apartman', 'slug' => 'apartman-projesi', 'image' => '/img/apartman.jpg'],
        ];
    }
}

JSON API Dondurmek

// src/Controllers/ProjectController.php
public function apiList(Request $request, Response $response): Response
{
    $projects = $this->getAllProjects();

    $response->getBody()->write(json_encode([
        'success' => true,
        'data'    => $projects,
        'count'   => count($projects),
    ]));

    return $response
        ->withHeader('Content-Type', 'application/json')
        ->withStatus(200);
}

Laravel'deki return response()->json() yerine PSR-7 standardi. Daha verbose ama standart uyumlu -- bu response'u herhangi bir PSR-7 uyumlu framework'e tasiyabilirsiniz.

Twig Template Engine

Slim + Twig kombinasyonu, Blade'e cok benzer ama daha strict:

{# templates/layouts/base.twig #}
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <title>{{ title }} | {{ app.name }}</title>
    <meta name="description" content="{{ description ?? app.name }}">
    {% block styles %}{% endblock %}
</head>
<body>
    {% include 'partials/header.twig' %}

    {% block content %}{% endblock %}

    {% include 'partials/footer.twig' %}

    {% block scripts %}{% endblock %}
</body>
</html>
{# templates/pages/projects.twig #}
{% extends 'layouts/base.twig' %}

{% block content %}
<section class="projects">
    <h1>Projelerimiz</h1>

    <div class="grid">
        {% for project in projects %}
            <div class="card">
                <img src="{{ project.image }}" alt="{{ project.title }}">
                <h3>{{ project.title }}</h3>
                <p>{{ project.description|truncate(120) }}</p>
                <a href="/projeler/{{ project.slug }}">Detay</a>
            </div>
        {% else %}
            <p>Henuz proje eklenmemis.</p>
        {% endfor %}
    </div>
</section>
{% endblock %}

Blade vs Twig farklari:

Blade Twig
@extends('layout') {% extends 'layout.twig' %}
@section('content') {% block content %}
@include('partial') {% include 'partial.twig' %}
{{ $variable }} {{ variable }}
@foreach($items as $item) {% for item in items %}
@if($condition) {% if condition %}

Syntax farklari disinda mantik ayni. Twig'in avantaji: sandbox modu ile guvenli template rendering ve auto-escaping varsayilan olarak acik.

Middleware

Slim'de middleware PSR-15 standardi:

// src/Middleware/AuthMiddleware.php
namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Response;

class AuthMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $session = $_SESSION ?? [];

        if (empty($session['admin_logged_in'])) {
            $response = new Response();
            return $response
                ->withHeader('Location', '/admin/login')
                ->withStatus(302);
        }

        return $handler->handle($request);
    }
}

Route grubuna middleware eklemek:

$app->group('/admin', function ($group) {
    // Admin route'lari...
})->add(new AuthMiddleware());

Multi-Provider Cloud Storage

Projelerimde Slim ile kurdu gum en ilginc yapilardan biri multi-provider storage servisi. Ayni interface ile Yandex Disk, S3, Cloudflare R2 ve lokal depolama arasinda gecis yapabiliyorsunuz:

// src/Services/CloudStorageService.php
namespace App\Services;

class CloudStorageService
{
    private string $provider;
    private array $config;

    public function __construct(array $config)
    {
        $this->provider = $config['provider'];
        $this->config = $config;
    }

    public function uploadFile(string $filePath, string $key): ?string
    {
        return match ($this->provider) {
            'yandex' => $this->uploadToYandexDisk($filePath, $key),
            's3'     => $this->uploadToS3($filePath, $key),
            'r2'     => $this->uploadToR2($filePath, $key),
            default  => $this->uploadToLocal($filePath, $key),
        };
    }

    private function uploadToYandexDisk(string $filePath, string $key): ?string
    {
        $webdavUrl = "https://webdav.yandex.com/{$this->config['folder']}/{$key}";

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL            => $webdavUrl,
            CURLOPT_CUSTOMREQUEST  => 'PUT',
            CURLOPT_POSTFIELDS     => file_get_contents($filePath),
            CURLOPT_USERPWD        => "{$this->config['username']}:{$this->config['password']}",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER     => [
                'Content-Type: ' . mime_content_type($filePath),
            ],
        ]);

        curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return ($httpCode >= 200 && $httpCode < 300)
            ? $this->getPublicUrl($key)
            : null;
    }

    private function uploadToS3(string $filePath, string $key): ?string
    {
        $s3 = new \Aws\S3\S3Client([
            'region'      => $this->config['region'],
            'version'     => 'latest',
            'credentials' => [
                'key'    => $this->config['access_key'],
                'secret' => $this->config['secret_key'],
            ],
        ]);

        $result = $s3->putObject([
            'Bucket'      => $this->config['bucket'],
            'Key'         => $key,
            'SourceFile'  => $filePath,
            'ACL'         => 'public-read',
            'CacheControl'=> 'public, max-age=31536000',
        ]);

        return $result['ObjectURL'] ?? null;
    }

    private function uploadToLocal(string $filePath, string $key): ?string
    {
        $destination = $this->config['local_path'] . '/' . $key;
        $dir = dirname($destination);

        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }

        copy($filePath, $destination);
        return '/uploads/' . $key;
    }
}

.env dosyasinda STORAGE_PROVIDER=yandex yazarsaniz Yandex Disk'e, s3 yazarsaniz AWS'ye, local yazarsaniz sunucudaki diske yukler. Ayni interface, farkli backend.

Slim vs Laravel: Ne Zaman Hangisi?

Kriter Slim PHP Laravel
Ogrenme egrisi Dusuk Orta-Yuksek
Kurulum suresi 5 dakika 10 dakika
Bootstrap suresi ~5ms ~30-50ms
Bellek kullanimi ~2-5MB ~20-40MB
ORM Yok (PDO veya Doctrine) Eloquent dahil
Template Twig (ayri paket) Blade dahil
Auth sistemi Kendin yaz Breeze/Fortify/Jetstream
CLI Yok Artisan dahil
Ekosistem Kucuk Devasa

Slim'i sec eger:

  • Sektorel hazir site yapiyorsaniz (insaat, avukat, doktor, restoran)
  • Basit API backend gerekiyorsa
  • Performans kritikse (her milisaniye onemli)
  • Framework'un ne yaptigini tam olarak bilmek istiyorsaniz
  • Proje kucuk-orta olcekli ve tek gelistirici

Laravel'i sec eger:

  • Karmasik is mantigi varsa (e-ticaret, SaaS, CRM)
  • Auth, queue, notification, broadcasting gerekiyorsa
  • Buyuk ekiple calisiyorsaniz
  • Hizli prototipleme onemli (Eloquent + Blade + Artisan)
  • Ekosistem destegi lazimsa (Nova, Horizon, Telescope)

Sektorel Hazir Site Ornegi

Slim PHP ile insaat, avukat, diyetisyen gibi sektorel siteleri hizla uretiyordum. Ortak yapi su sekilde:

Frontend:
  / ................ Ana sayfa
  /hizmetler ....... Hizmet listesi
  /hizmetler/{slug}  Hizmet detay
  /projeler ........ Proje galerisi
  /projeler/{slug} . Proje detay
  /blog ............ Blog listesi
  /blog/{slug} ..... Blog detay
  /hakkimizda ...... Hakkimizda
  /iletisim ........ Iletisim formu

Admin:
  /admin ........... Dashboard
  /admin/projeler .. Proje yonetimi
  /admin/ayarlar ... Site ayarlari
  /admin/analytics . Google Analytics

Her sektor icin template degisir ama yapi ayni kalir. Slim'in hafifliginden dolayi ayni sunucuda 10-15 site calistirabilirsiniz. Laravel ile bu sayi 3-4'e duser.

Sonuc

Slim PHP, "framework sana ne verir" degil "sen framework'tan ne istersin" yaklasimiyla calisir. Her sey elinizde, her karar sizin. Bu ozgurluk hem guc hem sorumluluk getirir. ORM yok, kendin sec. Auth yok, kendin yaz. Ama karsiliginda hafif, hizli ve tamamen kontrol edebileceginiz bir uygulama elde edersiniz.

Laravel ile basladim, Slim ile sektorel hazir siteleri hizla urettim, sonra buyuk projeler icin tekrar Laravel'e dondum. Dogru arac, dogru is icin. Slim PHP toolbox'unuzda olmasi gereken bir arac.