Eğitim Rehberi Başlangıç 6 Aşama

Laravel 13 & Paper ile Sıfırdan Veritabanısız İçerik Yönetimi İnşası

Veritabanı Bağımsızlığı, Git Versiyonlama, In-Memory Caching ve Eloquent API

02 Mart 2026 Backend & Mimari Tamamlandı 15 dk uygulama 3 dk okuma
#Laravel 13 #Paper #Flat-File CMS #Markdown #GitOps #Caching
Zorluk Seviyesi
Başlangıç
Aşama Sayısı
6 Aşama
Tahmini Süre
15 dk uygulama
Format
Adım Adım Rehber
Gerekli Ön Koşullar & Hazırlık:
PHP 8.3+ ve Composer paket yöneticisi Laravel 13 temel proje yapısı ve artisan komutları Markdown ve YAML sözdizimi aşinalığı

Laravel 13 & Paper ile Sıfırdan Veritabanısız İçerik Yönetimi İnşası

Geleneksel web uygulamalarında içerik yönetimi (CMS), teknik dokümantasyon veya blog modülleri genellikle MySQL, PostgreSQL veya SQLite gibi ilişkisel veritabanları üzerinde kurgulanır. Ancak teknik notlar, eğitim rehberleri ve portföy içerikleri söz konusu olduğunda veritabanı bağımlılığı birtakım dezavantajlar doğurur:

  • İçerik değişikliklerinin Git commit geçmişinde (git diff) satır satır izlenememesi,
  • Ortamlar arası (Local, Staging, Production) veritabanı taşıma (migration/dump) maliyetleri,
  • Yapay zeka ajanlarının dosya sistemindeki dosyalara doğrudan erişemeyip karmaşık DB araçlarına ihtiyaç duyması.

Laravel Paper (jacobjoergensen/laravel-paper), bu problemi ortadan kaldırarak Eloquent benzeri bir API ile doğrudan Markdown ve YAML dosyalarını veritabanı tabloları gibi sorgulamamızı sağlar.

Bu rehberde, sıfırdan Laravel 13 üzerinde Paper paketini entegre edecek, modelimizi PHP 8.3+ öznitelikleriyle (Attributes) yapılandıracak ve yüksek performanslı in-memory caching ile çalışan bir Flat-File içerik sistemi kuracağız.


1. Rehber Özeti & Neler Öğreneceğiz?

Bu rehberi tamamladığınızda şu yetkinlikleri kazanmış olacaksınız:

  • Flat-File Mimari Prensipleri: Veritabanı olmadan GitOps tabanlı içerik saklama avantajları.
  • Paper Paketi Entegrasyonu: Composer kurulumu ve temel konfigürasyon.
  • PHP 8.3+ Attributes ile Model Tanımı: #[Driver('markdown')] ve #[ContentPath('...')] kullanımı.
  • Frontmatter & Markdown Ayrıştırma: Başlık, zorluk, etiketler ve adımların dinamik accessor metodları ile modellenmesi.
  • In-Memory Caching & Mtime Doğrulaması: Dosya okuma maliyetlerini sıfıra indiren önbellek optimizasyonu.

2. Ön Koşullar & Gerekli Araçlar

Bileşen Minimum Sürüm Amaç
PHP 8.3+ PHP 8 Attributes ve modern accessor sözdizimi
Laravel 13.x Web çatısı
Composer 2.7+ Bağımlılık yönetimi
Git Güncel İçeriklerin versiyon kontrolü

3. Aşama Aşama Adımlar

Adım 1: Laravel Paper Paketinin Kurulumu ve Yapılandırılması

Terminali açın ve Laravel projenizin kök dizininde jacobjoergensen/laravel-paper paketini Composer ile projenize dahil edin:

composer require jacobjoergensen/laravel-paper

Paket, Laravel'in paket keşif (package discovery) mekanizması sayesinde servis sağlayıcısını otomatik olarak kaydeder. Gerekirse ayarları özelleştirmek için yapılandırma dosyasını yayınlayabilirsiniz:

php artisan vendor:publish --tag=paper-config

Bu komut config/paper.php dosyasını oluşturur. Varsayılan ayarlar çoğu Markdown projesi için hazırdır.


Adım 2: İçerik Depolama Dizin Yapısının ve İzinlerinin Oluşturulması

İçeriklerimizi saklamak için projenin kök dizininde content/learn/ adında bir dizin oluşturuyoruz:

mkdir -p content/learn

Dizinin web sunucusu (veya yerel PHP işlemi) tarafından okunabilir olduğundan emin olun:

chmod -R 755 content/learn

Her içerik bu klasör altında birer .md dosyası olarak saklanacaktır (örneğin content/learn/modern-mcp-servers.md). Dosya adı varsayılan olarak modelin slug veya id değeri kabul edilir.


Adım 3: Paper Modelinin PHP 8.3+ Attribute'ları ile Tanımlanması

Laravel 13'te Paper modeli tanımlarken PHP özniteliklerinden (Attributes) faydalanırız. Eloquent Model sınıfından miras alıp Paper trait'ini dahil ediyoruz.

app/Models/LearnEntry.php modelimizi oluşturalım:

<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use JacobJoergensen\LaravelPaper\Attributes\ContentPath;
use JacobJoergensen\LaravelPaper\Attributes\Driver;
use JacobJoergensen\LaravelPaper\Paper;

#[Driver('markdown')]
#[ContentPath('content/learn')]
class LearnEntry extends Model
{
    use Paper;

    protected $guarded = [];

    /**
     * Markdown metnini HTML çıktısına dönüştürür.
     */
    public function getHtmlBodyAttribute(): string
    {
        return Str::markdown((string) ($this->content ?? ''));
    }

    /**
     * Ortalama okuma süresini dakika bazında hesaplar.
     */
    public function getReadingTimeAttribute(): int
    {
        $content = (string) ($this->content ?? '');
        $words = preg_match_all('/\p{L}+/u', strip_tags($content), $matches) ? count($matches[0]) : str_word_count(strip_tags($content));
        return max(1, (int) ceil($words / 180));
    }

    /**
     * Frontmatter'daki difficulty değerini döner (varsayılan: 'Orta Seviye').
     */
    public function getDifficultyAttribute(): string
    {
        return (string) ($this->attributes['difficulty'] ?? $this->difficulty ?? 'Orta Seviye');
    }

    /**
     * Bootstrap 5 rozet sınıfını döner.
     */
    public function getDifficultyBadgeClassAttribute(): string
    {
        return match ($this->difficulty) {
            'Başlangıç' => 'text-bg-success',
            'Orta Seviye' => 'text-bg-primary',
            'İleri Seviye' => 'text-bg-warning',
            default => 'text-bg-secondary',
        };
    }
}

Özniteliklerin Anlamı:

  • #[Driver('markdown')]: Dosyaların league/commonmark ve spatie/yaml-front-matter ile ayrıştırılacağını belirtir.
  • #[ContentPath('content/learn')]: Dosyaların aranacağı göreceli disk yolunu tanımlar.
  • use Paper;: Modele all(), where(), sortBy(), firstWhere() gibi sorgu yeteneklerini ekler.

Adım 4: YAML Frontmatter ve Markdown İçerik Standartlarının Belirlenmesi

Her dosya --- ayraçları arasında YAML üstverileriyle başlamalıdır.

Örnek bir içerik şablonu:


---

title: "Laravel 13 & Paper ile Sıfırdan Veritabanısız İçerik Yönetimi İnşası"
subtitle: "Veritabanı Bağımsızlığı ve Git Tabanlı CMS"
category: "Backend & Mimari"
status: "completed"
type: "guide"
difficulty: "Başlangıç"
duration: "15 dk uygulama"
prerequisites:
  - "PHP 8.3+"
  - "Laravel 13"
tags:
  - "Laravel 13"
  - "Paper"
icon: "fab fa-laravel"
gradient: "linear-gradient(135deg, #ef4444 0%, #f97316 100%)"
date: "2026-03-02"

---

# Rehber Başlığı
İçerik buraya yazılır...

Model, YAML bloktaki her anahtarı bir model niteliği ($entry->title, $entry->difficulty) olarak erişilebilir kılar. Markdown gövdesi ise $entry->content alanına yerleştirilir.


Adım 5: Controller ile Sorgulama, Sıralama ve Filtreleme Mantığının Kurulması

Controller katmanında Paper modellerini tıpkı Eloquent gibi sorgulayabiliriz.

app/Http/Controllers/LearnController.php dosyasını oluşturalım:

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\LearnEntry;
use Illuminate\Http\Request;
use Illuminate\View\View;

final class LearnController extends Controller
{
    public function index(Request $request): View
    {
        // 1. Tüm içerikleri tarihe göre azalan sırada al
        $entries = LearnEntry::all()->sortByDesc('date');

        // 2. İstatistikleri hesapla
        $stats = [
            'total' => $entries->count(),
            'completed' => $entries->where('status', 'completed')->count(),
            'learning' => $entries->where('status', 'learning')->count(),
            'experimenting' => $entries->where('status', 'experimenting')->count(),
        ];

        // 3. Benzersiz kategorileri listele
        $categories = $entries->pluck('category')->filter()->unique()->values()->all();

        return view('learn.index', compact('entries', 'stats', 'categories'));
    }

    public function show(string $slug): View
    {
        // Dosya adı veya slug eşleşmesi
        $entry = LearnEntry::all()->firstWhere('slug', $slug);

        if (!$entry) {
            abort(404, 'Eğitim içeriği bulunamadı.');
        }

        // Önceki ve sonraki kayıt navigasyonu
        $allEntries = LearnEntry::all()->sortBy('date')->values();
        $currentIndex = $allEntries->search(fn ($item) => ($item->slug ?? $item->id) === $slug);

        $previous = $currentIndex > 0 ? $allEntries->get($currentIndex - 1) : null;
        $next = ($currentIndex !== false && $currentIndex < $allEntries->count() - 1) ? $allEntries->get($currentIndex + 1) : null;

        return view('learn.show', compact('entry', 'previous', 'next'));
    }
}

Adım 6: Production Ortamı İçin In-Memory Dosya Önbellekleme (Caching) Optimizasyonu

Her HTTP isteğinde diskteki onlarca Markdown dosyasını açıp YAML parse etmek disk I/O yükü getirebilir. Laravel Cache katmanını kullanarak içerikleri belleğe alabilir ve dosya değişiklik zamanını (mtime) izleyerek otomatik önbellek yenilemesi yapabiliriz:

// app/Models/LearnEntry.php içine eklenebilir veya Servis katmanında kullanılabilir:
public static function getCachedEntries()
{
    return cache()->remember('paper_learn_entries', now()->addDay(), function () {
        return static::all()->sortByDesc('date');
    });
}

İçerik deploy edildiğinde veya Git push yapıldığında önbelleği tek komutla temizleyebilirsiniz:

php artisan cache:clear

4. Yaygın Hatalar ve Çözümleri (Troubleshooting)

1. YAML Parse Error: "A colon cannot be used in an unquoted mapping value"

  • Neden: Başlık veya alt başlıklarda tırnak işareti olmadan : (iki nokta üst üste) karakteri kullanılması.
  • Çözüm: YAML frontmatter içindeki metin alanlarını her zaman çift tırnak "..." içine alın.

2. Dosya Bulunamadı (ContentPath Error)

  • Neden: #[ContentPath('content/learn')] yolunun yanlış yazılması veya klasörün olmaması.
  • Çözüm: base_path('content/learn') klasörünün mevcut olduğundan ve mkdir -p content/learn çalıştırıldığından emin olun.

3. Tarih Formatı Ayrıştırma Hatası

  • Neden: Frontmatter'daki date alanının Carbon tarafından tanınmayan bir formatta olması.
  • Çözüm: Tarihleri daima ISO-8601 standardında (YYYY-MM-DD, örn: 2026-03-02) yazın.

5. Test, Doğrulama & Canlı Çalıştırma

Modelin doğru çalıştığını php artisan tinker veya tek satırlık test komutuyla doğrulayalım:

php artisan tinker --execute="echo 'Kayıt Sayısı: ' . \App\Models\LearnEntry::all()->count() . PHP_EOL;"

Beklenen Çıktı:

Kayıt Sayısı: 4

Bir kaydın detaylarını incelemek için:

php artisan tinker --execute="
$entry = \App\Models\LearnEntry::all()->first();
dump($entry->title, $entry->difficulty, $entry->steps_count, $entry->reading_time);
"

Tüm testler başarıyla geçtiğinde; veritabanı kurulumuna gerek duymayan, Git ile tam uyumlu ve ışık hızında çalışan flat-file içerik yönetiminiz kullanıma hazırdır!