SQL Yaklaşımı

Nette Database iki yol sunar: SQL sorgularını kendiniz yazabilir (SQL yaklaşımı) veya otomatik olarak oluşturulmalarını sağlayabilirsiniz (bkz. Explorer). SQL yaklaşımı size sorgular üzerinde tam kontrol sağlarken, güvenli bir şekilde oluşturulmalarını da garanti eder.

Veritabanı bağlantısı ve yapılandırmasıyla ilgili ayrıntıları Bağlantı ve Yapılandırma bölümünde bulabilirsiniz.

Temel Sorgulama

Veritabanına sorgu yapmak için query() metodu kullanılır. Bu metot, sorgu sonucunu temsil eden bir ResultSet nesnesi döndürür. Başarısızlık durumunda metot istisna fırlatır. Sorgu sonucunu foreach döngüsüyle gezebilir veya yardımcı fonksiyonlardan birini kullanabiliriz.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}

SQL sorgularına güvenli bir şekilde değer eklemek için parametreli sorgular kullanırız. Nette Database bunları son derece basit hale getirir – SQL sorgusundan sonra virgül ve değeri eklemeniz yeterlidir:

$database->query('SELECT * FROM users WHERE name = ?', $name);

Birden fazla parametre olduğunda iki yazım seçeneğiniz vardır. SQL sorgusunu parametrelerle “serpiştirebilirsiniz”:

$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);

Veya önce tüm SQL sorgusunu yazıp sonra tüm parametreleri ekleyebilirsiniz:

$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);

SQL Injection'dan Korunma

Parametreli sorguları kullanmak neden önemlidir? Çünkü sizi, saldırganın kendi SQL deyimlerini ekleyerek veritabanındaki verilere erişebileceği veya zarar verebileceği SQL injection adlı saldırıdan korurlar.

Asla değişkenleri doğrudan SQL sorgusuna eklemeyin! Sizi SQL injection'dan koruyan parametreli sorguları her zaman kullanın.

// ❌ TEHLİKELİ KOD - SQL injection'a karşı savunmasız
$database->query("SELECT * FROM users WHERE name = '$name'");

// ✅ Güvenli parametreli sorgu
$database->query('SELECT * FROM users WHERE name = ?', $name);

Olası güvenlik riskleri hakkında bilgi edinin.

Sorgulama Teknikleri

WHERE Koşulları

WHERE koşullarını, anahtarların sütun adları ve değerlerin karşılaştırma için veriler olduğu ilişkisel bir dizi olarak yazabilirsiniz. Nette Database, değerin tipine göre en uygun SQL operatörünü otomatik olarak seçer.

$database->query('SELECT * FROM users WHERE', [
	'name' => 'John',
	'active' => true,
]);
// WHERE `name` = 'John' AND `active` = 1

Anahtarda karşılaştırma için operatörü açıkça belirtebilirsiniz:

$database->query('SELECT * FROM users WHERE', [
	'age >' => 25,          // > operatörünü kullanır
	'name LIKE' => '%John%', // LIKE operatörünü kullanır
	'email NOT LIKE' => '%example.com%', // NOT LIKE operatörünü kullanır
]);
// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'

Nette, null değerleri veya diziler gibi özel durumları otomatik olarak işler.

$database->query('SELECT * FROM products WHERE', [
	'name' => 'Laptop',         // = operatörünü kullanır
	'category_id' => [1, 2, 3], // IN kullanır
	'description' => null,      // IS NULL kullanır
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL

Negatif koşullar için NOT operatörünü kullanın:

$database->query('SELECT * FROM products WHERE', [
	'name NOT' => 'Laptop',         // <> operatörünü kullanır
	'category_id NOT' => [1, 2, 3], // NOT IN kullanır
	'description NOT' => null,      // IS NOT NULL kullanır
	'id' => [],                     // atlanır
]);
// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL

Koşulları birleştirmek için AND operatörü kullanılır. Bu, ?or yer tutucu sembolü kullanılarak değiştirilebilir.

ORDER BY Kuralları

ORDER BY sıralaması bir dizi kullanılarak yazılabilir. Anahtarlarda sütunları belirtiriz ve değer, artan sırada sıralanıp sıralanmayacağını belirleyen bir boolean olur:

$database->query('SELECT id FROM author ORDER BY', [
	'id' => true, // artan sırada
	'name' => false, // azalan sırada
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC

Veri Ekleme (INSERT)

Kayıt eklemek için INSERT SQL deyimi kullanılır.

$values = [
	'name' => 'John Doe',
	'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();

getInsertId() metodu, son eklenen satırın ID'sini döndürür. Bazı veritabanlarında (örn. PostgreSQL), ID'nin oluşturulacağı sıra adını $database->getInsertId($sequenceId) kullanarak parametre olarak belirtmek gerekir.

Parametre olarak dosyalar, DateTime nesneleri veya enum tipleri gibi speciální hodnoty de iletebiliriz.

Aynı anda birden fazla kayıt ekleme:

$database->query('INSERT INTO users ?', [
	['name' => 'User 1', 'email' => 'user1@mail.com'],
	['name' => 'User 2', 'email' => 'user2@mail.com'],
]);

Çoklu INSERT çok daha hızlıdır, çünkü birçok tekil sorgu yerine tek bir veritabanı sorgusu yürütülür.

Güvenlik uyarısı: Asla $values olarak doğrulanmamış verileri kullanmayın. Olası riskler hakkında bilgi edinin.

Veri Güncelleme (UPDATE)

Kayıtları güncellemek için UPDATE SQL deyimi kullanılır.

// Tek bir kaydı güncelleme
$values = [
	'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);

Etkilenen satır sayısı $result->getRowCount() tarafından döndürülür.

UPDATE için += ve -= operatörlerini kullanabiliriz:

$database->query('UPDATE users SET ? WHERE id = ?', [
	'login_count+=' => 1, // login_count'u artırma
], 1);

Zaten varsa bir kaydı ekleme veya düzenleme örneği. ON DUPLICATE KEY UPDATE tekniğini kullanıyoruz:

$values = [
	'name' => $name,
	'year' => $year,
];
$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
	$values + ['id' => $id],
	$values,
);
// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
//   ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978

Nette Database'in, parametreyi dizi ile SQL deyiminin hangi bağlamına eklediğimizi tanıdığına ve buna göre SQL kodunu oluşturduğuna dikkat edin. Yani ilk diziden (id, name, year) VALUES (123, 'Jim', 1978) oluştururken, ikincisini name = 'Jim', year = 1978 şekline dönüştürdü. Buna SQL Oluşturma İpuçları bölümünde daha ayrıntılı olarak değiniyoruz.

Veri Silme (DELETE)

Kayıtları silmek için DELETE SQL deyimi kullanılır. Silinen satır sayısını alma örneği:

$count = $database->query('DELETE FROM users WHERE id = ?', 1)
	->getRowCount();

SQL Oluşturma İpuçları

İpucu, parametre değerinin SQL ifadesine nasıl çevrileceğini belirten SQL sorgusundaki özel bir yer tutucu semboldür:

İpucu Açıklama Otomatik olarak kullanılır
?name tablo veya sütun adı eklemek için kullanılır
?values (key, ...) VALUES (value, ...) üretir INSERT ... ?, REPLACE ... ?
?set key = value, ... atamasını üretir SET ?, KEY UPDATE ?
?and dizideki koşulları AND operatörüyle birleştirir WHERE ?, HAVING ?
?or dizideki koşulları OR operatörüyle birleştirir
?order ORDER BY yan tümcesini üretir ORDER BY ?, GROUP BY ?

Tablo ve sütun adlarını sorguya dinamik olarak eklemek için ?name yer tutucu sembolü kullanılır. Nette Database, tanımlayıcıların ilgili veritabanının kurallarına göre (örn. MySQL'de geri tırnak içine alma) doğru bir şekilde işlenmesini sağlar.

$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `name` FROM `users` WHERE id = 1 (MySQL'de)

Uyarı: ?name sembolünü yalnızca doğrulanmış girdilerden gelen tablo ve sütun adları için kullanın, aksi takdirde güvenlik riskine maruz kalırsınız.

Diğer ipuçlarını genellikle belirtmeye gerek yoktur, çünkü Nette SQL sorgusunu oluştururken akıllı otomatik algılama kullanır (tablonun üçüncü sütununa bakın). Ancak, örneğin koşulları AND yerine OR ile birleştirmek istediğiniz bir durumda kullanabilirsiniz:

$database->query('SELECT * FROM users WHERE ?or', [
	'name' => 'John',
	'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'

Özel Değerler

Normal skaler tiplerin (string, int, bool) yanı sıra, parametre olarak özel değerler de iletebilirsiniz:

  • dosyalar: fopen('image.gif', 'r') dosyanın ikili içeriğini ekler
  • tarih ve saat: DateTime nesneleri veritabanı formatına dönüştürülür
  • enum tipleri: enum örnekleri değerlerine dönüştürülür
  • SQL literalleri: Connection::literal('NOW()') ile oluşturulanlar doğrudan sorguya eklenir
$database->query('INSERT INTO articles ?', [
	'title' => 'My Article',
	'published_at' => new DateTime,
	'content' => fopen('image.png', 'r'),
	'state' => Status::Draft,
]);

datetime veri tipi için yerel desteği olmayan veritabanlarında (SQLite ve Oracle gibi), DateTime veritabanı yapılandırmasındaki formatDateTime öğesiyle belirtilen değere dönüştürülür (varsayılan değer U – unix zaman damgasıdır).

SQL Literalleri

Bazı durumlarda, değer olarak doğrudan SQL kodu belirtmeniz gerekir, ancak bu kodun bir karakter dizisi olarak anlaşılmaması ve kaçış yapılmaması gerekir. Bunun için Nette\Database\SqlLiteral sınıfının nesneleri kullanılır. Bunları Connection::literal() metodu oluşturur.

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())

Veya alternatif olarak:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())

SQL literalleri parametreler içerebilir:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017)

Bu sayede ilginç kombinasyonlar oluşturabiliriz:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('?or', [
		'active' => true,
		'role' => $role,
	]),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')

Veri Alma

SELECT Sorguları için Kısayollar

Veri alımını basitleştirmek için Connection, query() çağrısını ardından fetch*() ile birleştiren birkaç kısayol sunar. Bu metotlar, query() ile aynı parametreleri kabul eder, yani SQL sorgusu ve isteğe bağlı parametreler. fetch*() metotlarının tam açıklaması aşağıda bulunabilir.

fetch($sql, ...$params): ?Row Sorguyu yürütür ve ilk satırı Row nesnesi olarak döndürür
fetchAll($sql, ...$params): array Sorguyu yürütür ve tüm satırları Row nesneleri dizisi olarak döndürür
fetchPairs($sql, ...$params): array Sorguyu yürütür ve ilk sütunun anahtar, ikinci sütunun değer olduğu ilişkisel bir dizi döndürür
fetchField($sql, ...$params): mixed Sorguyu yürütür ve ilk satırdaki ilk hücrenin değerini döndürür
fetchList($sql, ...$params): ?array Sorguyu yürütür ve ilk satırı indeksli bir dizi olarak döndürür

Örnek:

// fetchField() - ilk hücrenin değerini döndürür
$count = $database->query('SELECT COUNT(*) FROM articles')
	->fetchField();

foreach – Satırlar Üzerinde Yineleme

Sorgu yürütüldükten sonra, sonuçları birkaç şekilde gezmenizi sağlayan bir ResultSet nesnesi döndürülür. Bir sorguyu yürütmenin ve satırları almanın en kolay yolu foreach döngüsünde yinelemektir. Bu yöntem bellek açısından en verimli olanıdır, çünkü verileri kademeli olarak döndürür ve hepsini aynı anda bellekte saklamaz.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
	// ...
}

ResultSet yalnızca bir kez yinelenebilir. Tekrar tekrar yinelemeniz gerekiyorsa, önce verileri bir diziye yüklemeniz gerekir, örneğin fetchAll() metodunu kullanarak.

fetch(): ?Row

Satırı Row nesnesi olarak döndürür. Başka satır yoksa null döndürür. Dahili göstericiyi bir sonraki satıra taşır.

$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // ilk satırı yükler
if ($row) {
	echo $row->name;
}

fetchAll(): array

ResultSet'ten kalan tüm satırları Row nesneleri dizisi olarak döndürür.

$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // tüm satırları yükler
foreach ($rows as $row) {
	echo $row->name;
}

fetchPairs (string|int|null $key = null, string|int|null $value = null)array

Sonuçları ilişkisel bir dizi olarak döndürür. İlk argüman, dizide anahtar olarak kullanılacak sütun adını belirtir, ikinci argüman değer olarak kullanılacak sütun adını belirtir:

$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]

Yalnızca ilk parametreyi belirtirsek, değer tüm satır, yani Row nesnesi olacaktır:

$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]

Yinelenen anahtarlar durumunda, son satırdaki değer kullanılır. Anahtar olarak null kullanıldığında, dizi sıfırdan başlayarak sayısal olarak indekslenir (o zaman çakışma olmaz):

$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]

fetchPairs (Closure $callback)array

Alternatif olarak, parametre olarak her satır için ya değeri ya da anahtar-değer çiftini döndürecek bir geri arama (callback) belirtebilirsiniz.

$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]

// Geri arama ayrıca anahtar & değer çifti içeren bir dizi döndürebilir:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]

fetchField(): mixed

Geçerli satırdaki ilk hücrenin değerini döndürür. Başka satır yoksa null döndürür. Dahili göstericiyi bir sonraki satıra taşır.

$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // ilk satırdan adı yükler

fetchList(): ?array

Satırı indeksli bir dizi olarak döndürür. Başka satır yoksa null döndürür. Dahili göstericiyi bir sonraki satıra taşır.

$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']

getRowCount(): ?int

Son UPDATE veya DELETE sorgusundan etkilenen satır sayısını döndürür. SELECT için bu, döndürülen satır sayısıdır, ancak bu bilinmeyebilir – bu durumda metot null döndürür.

getColumnCount(): ?int

ResultSet'teki sütun sayısını döndürür.

Sorgu Bilgileri

Hata ayıklama amacıyla, son yürütülen sorgu hakkında bilgi alabiliriz:

echo $database->getLastQueryString();   // SQL sorgusunu yazdırır

$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString();    // SQL sorgusunu yazdırır
echo $result->getTime();           // yürütme süresini saniye cinsinden yazdırır

Sonucu HTML tablosu olarak görüntülemek için şunu kullanabilirsiniz:

$result = $database->query('SELECT * FROM articles');
$result->dump();

ResultSet, sütun tipleri hakkında bilgi sunar:

$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();

foreach ($types as $column => $type) {
	echo "$column tipi $type->type"; // örn. 'id tipi int'
}

Sorgu Günlüklemesi

Kendi sorgu günlüklememizi uygulayabiliriz. onQuery olayı, her yürütülen sorgudan sonra çağrılacak geri arama (callback) dizisidir:

$database->onQuery[] = function ($database, $result) use ($logger) {
	$logger->info('Sorgu: ' . $result->getQueryString());
	$logger->info('Süre: ' . $result->getTime());

	if ($result->getRowCount() > 1000) {
		$logger->warning('Büyük sonuç kümesi: ' . $result->getRowCount() . ' satır');
	}
};
versiyon: 4.0