SQL ve Veritabanı Standartları
Veritabanına doğrudan uzanmak Drupal'da neredeyse hiçbir zaman doğru hamle değildir — Database API ve Entity Query API tam olarak kodun buna ihtiyaç duymaması için var, ve bunları tutarlı şekilde kullanmak sorguları güvenli, veritabanı motorları arasında taşınabilir ve okunabilir tutan şeydir.
1. Ham SQL Yerine Database API Kullanın
Elle oluşturulmuş ham SQL string'leri, Drupal'ın query builder'ını ve onunla birlikte gelen korumaları es geçer. Database API'nin select(), insert(), update() ve delete() method'ları bir modülün ihtiyaç duyduğu şeylerin büyük çoğunluğunu karşılar ve MySQL, PostgreSQL ve SQLite arasında aynı şekilde çalışan sorgular üretir.
// ❌ Yanlış: elle oluşturulmuş bir SQL string'i.
$result = db_query("SELECT * FROM users WHERE name = '$name'");
// ✅ Doğru: placeholder'lı query builder.
$result = \Drupal::database()->select('users_field_data', 'u')
->fields('u')
->condition('name', $name)
->execute();2. Placeholder'lar SQL Injection'ı Engeller
Kullanıcı girdisi hiçbir zaman doğrudan bir sorgu string'inin içine eklenmez — bunun yerine bir placeholder değeri olarak geçirilir, ve Database API onu altında çalışan veritabanı motoru her ne ise ona göre doğru şekilde escape etmeyi üstlenir.
// ❌ 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]);3. Entity Query API
Sorgulanan veri ham tablolar değil de content entity'lerse, hem ham SQL'e hem de Database API'ye tercih edilen Entity Query API'dir — storage backend'leri arasında geçerliliğini korur ve elle yazılmış bir sorgunun sessizce atlayacağı access kontrollerine otomatik olarak uyar.
// ❌ Yanlış: entity'nin base tablosuna karşı ham bir sorgu.
$result = \Drupal::database()->select('node_field_data', 'n')
->fields('n', ['nid'])
->condition('status', 1)
->execute();
// ✅ Doğru: Entity Query API.
$nids = \Drupal::entityQuery('node')
->condition('status', 1)
->accessCheck(TRUE)
->execute();4. Süslü Parantezle Tablo Prefix'leme
Ham bir sorgu string'i içindeki bir tablo adı, süslü parantez içine alınır — {table_name} — böylece Drupal site için yapılandırılmış herhangi bir tablo prefix'ini uygulayabilir. Parantezleri atlamak prefix'i olmayan bir sitede sorunsuz çalışır, ama prefix yapılandırılmış bir sitede sessizce bozulur.
// ✅ Doğru: süslü parantezler Drupal'ın sitenin tablo prefix'ini uygulamasına izin verir.
$result = \Drupal::database()->query('SELECT nid FROM {node_field_data} WHERE status = :status', [':status' => 1]);5. Schema Tanımlama
Kendi veritabanı tablosunu oluşturan bir modül, yapısını hook_schema() içinde tanımlar; her alanı, tipini, key'lerini ve index'lerini tarif eder — drush updatedb ve installer'ın tabloyu gerçekten oluşturmak için okuduğu şey budur.
/**
* Implements hook_schema().
*/
function product_import_schema(): array {
$schema['product_import_log'] = [
'fields' => [
'id' => [
'type' => 'serial',
'not null' => TRUE,
],
'product_id' => [
'type' => 'int',
'not null' => TRUE,
],
],
'primary key' => ['id'],
];
return $schema;
}6. Transaction'lar
Birlikte başarılı olması ya da birlikte başarısız olması gereken bir yazma grubu bir transaction içine alınır. Drupal'ın transaction'ları scope tabanlıdır — transaction'ı tutan değişken scope dışına çıktığında commit edilir, o noktadan önce bir exception fırlatılırsa da otomatik olarak geri alınır.
$transaction = \Drupal::database()->startTransaction();
try {
// İlgili birden fazla yazma işlemi burada.
}
catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}