Drupal Kodlama Standartları

Kodlama Standartları Neden Önemli?

Bir Drupal projesinde birkaç geliştirici aynı anda çalışmaya başladığı an, herkesin kendi alışkanlığıyla kod yazdığını fark edersiniz — kimi girintide tab kullanır, kimi dizileri farklı hizalar, kimi satır sonlarını hiç düşünmez. Kodlama standartları tam da burada devreye giriyor: amaç kodun sadece çalışması değil, başka birinin açıp okuduğunda ne olup bittiğini hemen anlayabilmesi.

Ve bu standartlar yalnızca PHP'yi kapsamıyor. Drupal, JavaScript'ten CSS'e, Twig'den YAML'a ve veritabanı sorgularına kadar hemen her katman için kendi kurallarını tanımlamış durumda.

Tek kişilik veya küçük bir projede bu detaylar göz ardı edilebilir gibi görünür. Ama ekip büyüdükçe, yeni biri koda dokunmaya başladıkça durum değişir. Herkes kendi tercihini uyguladığında code review süreci satır satır biçim tartışmasına dönüşür; oysa asıl konuşulması gereken kodun ne yaptığıdır, nasıl göründüğü değil.

php
// ❌ Standart dışı.
if($node->isPublished()){
$title=$node->label();
}

// ✅ Drupal standardına uygun.
if ($node->isPublished()) {
$title = $node->label();
}

İkisi de aynı şeyi yapıyor aslında. Fark, altı ay sonra bu dosyayı açan başka bir geliştiricinin ne kadar hızlı anlayacağında ortaya çıkıyor.

PHP Kodlama Standartları

Drupal'ın en detaylı kural seti PHP tarafında karşımıza çıkıyor — girintiden dizi yazımına kadar hemen her şey tanımlanmış.

En temel kural 2 boşluklu girinti, tab yok. Operatörlerin etrafında boşluk bırakılıyor, diziler kısa syntax'la ([]) yazılıyor, çok satırlı dizilerin sonuna da trailing comma ekleniyor — küçük bir detay gibi görünse de git diff'lerini gereksiz yere şişirmemek için işe yarıyor.

php
// ❌ Yanlış: sondaki virgül yok, öğeler tek satırda.
$options = ['status' => TRUE, 'type' => 'article'];

// ✅ Doğru: her öğe kendi satırında, sonda virgül var.
$options = [
'status' => TRUE,
'type' => 'article',
];

Modern Drupal kodunda parametrelere ve dönüş değerlerine tip belirtmek artık neredeyse standart hale geldi.

php
// ❌ Yanlış: tip belirtilmemiş.
public function getTitle($node) {
return $node->label();
}

// ✅ Doğru: parametre ve dönüş tipleri belirtilmiş.
public function getTitle(NodeInterface $node): string {
return $node->label();
}

Bunu yazınca hem method'un ne beklediği netleşiyor hem de PHPStan gibi araçlar hatayı kod çalışmadan önce yakalayabiliyor.

Dokümantasyon Standartları

Kodun kendisi kadar, neden o şekilde yazıldığı da bir noktadan sonra önem kazanıyor.

Drupal, class'lar, method'lar, interface'ler ve bazı dosyalar için DocBlock kullanımını destekliyor. Ama her satıra yorum eklemek gerekmiyor; gerçekten açıklanmaya değer noktalar var, gerisi kodun kendisinden zaten anlaşılıyor.

php
/**
* Returns active products.
*/
public function getActiveProducts(): array {
// ...
}

Method parametre alıyorsa ya da exception fırlatabiliyorsa @param, @return, @throws etiketleri eklenir. Burada kodu cümleye çevirmek değil, okuyan kişiye bir kısayol sunmak hedefleniyor.

Nesne Yönelimli Kod ve Mimari Standartlar

Modern Drupal, büyük ölçüde nesne yönelimli bir mimari üzerine kurulu.

Controller, plugin, event subscriber ya da service class'ları yazarken ihtiyaç duyulan servisleri dependency injection üzerinden almak bekleniyor.

Diyelim bir import servisimiz var; hem bir API client'a hem de logger'a ihtiyaç duyuyor.

php
// ❌ Yanlış: statik servis çağrısı bir method'un içine gömülmüş.
public function importAll() {
$logger = \Drupal::service('logger.factory')->get('product_import');
$logger->error('İçe aktarma başarısız oldu.');
}

// ✅ Doğru: servisler constructor üzerinden enjekte edilir.
public function __construct(LoggerChannelFactoryInterface $logger_factory) {
$this->logger = $logger_factory->get('product_import');
}

public function importAll() {
$this->logger->error('İçe aktarma başarısız oldu.');
}

Küçük bir fark gibi görünse de sonucu büyük: class'ın hangi servislere bağımlı olduğu ilk bakışta görülüyor, test yazarken de bu servisleri mock'lamak çok daha kolay oluyor — iki bağımlılığı da böyle constructor'dan almak servisi aynı zamanda daha okunur hale getiriyor.

Adlandırma Kuralları

İsimlendirme kuralları aslında basit bir amaca hizmet ediyor: bir isme baktığınızda onun ne olduğunu tahmin edebilmeniz.

Modül machine name'leri küçük harf ve alt çizgiyle yazılıyor:

text
product_import
custom_search
weather_api

Class isimleri için:

text
ProductImporter
ProductImporterInterface

method isimleri için ise:

text
getProducts()
importProducts()
updateProduct()

gibi bir kalıp izleniyor. Procedural fonksiyonlarda da modülün adını prefix olarak kullanmak yaygın:

php
function product_import_cron() {
}

Bu küçük alışkanlık, özellikle onlarca modülün bir arada çalıştığı büyük projelerde isim çakışmalarını büyük ölçüde önlüyor.

YAML Dosya Standartları

Bir Drupal projesinde çok geçmeden YAML dosyalarıyla karşılaşmaya başlıyoruz.

En çok karşılaşacağınız dosyalar .info.yml, .services.yml, .routing.yml ve .libraries.yml. Ve YAML'da girintileme şakaya gelmiyor — bir boşluk bile fark yaratabiliyor.

yaml
# ❌ Yanlış: girinti yok, YAML iç içe yapıyı ayrıştıramaz.
product_import.settings:
path: '/admin/config/product-import'
defaults:
_title: 'Product Import'

# ✅ Doğru: 2 boşluklu girinti iç içe yapıyı gösterir.
product_import.settings:
  path: '/admin/config/product-import'
  defaults:
    _title: 'Product Import'

Tab karakteri ya da yanlış girinti kullanırsanız Drupal dosyayı hiç okuyamayabilir, hata da genelde pek açıklayıcı olmaz. Configuration üreten modüllerde ayrıca config schema tanımlamayı da unutmamak gerekiyor.

JavaScript Kodlama Standartları

JavaScript tarafında işlerin döndüğü yer Drupal.behaviors sistemi.

Sebebi basit: Drupal sayfaları her zaman baştan yüklenmiyor. Bir AJAX isteğinden sonra sayfanın sadece bir parçası yeniden oluşturulabiliyor, geri kalanı olduğu gibi kalıyor.

javascript
// ❌ Yanlış: her AJAX kısmi yeniden oluşturmada tekrar çalışır.
Drupal.behaviors.example = {
attach(context) {
console.log('Bu her context işlendiğinde çalışır.');
},
};

// ✅ Doğru: once() aynı elementte iki kez çalışmasını engeller.
Drupal.behaviors.example = {
attach(context) {
once('example', '.my-element', context).forEach((element) => {
// JavaScript işlemleri.
});
},
};

Aynı elementin üzerinde kod iki kez çalışmasın diye once() kullanılıyor. Backend'den JavaScript'e veri taşımak gerektiğinde de global değişken tanımlamak yerine drupalSettings'e yazmak alışılagelmiş yöntem.

Bir API adresini ya da modülün birkaç ayarını JavaScript tarafına geçirmek istediğinizde aklınıza gelmesi gereken ilk yöntem bu.

CSS Kodlama Standartları

Büyük bir Drupal temasında CSS çok çabuk dağınıklaşabiliyor, bu yüzden component tabanlı bir yaklaşım işe yarıyor.

Bir component'in kendisiyle alt elemanları arasındaki ilişkiyi class isminden anlayabilmek, aylar sonra o CSS dosyasına dönüp bakan kişiye zaman kazandırıyor.

css
/* ❌ Yanlış: class isimleri arasında net bir ilişki yok. */
.card {
}

.title {
}

.featured {
}

/* ✅ Doğru: BEM tarzı isimlendirme ilişkiyi gösteriyor. */
.card {
}

.card__title {
}

.card--featured {
}

.card__title yazısını gördüğünüzde onun .card'ın bir parçası olduğunu anlamak için başka bir dosyaya bakmanıza gerek kalmıyor. Bu kuralların otomatik denetimi için de Stylelint kullanılabiliyor.

Twig Standartları

Twig şablonlarının işi aslında oldukça sınırlı: hazırlanmış veriyi kullanıcıya göstermek.

Karmaşık iş mantığını Twig'in içine sıkıştırmak cazip gelebilir ama genelde pişman olunan bir tercih. Bu mantığın preprocess fonksiyonlarında ya da PHP tarafında halledilmesi, hem template'i sade tutuyor hem de o mantığı test edilebilir hale getiriyor.

twig
{% if title %}
<h1>{{ title }}</h1>
{% endif %}

Kullanıcıya gösterilen her metin, Drupal'ın çeviri sistemi üzerinden geçmeli:

twig
{# ❌ Yanlış: sabit metin, hiç çevrilmiyor. #}
Read more

{# ✅ Doğru: çeviri sistemi üzerinden geçiyor. #}
{{ 'Read more'|t }}

Twig zaten otomatik escaping yaptığı için |raw filtresine genelde ihtiyaç yok. Kullanıcıdan ya da bir editörden gelen içerikte bunu gelişigüzel kullanmak, XSS açığına davetiye çıkarmak anlamına gelebilir.

twig
{# ❌ Yanlış: kullanıcı içeriğinde |raw kullanmak XSS'e kapı açar. #}
{{ comment.body|raw }}

{# ✅ Doğru: Twig'in otomatik escaping'i işini yapsın. #}
{{ comment.body }}

SQL ve Veritabanı Standartları

Veritabanına doğrudan erişmek yerine Drupal'ın sunduğu API katmanlarını kullanmak neredeyse her zaman daha güvenli bir yol.

Kullanıcıdan gelen bir veriyi hiçbir zaman doğrudan SQL sorgusunun içine eklememek gerekiyor — bu, SQL injection'a açık kapı bırakmanın en klasik yollarından biri.

Böyle bir şey yazmak yerine:

php
// ❌ Yanlış: kullanıcı girdisi doğrudan SQL'e ekleniyor.
$query = "SELECT * FROM users WHERE name = '$name'";
$result = db_query($query);

// ✅ Doğru: placeholder, escaping'i Database API'ye bırakıyor.
$result = \Drupal::database()->query('SELECT * FROM {users_field_data} WHERE name = :name', [':name' => $name]);

İşte Drupal'ın Database API'si tam olarak bunun için var. Entity'lerle çalışırken de genellikle ham SQL yerine Entity API tercih ediliyor. Diyelim yayınlanmış tüm içerikleri listelemek istiyorsunuz — node_field_data tablosuna elle sorgu yazmak yerine Entity Query kullanmak, kodu Drupal'ın kendi entity mantığına daha yakın tutuyor.

Güvenlikle İlişkili Standartlar

Kodlama standartlarının bir kısmı da doğrudan güvenlikle ilgili — bazı hatalar sadece kod kalitesini değil, sitenin güvenliğini de etkiliyor.

Kullanıcıdan gelen bir veriyi doğrudan HTML'e basmak bunlardan biri. Bir formdan gelen isim alanını düşünün, mesela şöyle bir değer içerebilir:

text
<script>alert('test')</script>

Bunu olduğu gibi sayfaya yazdırırsanız script çalışır — Drupal'ın escape ve XSS filtreleme mekanizmalarının devreye girmesi bunun için var. Çevrilebilir metinlerde placeholder kullanmak da aynı sebepten kritik:

php
// ❌ Yanlış: kullanıcı girdisi doğrudan metne ekleniyor.
$this->t('Welcome ' . $username);

// ✅ Doğru: placeholder değeri Drupal otomatik olarak escape eder.
$this->t('Welcome @username', [
'@username' => $username,
]);

Formları Drupal'ın Form API'siyle kurmak da CSRF koruması gibi güvenlik önlemlerini otomatik olarak beraberinde getiriyor.

Araçlarla Otomatik Kontrol

Bütün bu kuralları her seferinde elle kontrol etmeyi beklemek gerçekçi değil — insan gözü yorulur, standartlar da gözden kaçar.

Drupal projelerinde bu iş için en çok PHP_CodeSniffer ve Coder kullanılıyor.

bash
vendor/bin/phpcs \
--standard=Drupal,DrupalPractice \
web/modules/custom

Bu komut custom modülleri tarayıp standarda uymayan yerleri listeliyor; bir kısmını phpcbf otomatik düzeltebiliyor. Ama phpcs sadece biçimle ilgileniyor — asıl mantık hatalarını (yanlış method çağrısı, uyuşmayan veri tipi gibi) yakalamak için PHPStan ve phpstan-drupal devreye giriyor, hem de kod hiç çalıştırılmadan.

CI/CD ile Standart Denetimi

PHPCS'yi sadece lokal ortamda çalıştırıyorsak kontrolün önemli bir kısmını geliştiricinin inisiyatifine bırakmış oluyoruz.

Bu yüzden aynı kontrolleri CI/CD pipeline'ına da taşımak mantıklı.

yaml
phpcs:
stage: test
script:
- vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom

Her merge request açıldığında bu kontrol otomatik tetiklenir. Biri standarda uymayan bir kod gönderdiğinde pipeline kırmızıya döner ve sorun ana branch'e hiç ulaşmadan fark edilir. Aynı pipeline'a PHPStan'i, testleri ve diğer kalite kontrollerini de eklemek mümkün.

En Sık Karşılaşılan Kodlama Standardı Hataları

Aynı hatalar projeden projeye şaşırtıcı derecede tekrar ediyor:

  • 2 yerine 4 boşluk kullanılması,
  • tab kullanılması,
  • eski array() yapısının devam ettirilmesi,
  • çok satırlı dizilerde trailing comma unutulması,
  • namespace veya class adlarının yanlış oluşturulması,
  • service class'larında dependency injection kullanılmaması,
  • translation gerektiren metinlerin doğrudan yazılması,
  • Twig içerisinde gereksiz |raw kullanılması,
  • JavaScript behavior'larında once() kullanılmaması,
  • deprecated Drupal API'lerinin kullanılmaya devam edilmesi.

Mesela şu satırları eski Drupal projelerinde hâlâ görmek mümkün:

php
// ❌ Eski syntax: array().
$items = array(
'one',
'two',
);

// ✅ Güncel syntax: kısa dizi literali.
$items = [
'one',
'two',
];

Tek başına bakıldığında bu fark önemsiz görünebilir. Ama binlerce satırlık bir kod tabanında yüzlerce kez tekrarlanınca, bakım maliyetine gerçek bir katkısı oluyor.

Sonuç

Bütün bu kurallara ilk bakışta bürokratik bir liste gibi bakmak kolay. Ama pratikte fark ettiğim şu: bir proje büyüdükçe, standartlara uyan kod tabanı ile uymayan kod tabanı arasındaki fark katlanarak açılıyor. Birinde yeni bir geliştirici bir haftada işe koyulabiliyor, diğerinde aylarca "burada neden böyle yapılmış" sorusuyla boğuşuyor.

İyi haber şu ki bunların çoğunu ezberlemenize gerek yok. PHPCS, PHPStan, ESLint ve Stylelint gibi araçları bir kere doğru kurduğunuzda, standartların büyük kısmını sizin yerinize onlar takip ediyor — siz de zamanınızı asıl önemli olan şeye, kodun ne yaptığına ayırabiliyorsunuz.

Resmî kaynak: https://project.pages.drupalcode.org/coding_standards/

Son güncelleme: 17.09.2026 16:35