Her deneyimli geliştirici yeni bir framework’ün cazibesini bilir. Benim için bu 10 yılı aşkın bir süredir Laravel oldu. Ancak kişisel sitemi, klytron.com’u inşa etmeye geldiğinde, içerik yönetimi ile ilgili yaygın bir sorunla karşılaştım: Nasıl bir içerik yönetimi yapmalıyım? Kolay yol bir veritabanı destekli CMS, fakat ben hem son derece hızlı, kolay bakım yapılabilir ve modern bir PHP framework ile derin entegrasyona sahip bir çözüm istiyordum. Monolitik bir yapıdan uzak durmak ve dinamik sunucu tarafı yeteneklerinden ödün vermek istemedim.
Bu makale, Laravel içinde tamamen Markdown tabanlı bir içerik yönetim sistemi kurmanın teknik mimarisini detaylandırıyor. Beklenmedik bir şekilde, içerik için veritabanı yok.
Neden Veritabanından Vazgeçtim?
Neden Veritabanından Vazgeçtim?
Deneyimli bir BT Danışmanı olarak, içerik odaklı uygulamalar için varsayılan refleksim her zaman bir veritabanı şemasını tasarlamaktır. Klytron.com için tam olarak bunu yaptım. Ama sonra durdum ve daha temel bir soruyu sordum: Bu içerik için gerçekten bir veritabanına ihtiyacım var mı?
Kişisel bir portföy ve blog için yazma süreci (yeni yazılar yayınlama) oldukça hafif – belki de haftada birkaç yazı. Ancak okuma süreci çok daha önemlidir. Ziyaretçiler anında bilgiye erişim bekler. Bu tür bir senaryoda, düz dosya sistemi ile akıllı bir önbellekleme stratejisi, genellikle ilişkisel veritabanının algılanan faydalarından daha üstün avantajlar sunar:
- Dağıtımda Sıfır Şema Göçü: Her
git pushbir dağıtımı tetikler ve düz dosya sistemi ile endişelenecekphp artisan migratekomutları yok. İçerik kod tabanının bir parçasıdır,Deployergibi araçlarla sorunsuz bir şekilde yönetilir. - İçerik Varsayılan Olarak Versiyon Kontrolü Altında: Bu büyük bir kazanç. Her içerik düzenlemesi, yazım hatası düzeltmelerinden büyük yeniden yazım süreçlerine kadar bir Git commit’idir. Bu, anında geri alma ve denetim için tam bir diff tarihi sağlar.
- Gelişmiş Güvenlik: İçerik için veritabanının ortadan kaldırılması, bir saldırı vektörünü daha azaltır. Üretim ortamındaki konfigürasyonda veritabanı kimlik bilgileri yoktur, böylece potansiyel istismar yüzeyini azaltır.
- Hızlı Yerel Geliştirme Kurulumu: Depoyu klonlayın,
composer installvenpm installkomutlarını çalıştırın, hazırsınız.php artisan migrate --seedgerekmez, bu da iş birliği yapanların (ya da gelecekteki kendinizin) dahil olması için son derece basit hale gelir. - Eşsiz Barındırma Esnekliği: Site hemen hemen her PHP barındırma platformunda etkin bir şekilde çalışır, yönetilen bir veritabanı eklemesine ihtiyaç duyulmaz. Bu, altyapıyı basitleştirir ve olası maliyetleri azaltır.
Elbette ana değiş tokuş, sorgu esnekliğidir. İçeriğiniz üzerinde karmaşık SQL sorguları çalıştıramazsınız. Ancak dosya sistemi dizinlerine büyük ölçüde eşleşen bir içerik yapısı için (örneğin, blog/, portfolio/) genellikle karmaşık WHERE koşullarına veya JOIN işlemlerine ihtiyaç duymazsınız. İçerik yüklendikten sonra bir Laravel Collection’ı bellekte filtrelemek genellikle yeterlidir.
Temel Mimari: Düz Dosyalar + ContentService + Önbellek
Temel Mimari: Düz Dosyalar + ContentService + Önbellek
Sistem, hem performansı hem de bakım kolaylığını sağlamak için net bir üç katmanlı yapı ile tasarlanmıştır:
resources/content/ ← Gerçek kaynak (Markdown dosyaları)
↓
ContentService ← Parçalar, doğrular, dönüştürür
↓
Laravel Dosya Önbelleği (1s TTL) ← Tüm istekleri işler
Bu mimarinin kalbinde basit Markdown dosyaları vardır. Her içerik girişi bir .md dosyasıdır ve üst kısımda metadata için YAML ön bilgi içerir:
yaml
yaml
title: ‘Yazı Başlığı’
slug: my-post-slug
category: ‘Laravel’
tags:
- laravel
- php
- status: published
- published_at: ‘2026-01-15 09:00:00’
- author: ‘Michael K. Laweh’
- read_time: 8 min read
- —
Bu yapılandırılmış yaklaşım mevcut, savaşta test edilmiş kütüphaneleri kullanmaktadır. spatie/yaml-front-matter metadata’yı verimli bir şekilde ayrıştırırken, league/commonmark (GitHub-Flavoured Markdown uzantısıyla) içerik gövdesini işler. Bu, özel ayrıştırma mantığına ihtiyaç duymadan, fenced code blocklar, tablolar, görev listeleri ve üstü çizili metin gibi özellikleri güçlü bir şekilde destekler.
ContentService — İçerik Dağıtım Motoru
ContentService — İçerik Dağıtım Motoru
ContentService, bu mimarinin belki de en önemli bileşenidir. AppServiceProvider içinde singleton olarak kaydedilir ve tüm içerik yükleme, ayrıştırma ve önbellek mantığı için merkezi bir kapı görevi görür.
php
block(2, function () use ($cacheKey, $path) {
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($path) {
return $this->parse($path);
});
});
}
public function all(string $directory): Collection
{
$cacheKey = 'content.dir.' . str_replace("https://dev.to/", '.', $directory);
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($directory) {
$path = $this->contentPath . "https://dev.to/" . $directory;
return collect(glob($path . '/*.md'))
->map(fn (string $file) => $this->parse(
$directory . "https://dev.to/" . basename($file, '.md')
))
->filter(fn (array $item) => ($item['status'] ?? '') === 'published')
->sortByDesc('published_at')
->values();
});
}
private function parse(string $path): array
{
$fullPath = $this->contentPath . "https://dev.to/" . $path . '.md';
abort_unless(file_exists($fullPath), 404);
$document = YamlFrontMatter::parseFile($fullPath);
return array_merge($document->matter(), [
'content' => $this->converter->convert($document->body())->getContent(),
]);
}
}
Şimdi bazı ana yöntemleri inceleyelim:
get(string $path): Bu yöntem, tek bir içerik parçasını alır. İçerik yolu üzerinden benzersiz bir önbellek anahtarı oluşturur ve ardından önbellek bütünlüğü ve performansı sağlamak için güçlü bir atomik kilit modeli kullanır.all(string $directory): Bu yöntem, belirtilen dizin içindeki tüm içerik öğelerini alır. Tüm.mddosyalarını dolaşır, bunları ayrıştırır, yayımlanmış öğeleri filtreler,published_attarihine göre sıralar ve bir Laravel Collection olarak döner. Bu koleksiyon tabanlı yaklaşım, daha fazla filtreleme ve manipülasyonu son derece sezgisel hale getirir.parse(string $path): Bu özel yardımcı yöntem, Markdown dosyasını okumak, YAML ön bilgilerinin ayrıştırılmasını yapmak ve Markdown gövdesinileague/commonmarkkullanarak HTML’ye dönüştürmekle görevlidir. Ayrıca, 404 hatası fırlatmak içinfile_existskontrolü içerir.
Atomik Kilit Deseni: Önbellek Saldırılarını Önleme
Atomik Kilit Deseni: Önbellek Saldırılarını Önleme
Cache::lock() çağrısı, get() yöntemindeki sadece hoş bir özellik değil; yüksek trafik senaryoları için kesinlikle kritiktir.
php
Cache::lock($cacheKey . ‘.lock’, 5)
->block(2, function () use ($cacheKey, $path) {
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($path) {
return $this->parse($path);
});
});
Bu model olmadan, önbelleğinizin süresi dolduğunu düşünün ve aniden yüzlerce veya binlerce eşzamanlı istek sunucunuza yüklense. Her bir istek aynı Markdown dosyasını diskten okumaya çalışacak ve ardından önbellek kaydını aynı anda yazmaya çalışacaktır. Bu olaya önbellek saldırısı veya gürültü yük sorunu denir; bu, disk I/O’nuzu aşırı yükleyebilir ve önbelleklemenin faydalarını sıfıra indirebilir, hatta sunucunuzun çökmesine yol açabilir.
Cache::lock() ile:
- İlk gelen istek kilidi başarıyla alır.
- Markdown dosyasını okur, ayrıştırır ve önbelleği doldurur.
- Aynı içeriğe erişmeye çalışan tüm sonraki eşzamanlı istekler, kilit açılana kadar (bu örnekte
block(2, ...)parametreyi belirlediğimiz kadar) engellenir. - Kilit açıldığında, bu engellenen istekler bir sıcak önbellek kaydı bulur ve yeniden ayrıştırmaya gitmeden ilerleyebilir.
Bu, yalnızca bir isteğin bir seferde pahalı dosya ayrıştırma ve önbellek yazma işlemini yapmasını sağlayarak kaynak tükenmesini önler ve ani yük artışları altında bile performansı korur.
Routing & Controllerlar: Temiz Bir Sorumluluk Ayrımı
Routing & Controllerlar: Temiz Bir Sorumluluk Ayrımı
Laravel’in routing ve controller katmanları, altta yatan içerik depolama mekanizmasından habersizdir. Bu güçlü mimari tasarımın bir kanıtıdır – ContentService düz dosya uygulamasını tamamen soyutlar.
Route’lar açıklayıcı ve basittir, içerik türlerini özel controller’lara eşler:
php
// routes/web.php
Route::get(‘/blog’, [BlogController::class, ‘index’])->name(‘blog.index’);
Route::get(‘/blog/{slug}’, [BlogController::class, ‘show’])->name(‘blog.show’);
Route::get(‘/blog/category/{category}’, [BlogController::class, ‘category’])->name(‘blog.category’);
Route::get(‘/portfolio’, [ProjectController::class, ‘index’])->name(‘portfolio.index’);
Route::get(‘/portfolio/{slug}’, [ProjectController::class, ‘show’])->name(‘portfolio.show’);
Controllerlar kasıtlı olarak ince tutulur, “şişman model, ince controller” ilkesine (veya bu durumda “şişman servis, ince controller”) uyar. Tüm içerik alma görevlerini doğrudan enjekte edilen ContentServiceInterface‘na yönlendirirler:
php
public function show(string $slug, ContentServiceInterface $contentService): View
{
$post = $contentService->get(“blog/{$slug}”);
$relatedPosts = $contentService->all('blog')
->where('category', $post['category'])
->where('slug', '!=', $slug)
->take(3);
return view('blog.show', compact('post', 'relatedPosts'));
}
Gördüğünüz gibi, içerik almak $contentService->get() veya $contentService->all() çağırmak kadar basittir. Controller, daha sonra gerekli iş mantığını (kategorilere göre ilgili yazıları bulmak gibi) standart Laravel Collection yöntemlerini kullanarak gerçekleştirir ve verileri görünüme iletir.
Keşif & SEO: İçeriğin Ötesinde
Keşif & SEO: İçeriğin Ötesinde
Veritabanından vazgeçmenin RSS akışları veya XML site haritaları gibi özellikleri karmaşıklaştırdığını varsayabilirsiniz. Tam tersine, durum böyle değil. Çünkü ContentService::all() her zaman yayımlanmış tüm içeriklerin sıralı bir Laravel Collection’ını döndürdüğünden, keşif mekanizmaları oluşturmak koleksiyon dönüşümünde son derece kolay hale gelir. Hiçbir ORM, karmaşık sorgu oluşturucu ve N+1 sorgu kaygısı yok.
RSS / Atom / JSON Akışları
RSS / Atom / JSON Akışları
Üç popüler akış formatı sunuyorum: Atom (/feed/posts), RSS 2.0 (/feed/posts/rss) ve JSON Feed 1.1 (/feed/posts/json). FeedController son yazıları alır ve uygun XML veya JSON görünümüne yansıtır:
php
// FeedController::rss() örneği
$posts = $this->contentService->all(‘blog’)->take(20);
return response(
view(‘feeds.rss’, compact(‘posts’))->render(),
200,
[‘Content-Type’ => ‘application/rss+xml; charset=UTF-8’]
);
Otomatik olarak keşif tags‘ları sitedeki <head> kısmına yerleştirerek, modern akış okuyucuları ve tarayıcıların akışları kolayca algılamasına ve abone olmasına olanak tanır.
XML Site Haritası
XML Site Haritası
Benzer şekilde, XML site haritası dinamik olarak yayımlanmış tüm blog yazılarını, portföy öğelerini ve hizmet sayfalarını içerir. Bu, resources/content/ dizini ile her zaman tam bir senkronizasyona sahiptir ve ayrı bir sitemap_entries tablosu ya da manuel güncellemeler gerektirmez.
php
// SitemapController::index() örneği
$posts = $this->contentService->all(‘blog’);
$projects = $this->contentService->all(‘portfolio’);
$services = $this->contentService->all(‘services’);
return response(
view(‘sitemap.index’, compact(‘posts’, ‘projects’, ‘services’))->render(),
200,
[‘Content-Type’ => ‘application/xml’]
);
Gelişmiş SEO: Schema.org & Open Graph
Gelişmiş SEO: Schema.org & Open Graph
Maksimum keşfedilebilirlik ve arama sonuçlarında zengin snippet’ler için, özel bir SeoService her sayfa isteğinde yapılandırılmış veri üretir. Bu hizmet, her sayfa isteğinde türüne özgü JSON-LD şemalarını HTML içine enjekte eder. Örneğin, bir blog yazısı için şu şekilde olabilir:
// Bir blog yazısı için
{
“@context“: “https://schema.org“,
“@type”: “Article”,
“headline”: “Yazı Başlığı”,
“author”: { “@type”: “Person”, “name”: “Michael K. Laweh” },
“datePublished”: “2026-01-15”,
“image”: “https://klytron.com/assets/images/blog/hero.png“
}
Dahası, etkili sosyal medya paylaşımı için, dinamik Open Graph görselleri OgImageController aracılığıyla anlık olarak üretilir. Bu controller, PHP’nin GD kütüphanesini kullanarak marka arka plan, yazı başlığı ve site alan adını içeren özel PNG görsellerini sunucu tarafında render eder – tamamen ücretsiz ve herhangi bir üçüncü taraf görüntü hizmetine veya API anahtarlarına bağımlı olmadan.
Dağıtım: Sıfır-Duraklama Atomik Yayınlar
Dağıtım: Sıfır-Duraklama Atomik Yayınlar
İçerik güncellemeleri ve kod değişiklikleri, standart bir Laravel uygulaması gibi tek bir git push komutu ile dağıtılır. Atomik, sıfır-duraklama yayınlarını yönetmek için Deployer adında harika bir PHP dağıtım aracı kullanıyorum:
bash
dep deploy production
Büyülü kısım Aşama 7: ln -sfn releases/YYYYMMDDHHII current. Bu komut, çekirdek seviyesinde atomik bir symlink değiştirmesi yapar. Bu, web sunucusunun (benim durumumda Nginx) anında eski current sürüm dizininden yeni birine geçmesini sağlar, tek bir bölünmez işlemde. Bu, sunucunun kırık, kısmi veya tutarsız bir yapı gösterme süresi olmadığı anlamına gelir ve gerçekten sıfır-duraklama dağıtımı sağlanır. Kritik olarak, son php artisan cache:clear, herhangi bir eski içerik önbellek kaydını temizler ve ContentService‘nin yeni dağıtılan Markdown dosyalarından içerikleri yeniden okumasını ve yeniden önbelleğe almasını sağlar.
Öğrenilenler ve Gelecek Geliştirmeler
Öğrenilenler ve Gelecek Geliştirmeler
Bu düz dosya CMS son derece etkili olmasına rağmen, hiçbir sistem mükemmel değildir. İşte gelecekte geliştirmeyi veya farklı yapmayı düşündüğüm birkaç alan:
- Yerel Geliştirme için İçerik İzleme: Şu anda, yerel geliştirme ortamımda bir Markdown dosyasını düzenlemek için değişiklikleri görmek için manuel bir
php artisan cache:clearçalıştırmak gerekiyor. Basit bir dosya izleyici (örneğininotifywaitkullanarak veya Vite’in HMR’si ile entegre olarak) otomatik olarak bir önbellek temizleme sinyali gönderebilir, geliştirici deneyimini önemli ölçüde iyileştirebilir. - İçerik Yönetimi için Hafif CLI: Yeni blog yazıları için ön bilgi şablonunu elle kopyalamak sıkıcı olabilir. Basit bir
php artisan content:new blog "Yazı Başlığım"komutu, içerik oluşturmayı kolaylaştırabilir ve doğru yapıda önceden doldurulmuş bir Markdown dosyası oluşturabilir. - Düz Dizin ile Ölçeklenebilir Arama: Mevcut site araması, PHP’de önbelleğe alınan koleksiyonları filtreleyerek çalışıyor ki bu mevcut içerik hacmi için oldukça yeterlidir. Ancak, makale sayısı yüzlerceyi geçtiğinde, önceden oluşturulmuş bir Fuse.js JSON dizini (istemci tarafı aramaları için) veya hafif, kendinden barındırılan bir MeiliSearch örneği (sunucu tarafı aramaları için) çok daha ölçeklenebilir ve verimli bir arama deneyimi sunar.
Sonuç: Ne Zaman Düz Dosyayı Tercih Etmeliyim?
Sonuç: Ne Zaman Düz Dosyayı Tercih Etmeliyim?
Geleneksel bir veritabanı, karmaşık ilişkiler, sık yazım işlemleri veya dinamik, kullanıcı tarafından üretilmiş içeriklerle ilgili çok çeşitli sorunlar için kaçınılmaz bir araçtır. Ancak kişisel bir portföy veya blog gibi içerik yoğun ancak yazım oranı düşük uygulamalar için, veritabanı genellikle gereksiz karmaşıklık ve yük getirebilir.
Burada tarif ettiğim düz dosya CMS mimarisi, dikkate değer bir alternatif sunar:
- İçerik versiyon kontrolü sağlar, Git’in gücünden yararlanır.
- Dağıtımlarda veritabanı göç yükünü tamamen ortadan kaldırır.
- Yerel geliştirme kurulumunu anında yapar.
- Ve, agresif, atomik olarak kilitlenmiş önbellek ile birlikte, çoğu geleneksel veritabanı destekli CMS’ten daha iyi okuma hızları ve kaynak kullanımı sağlar.
ContentService ve bu makalede detaylandırılmış olan ilişkili kalıplar, teorik değil – bugün klytron.com’da çalışan savaşta test edilmiş kodlardır. Bir Laravel geliştiricisi olarak hızlı bir içerik sitesi inşa etmeyi düşünüyorsanız, bu mimari, projelerinizde bir öğleden sonra içinde uyarlayıp uygulamak için oldukça basit.
Atomik kilitleme, koleksiyon tabanlı filtreleme ve tip denetlemeli ön bilgi gibi temel prensipler sağlam ve yaygın olarak uygulanabilir.
Belirli bir katmana – belki de akış oluşturma detayları, tam Schema.org uygulaması veya tam Deployer boru hattı kurulumu – daha derinlemesine dalmayı merak ediyorsanız, ulaşmaktan çekinmeyin.
👉 Tam kod deposu ve bonus güvenlik kontrol listesi ile kapsamlı derin dalışı klytron.com’da okuyun
Kaynak: Orijinal Makale
- Neden Veritabanından Vazgeçtim?
- Temel Mimari: Düz Dosyalar + ContentService + Önbellek
- yaml
- ContentService — İçerik Dağıtım Motoru
- Routing & Controllerlar: Temiz Bir Sorumluluk Ayrımı
- Keşif & SEO: İçeriğin Ötesinde
- Dağıtım: Sıfır-Duraklama Atomik Yayınlar
- Öğrenilenler ve Gelecek Geliştirmeler
- Sonuç: Ne Zaman Düz Dosyayı Tercih Etmeliyim?


