Ana sayfaya dön

CPalius CMF: Bir İçerik Yönetim Sisteminden "Kurşun Geçirmez" Kurumsal Uygulama Framework'üne Evrim

Sürüm: v1.0.0-draft

Yayın Türü: Teknik İnceleme (Whitepaper) & Mimari Yol Haritası (RFC)
Sürüm: v1.0.0-draft
Hedef Kitle: Kıdemli PHP Geliştiricileri, Sistem Mimarları, Açık Kaynak Geliştiricileri ve Yapay Zeka Ajanları
Yazar / Kurucu: Ali Çömez (slaweally)
Teknoloji Yığını: PHP 8.2+, Symfony 7.4 LTS, Doctrine ORM, AssetMapper, Tailwind CSS Standalone Binary

Giriş: Teori, Pratik ve "Tekerleği Yeniden İcat Etme" Sancısı

Modern web ekosisteminde yeni bir projeye başlarken geliştiriciler kendilerini her zaman iki ucu keskin bir bıçağın üzerinde bulurlar. Bir yanda, hazır eklenti ve tema sistemleriyle donatılmış ancak hantal veritabanı şemaları, teknik borç dağları ve güvenlik zaafiyetleriyle boğuşan klasik CMS'ler (WordPress, Drupal) vardır. Diğer yanda ise her şeye sıfırdan başlamayı gerektiren, her projede auth, ACL, dosya yönetimi ve admin paneli gibi temel bileşenleri sıfırdan yazarak zaman kaybettiren saf modern framework'ler (Symfony, Laravel) bulunur.

Sıfırdan kendi MVC yapısını yazmak kulağa romantik gelse de günümüz standartlarında rasyonel değildir. Bir framework sadece yönlendirme (routing) ve kontrolcülerden ibaret değildir; güvenlik (CSRF, XSS, SQLi engelleme), Dependency Injection (DI) Container yönetimi ve HTTP katmanı gibi devasa bir "görünmez buzdağı" barındırır.

İşte CPalius, bu iki dünya arasındaki köprüyü kurmak; geliştiriciyi tekerleği yeniden icat etme sancısından kurtarırken ona dünya standartlarında, optimize, güvenli ve genişletilebilir bir altyapı sunmak amacıyla doğdu. Bu döküman, CPalius'un en temiz skeleton kurulumundan, "çökmeyen" bir işletim sistemi mimarisine; ardından bir CMS'ten "Kurumsal Uygulama Framework'üne" uzanan teknik yolculuğunun en ince ayrıntısına kadar dökümüdür.

1. Bölüm: "Pristine Root" ve Klasör Mimarisi

Standart bir Symfony projesinde üçüncü parti paketler ve tarifler (recipes) yüklendikçe projenin ana dizini (root) dosya ve klasör çöplüğüne döner. Geliştiricinin kendi yazdığı kodlar ile framework'ün sistem dosyaları birbirine karışır. CPalius, projenin ilk gününde bu karmaşaya savaş açtı ve "Pristine Root" (Tertemiz Ana Dizin) politikasını benimsedi.

Ana dizinde sadece neyin nerede olduğunu gösteren 4 temel klasör, .env dosyası ve composer.json kalacak şekilde tüm mimariyi izole ettik.

Klasör Hiyerarşisi

CPalius/ (Ana Dizin)
|
|-- cp-core/       # === KERNEL & SYSTEM SPACE ===
|   |-- bin/          # Konsol komut araci (bin/console)
|   |-- config/       # Core konfigurasyonlari (bundles, packages, routes)
|   |-- migrations/   # Cekirdek veritabani goc dosyalari (Git'e tabi)
|   |-- src/          # Cekirdek PHP siniflari (App\ namespace)
|   `-- var/          # Cache, log ve SQLite dosyalari (yazma alani)
|
|-- cp-includes/   # === DEPENDENCIES SPACE ===
|   `-- vendor/       # Composer paketleri (Symfony ve kutuphaneler)
|
|-- cp-content/    # === USER & DEVELOPER SPACE ===
|   |-- config/       # Config Sync alani (YAML olarak tasinan altyapi)
|   |-- modules/      # Bagimsiz moduller/eklentiler (Modules\ namespace)
|   |-- themes/       # Kullanici arayuz temalari
|   `-- translations/ # Global arayuz cevirileri (tr, en)
|
|-- public/        # === WEB ROOT === (Disariya acik tek klasor)
|   |-- index.php     # Front Controller (Tek giris kapisi)
|   `-- assets/       # Derlenmis/Semlink edilmis frontend varliklari
|
|-- .env           # Ortam yapilandirmalari
`-- composer.json  # CPalius ozel yol eslemeleri

Symfony Flex'in İç Mekanizmalarını Bükmek

Bu yapıyı kurabilmek için Symfony Flex ve Composer'ın yapılandırma yeteneklerini en uç sınırlarına kadar zorladık. composer.json dosyasını Flex'e kendi klasör yapımızı dikte edecek şekilde yapılandırdık: vendor-dir, bin-dir ve extra bloğu altındaki parametreler (config-dir, src-dir, var-dir, public-dir ve runtime project_dir / dotenv_path).

Teknik Not: Sadece config.vendor-dir değiştirmek yetersizdir. Symfony Flex'in içindeki komutlar (cache:clear, assets:install), yolları hesaplarken composer'ın standart konfigürasyonlarından değil, extra bloğu altındaki parametrelerden beslenir. Bu parametreler eklenmediğinde sistem derleme aşamasında çöküyordu. Bu sayede tüm Flex tariflerinin doğrudan cp-core altına kurulmasını garanti altına aldık.

2. Bölüm: "Core Never Dies" (Çökmeyen Çekirdek) Mimarisi

Bir CMF geliştirirken en büyük kabus, kullanıcı alanından (cp-content/modules) gelebilecek kötü yazılmış veya bozuk kodların tüm sistemi kilitlemesidir. PHP'de gerçek bir işletim sistemi seviyesinde "sandbox" olmadığı için, CPalius çok kademeli bir Aktif Savunma ve Karantina Hattı geliştirmiştir.

1. Savunma Hattı: Tavuk-Yumurta Probleminin Çözümü (bundles.php)

Symfony boot edilirken ilk olarak config/bundles.php dosyası okunur. Bu aşamada henüz veritabanı bağlantısı, servis konteyneri veya autoloader tam olarak ayağa kalkmamıştır. Modüllerin aktiflik durumunu doğrudan veritabanından okumaya çalışırsak, sistem daha boot edilemeden kilitlenir.

Çözüm: Bir modül aktif veya pasif edildiğinde CPalius, cp-core/config/active_modules.php adında statik, PHP opcache dostu bir dizi dosyası üretir. bundles.php sadece bu dosyayı okur; çekirdek bundle'lar her zaman yüklüdür ve her modül sınıfı yalnızca class_exists() başarılıysa eklenir.

2. Savunma Hattı: Çalışma Zamanı İzolasyonu (Kernel::boot Override)

Sınıfın fiziksel olarak var olması (class_exists), o modülün kodunun hatasız çalıştığı anlamına gelmez. Modülün kendi boot() metodu içinde fırlatacağı bir TypeError veya RuntimeException tüm sistemi çökertebilir. Bunu engellemek için Kernel::boot() override edilerek modül seviyesindeki tüm boot süreçleri try/catch (\Throwable) zırhıyla kuşatıldı; hatalı modül karantina günlüğüne yazılır, çekirdek çalışmaya devam eder.

3. Savunma Hattı: Compile-Time Kilitlenme Koruması & Dry-Run

Eklentideki bir services.yaml dosyasında geçersiz bir YAML sözdizimi varsa veya yanlış bir DI tanımı yapıldıysa, Symfony container'ı derlenemez. Bu durumda sistem çöker ve terminalden cp:module:deactivate komutunu bile çalıştıramayız.

Çözüm: Modül aktivasyonuna izole bir Dry-Run kontrolü entegre edildi. Modül sınıfı geçici olarak yazılır, symfony/process ile tamamen izole bir alt süreç başlatılır ve sırasıyla cache:clear --no-warmuplint:yamllint:container koşturulur. Sıfır dışı bir exit code'da ana süreç finally bloğunda dosyayı geri getirir, aktivasyonu iptal eder ve modülü kalıcı karantinaya alır. Dosya asla bozuk haliyle kaydedilmez.

4. Savunma Hattı: İzole Route Yükleyicisi

Standart Symfony'de bir modülün routes.yaml dosyasındaki bir syntax hatası, rota yükleme anında tüm sistemi çökertir. SafeModuleRouteLoader her modülün rotalarını izole bir try/catch bloğunda yükler; hatalı modül atlanır, sistemin geri kalanı tamamen ayakta kalır.

3. Bölüm: Hibrit Veri Modeli ve Yüksek Performanslı Flat Index Sistemi

İçerik yönetiminde iki klasik yaklaşım da sorunludur:

  • EAV (Entity-Attribute-Value) Modeli (Drupal): Her yeni alan için yeni bir veritabanı tablosu açılır. 15 özel alanı olan bir sayfayı çekmek için 15 JOIN sorgusu atılır; veritabanı kilitlenir.
  • Postmeta/Serialized Modeli (WordPress): Tüm özel alanlar tek bir meta tablosunda satır satır tutulur. Filtreleme ve sıralama yapmak tam bir performans felaketidir.

CPalius Hibrit Veri Modeli: Sık sorgulanan, filtrelenen ve indekslenen temel alanları (ID, Başlık, Slug, Tür, Durum, Dil, Tarihler) gerçek veritabanı sütunları olarak tutuyoruz. İçeriğin kendisine ait tüm dinamik, esnek ve her projede değişebilecek alanları (Gövde metni, öne çıkan görsel, galeri alanları, SEO verileri) ise tek bir SQL json sütununda (data) saklıyoruz.

SQLite, MySQL ve Postgres Uyumluluğunda İndeksleme Çıkmazı

Dinamik JSON verilerini veritabanı düzeyinde sorgulamak isterseniz, SQLite, MySQL ve Postgres arasında tamamen farklı SQL sözdizimleri kullanmanız gerekir. Ayrıca generated columns üzerinde indeks oluşturmak Doctrine ORM şema araçlarıyla son derece kırılgandır; Doctrine her şema güncellemesinde bu indeksleri silmeye çalışır.

Çözüm: NodeFieldIndex ve Dinamik İndeksleme Motoru

CPalius bu sorunu bir Flat Field Index tablosu ve bir Doctrine Event Listener ile çözer. Bağımsız bir indeks tablosu, sorgulanacak dinamik alanlar için tip-uygun kolonlar (value_string, value_int, value_decimal, value_datetime) taşır; her biri field_name üzerinden bileşik indekslidir.

Geliştiricinin belirlediği QueryableFieldsRegistry yapılandırmasına göre, bir Node kaydedildiğinde veya güncellendiğinde NodeIndexListener otomatik olarak JSON data sütununu parse eder, eski indeksleri temizler ve postPersist / postUpdate anında idempotent şekilde yardımcı tabloya yazar.

Artık hangi veritabanı motoru kullanılırsa kullanılsın, JSON verileri NodeRepository::findByIndexedField() (type, locale, fieldName, value, valueColumn, operator) ile tek bir standart SQL/DQL sorgusuyla ışık hızında sorgulanabilir.

4. Bölüm: Çok Dilli Yapı ve Kompozit Benzersizlik Kısıtları

Çok dilli (i18n) yapı projelere sonradan eklendiğinde tüm veri modelini çökertir. CPalius, çoklu dili ilk günden çekirdeğin hücrelerine işlemiştir.

Kompozit Unique Constraints (Benzersizlik Güvencesi)

Çok dilli bir yapıda, klasik unique: true kısıtlamaları sistemi kilitler. Örneğin, Türkçe /tr/hakkimizda ile İngilizce /en/hakkimizda sayfalarının aynı anda var olabilmesi gerekir. Global tek bir slug unique kısıtlaması bunu engeller.

CPalius Çözümü: Veritabanı şemamızda kompozit benzersizlik kısıtları kullandık:

  • Yönlendirme Benzersizliği: Aynı slug sadece aynı dilde unique olmalıdır: UNIQUE(slug, locale).
  • Çeviri Grubu Benzersizliği: Aynı çeviri grubunda aynı dilden sadece bir adet içerik bulunabilir: UNIQUE(translation_group_id, locale).

Aynı güvence, UNIQUE(translation_group_id, locale) ile dört entity'ye daha (categories, tags, menu_items, forum_sections) taşınmıştır; böylece "bir çeviri grubunda her dilden en fazla bir kayıt" kuralını eşzamanlı isteklerde bile veritabanının kendisi zorlar.

5. Bölüm: "CMS"ten "Uygulama Framework'üne" Geçiş

CPalius sadece bir içerik yönetim sistemi (CMS) değildir; arkasında Oto Galeri, Turizm Acentası, Personel Yönetimi veya CRM/ERP sistemlerinin çalışabileceği esnek bir uygulama platformudur. Bu dönüşümü sağlamak için iki sınıf varlık tanımladık:

ÖzellikContent Entities (Node)Business Records (Resource)
Kavramsal KarşılıkSayfa, Yazı, İlan Vitrini, BlogAraç, Fatura, Rezervasyon, Personel
ÖzellikleriSlug var, çoklu dil var, yayın durumu var, SEO var.Slug yok, dil yok, yayın yok, durum makinesi (workflow) var.
Ortak PaydaAynı Yetenek (Capability) modeli, aynı Twig bileşenleri, aynı CLI yönetimi, aynı Config Sync.

#[CpResource] Devrimi

Geliştiricinin iş süreçlerini kodlamasını saniyeler seviyesine indirmek için özel bir PHP Attribute yapısı tasarladık. Tek bir anotasyon ile veritabanındaki ham bir Doctrine sınıfını platformun tüm güçleriyle birleştiriyoruz:

#[CpResource(
    name: 'vehicle',
    module: 'oto-galeri',
    capabilities: ['create', 'edit', 'delete', 'view'],
    auditable: true,
    multiTenant: true,
    workflow: 'vehicle_lifecycle'
)]
#[ORM\Entity]
class Vehicle
{
    private ?int $id = null;
    private ?string $plate = null;
    private ?string $brand = null;
    private ?int $price = 0; // Kurus bazinda saklanir (Float asla!)
}

Bu tek nitelik (#[CpResource]) sayesinde:

  • Rol ve yetenek matrisine (vehicle.create, vehicle.edit vb.) dinamik yetenekler otomatik kaydolur.
  • Otomatik CRUD formları ve liste ekranları bu tanıma göre dinamik olarak türetilir.
  • SaaS projeleri için çoklu kiracı (multiTenant: true) izolasyonu arka planda otomatik uygulanır.
  • Entity üzerinde yapılan tüm değişiklikler anlık olarak sürüm geçmişine (audit log) kaydedilir.

6. Bölüm: Güvenlik ve Performans Anayasası

Güvenlik ve performans, projenin son aşamasında "üzerine eklenen" birer cila değildir; sistemin en temel yapı taşlarıdır.

1. Yetenek Tabanlı Erişim Kontrolü (CBAC)

Standart Symfony ROLE_ADMIN veya ROLE_USER yaklaşımları hantaldır ve sonradan genişletilemez. CPalius'ta kodun hiçbir yerinde rol ismi kontrol edilmez; her zaman dinamik olarak üretilen yetenekler denyAccessUnlessGranted('system.module.manage') ile kontrol edilir.

  • Roller = Config (YAML): Rol tanımları ve yetenek matrisleri cp-content/config/sync/ altında YAML dosyalarında tutulur ve Git ile taşınır.
  • Kullanıcılar = Content (DB): Gerçek kullanıcı kayıtları veritabanında kalır, asla dışa aktarılmaz.

2. SaaS Veri Sızıntısı Koruması (Automatic Tenant SQLFilter)

Çok kiracılı sistemlerde en büyük güvenlik açığı, yazılımcının bir sorgunun sonuna WHERE tenant_id = ? yazmayı unutmasıdır. CPalius'ta bu ihtimal platform seviyesinde yok edilmiştir: bir Doctrine SQLFilter, multi-tenant işaretli entity'ler için tenant kısıtını her sorguya enjekte eder.

3. N+1 Sorgu Muhafızı (Dev-Mode N+1 Guard)

Veritabanı şişmelerinin ve yavaşlıklarının en yaygın sebebi olan N+1 sorgu hatalarını engellemek için sadece dev ortamında tetiklenen bir Doctrine DBAL middleware'i devrededir. Tek bir HTTP isteğinde aynı tabloya atılan sorgu sayısı belirlenen limiti aşarsa, doğrudan MaxQueriesExceededException fırlatılır. Geliştirici bu hatayı lokalde çözmeden kodu canlıya alamaz.

7. Bölüm: Gelecek Yol Haritası ve Teknik İstişare (RFC)

CPalius'un çekirdek güvenlik, performans ve mimari omurgası tamamlanmıştır. Bu whitepaper'ın ilk taslağından bu yana birçok roadmap maddesi teslim edildi: AACP Safe Mode / Kurtarma Konsolu, izole Hook sistemi, birleşik Cron motoru, REST API Gateway, dördüncü savunma hattı (SafeModuleRouteLoader), anlık görsel pipeline'ı (cp_thumb) ve #[CpResource] audit log.

Sıradaki geliştirme sprintlerinde aşağıdaki sistemleri inşa edeceğiz:

  • Workflow & State Machine: Fatura, araç, rezervasyon gibi iş kayıtlarının geçiş süreçlerini (draft → preparation → sold) YAML tanımlarıyla yöneten ve her geçişi otomatik audit log'a ve bildirim kuyruğuna bağlayan mekanizma.
  • Pimcore Tarzı Bağımsız Asset Sistemi: Medyayı Node'un bir alt tipi yapmaktan kurtarıp, Flysystem (S3, MinIO) ile URL üzerinden anlık görsel türetme sunan modern dosya kütüphanesi.
  • Messenger Async Kuyruk: Gerçek bir transport (Doctrine/Redis) bağlayarak e-posta ve bildirim işlemlerini asenkron kuyruğa taşımak.

Soru ve Görüşleriniz (Topluluk İstişaresi)

Bu mimariyi daha da kusursuzlaştırmak için siz değerli geliştiricilerden şu konularda geri bildirim bekliyoruz:

  • Flat Field Index Modeli: SQLite, MySQL ve Postgres uyumluluğu için tasarladığımız bu model sizce devasa veri hacimlerinde nasıl bir performans gösterir? İndeks tablosunun partition edilmesi gerekir mi?
  • Config Sync Yaklaşımı: Drupal'ın config sync sistemini Symfony'nin TreeBuilder yapısıyla taklit etme fikrimiz hakkındaki düşünceleriniz nelerdir?
  • Zero Node.js Israrı: Geliştirici ve admin panelinde AssetMapper + Standalone Tailwind kullanma kararımız, gelecekte çok karmaşık frontend bileşenleri yazarken bizi kısıtlar mı?

Fikirlerinizi, eleştirilerinizi ve mimari önerilerinizi heyecanla bekliyoruz. CPalius, topluluğun gücüyle en sağlam uygulama framework'üne dönüşecek.