SQL Yolu

Nette Database iki çalışma yolu sunar: SQL sorgularını kendiniz yazabilirsiniz (SQL yolu) ya da otomatik üretilmelerini sağlayabilirsiniz (bkz. Explorer). SQL yolu, sorguların güvenli biçimde kurulmasını güvence altına alırken size sorgular üzerinde tam denetim verir.

Veritabanı bağlantısı ve yapılandırmasının ayrıntıları Bağlantı ve yapılandırma bölümünde bulunabilir.

Temel Sorgulama

Veritabanını sorgulamak için query() metodu kullanılır. Sorgu sonucunu temsil eden bir ResultSet nesnesi döndürür. Sorgu başarısız olursa metot istisna fırlatır. Sorgu sonucunu bir foreach döngüsüyle dolaşabilir ya da yardımcı metotlardan birini kullanabilirsiniz.

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

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

Değerleri SQL sorgularına güvenle koymak için parametreli sorgular kullanın. Nette Database bunu son derece basit kılar: SQL sorgusundan sonra yalnızca bir virgül ve değeri ekleyin:

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

Birden çok parametrede iki seçeneğiniz var: SQL sorgusuyla parametreleri iç içe geçirebilirsiniz:

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

Ya da ö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'a Karşı Koruma

Parametreli sorgular kullanmak neden önemli? Çünkü sizi SQL injection denen bir saldırıdan korurlar; bu saldırıda bir saldırgan kendi SQL komutlarını ekleyip veritabanındaki verilere erişebilir ya da onlara zarar verebilir.

Değişkenleri asla doğrudan bir SQL sorgusuna koymayın! Her zaman, sizi SQL injection'dan koruyan parametreli sorgular kullanın.

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

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

Olası güvenlik risklerini öğrenin.

Sorgulama Teknikleri

WHERE Koşulları

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

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

Karşılaştırma operatörünü anahtarda açıkça da 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ğerler ya da diziler gibi özel durumları otomatik olarak ele alır.

$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

Olumsuz 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 NOT' => [],                 // atlanır
]);
// WHERE `name` != 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL

Koşullar varsayılan olarak AND operatörüyle birleştirilir. Bu, ?or yer tutucusuyla değiştirilebilir.

ORDER BY Kuralları

ORDER BY yan tümcesi bir dizi kullanılarak yazılabilir. Sütunları anahtarlarda belirtin ve artan (true) ya da azalan (false) sıralamayı boolean bir değerle gösterin:

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

Veri Ekleme (INSERT)

Kayıt eklemek için SQL INSERT komutu 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 (örneğin PostgreSQL), ID'nin üretileceği dizinin adını $database->getInsertId($sequenceId) biçiminde parametre olarak belirtmek gerekir.

Parametre olarak dosyalar, DateTime nesneleri ya da enum türleri gibi Özel Değerler de aktarabilirsiniz.

Birden çok kaydı tek seferde ekleme:

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

Çok kayıtlı bir INSERT çok daha hızlıdır; çünkü pek çok ayrı sorgu yerine yalnızca tek bir veritabanı sorgusu çalıştırılır.

Güvenlik notu: Doğrulanmamış verileri asla $values olarak kullanmayın. Olası riskleri öğrenin.

Veri Güncelleme (UPDATE)

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

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

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

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

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

Kayıt zaten varsa güncelleyen, yoksa ekleyen bir örnek. 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, bir dizi parametresinin SQL komutunda hangi bağlamda kullanıldığını tanıdığına ve SQL kodunu buna göre kurduğuna dikkat edin. Yani ilk diziden (id, name, year) VALUES (123, 'Jim', 1978) kurdu, ikincisini ise name = 'Jim', year = 1978 biçimine çevirdi. Bunu SQL Kurma İpuçları bölümünde daha ayrıntılı ele alıyoruz.

Veri Silme (DELETE)

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

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

SQL Kurma İpuçları

İpucu (hint), SQL sorgusunda parametre değerinin bir SQL ifadesine nasıl dönüştürüleceğini belirten özel bir yer tutucudur:

İpucu Açıklama Otomatik kullanıldığı yer
?name Tablo ya da sütun adı eklemek için kullanılır
?values (key, ...) VALUES (value, ...) üretir INSERT ... ?, REPLACE ... ?
?set key = value, ... atamalarını üretir SET ?, KEY UPDATE ?
?and Dizideki koşulları AND ile birleştirir WHERE ?, HAVING ?
?or Dizideki koşulları OR ile birleştirir
?order ORDER BY yan tümcesini üretir ORDER BY ?, GROUP BY ?

?name yer tutucusu, sorguya tablo ve sütun adlarını dinamik olarak eklemek için kullanılır. Nette Database, tanımlayıcıların veritabanı uzlaşımlarına göre doğru tırnaklanmasını üstlenir (örneğin MySQL'de ters tırnak içine alma).

$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 yer tutucusunu yalnızca doğrulanmış tablo ve sütun adlarında kullanın. Aksi hâlde güvenlik açıkları riskini alırsınız.

Diğer ipuçlarını genellikle belirtmeye gerek yoktur; çünkü Nette, SQL sorgusunu kurarken akıllı otomatik saptama kullanır (tablonun üçüncü sütununa bakın). Ama örneğin koşulları AND yerine OR ile birleştirmek istediğinizde 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

Yaygın skaler türlerin (string, int, bool) yanı sıra parametre olarak özel değerler de aktarabilirsiniz:

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

datetime veri türünü yerel olarak desteklemeyen veritabanlarında (SQLite ve Oracle gibi), DateTime ve DateTimeImmutable nesneleri, veritabanı yapılandırmasında formatDateTime öğesiyle belirtilen bir değere dönüştürülür (varsayılan değer U, yani Unix zaman damgasıdır).

SQL Sabit Değerleri

Bazı durumlarda, dize olarak ele alınıp kaçışlanmaması gereken ham SQL kodunu değer olarak aktarmanız gerekir. Bunun için Nette\Database\SqlLiteral sınıfının nesneleri kullanılır. Connection::literal() metoduyla oluşturulurlar.

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

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 sabit değerleri parametre 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, ilginç bileşimlere olanak tanır:

$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ı İçin Kısayollar

Veri almayı kolaylaştırmak için Connection, bir query() çağrısıyla ardından gelen bir fetch*() çağrısını birleştiren çeşitli kısayollar sunar. Bu metotlar query() ile aynı parametreleri, yani bir SQL sorgusunu ve isteğe bağlı parametreleri alır. fetch*() metotlarının tam açıklaması aşağıda bulunabilir.

fetch($sql, ...$params): ?Row Sorguyu çalıştırır ve ilk satırı Row nesnesi ya da null olarak döndürür.
fetchAll($sql, ...$params): array Sorguyu çalıştırır ve tüm satırları Row nesnelerinden oluşan bir dizi olarak döndürür.
fetchPairs($sql, ...$params): array Sorguyu çalıştırır ve ilişkisel bir dizi (anahtar ⇒ değer çiftleri) döndürür.
fetchField($sql, ...$params): mixed Sorguyu çalıştırır ve ilk satırdaki ilk sütunun değerini döndürür.
fetchList($sql, ...$params): ?array Sorguyu çalıştırır ve ilk satırı indeksli bir dizi ya da null 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

Bir sorgu çalıştırıldıktan sonra, sonuçları çeşitli yollarla dolaşmayı sağlayan bir ResultSet nesnesi döndürülür. Bir sorguyu çalıştırıp satırları almanın en kolay yolu, foreach döngüsüyle dolaşmaktır. Bu yöntem bellek açısından en verimli olanıdır; veriyi satır satır getirir ve sonuç kümesinin tamamını belleğe bir kerede yüklemez.

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

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

ResultSet yalnızca bir kez dolaşılabilir. Defalarca dolaşmanız gerekiyorsa, veriyi önce örneğin fetchAll() metoduyla bir diziye yüklemelisiniz.

fetch(): ?Row

Bir satırı Row nesnesi olarak döndürür. Başka satır kalmadıysa null döndürür. İç işaretçiyi sonraki satıra ilerletir.

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

fetchAll(): array

ResultSet içinde kalan tüm satırları Row nesnelerinden oluşan bir dizi 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ç kümesini ilişkisel bir dizi olarak döndürür. İlk argüman anahtar olarak kullanılacak sütunu, ikinci argüman ise değer olarak kullanılacak sütunu belirtir:

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

Yalnızca ilk parametre ($key) verilirse, değer olarak satırın tamamı (Row nesnesi) kullanılır:

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

Anahtarlar yinelenirse son satırdaki değer kullanılır. Anahtar olarak null kullanmak, sayısal indeksli (sıfırdan başlayan) bir dizi verir ve anahtar çakışmalarını önler:

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

fetchPairs (Closure $callback)array

Alternatif olarak, her satırı işleyen bir callback verebilirsiniz. Callback tek bir değer ya da bir anahtar-değer çifti döndürebilir.

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

// Callback, anahtar ve değer çiftinden oluşan bir dizi de döndürebilir:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]

fetchField(): mixed

Geçerli satırdaki ilk sütunun değerini döndürür. Başka satır kalmadıysa null döndürür. İç işaretçiyi sonraki satıra ilerletir.

$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // ilk satırdaki name değerini yükler

fetchList(): ?array

Satırı indeksli bir dizi olarak döndürür. Başka satır kalmadıysa null döndürür. İç işaretçiyi sonraki satıra ilerletir.

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

getRowCount(): ?int

Son UPDATE ya da DELETE sorgusundan etkilenen satır sayısını döndürür. SELECT sorgularında sonuç kümesindeki satır sayısını döndürür. Ancak bu her zaman bilinmeyebilir; o durumda metot null döndürür.

getColumnCount(): ?int

ResultSet içindeki sütun sayısını döndürür.

Sorgu Bilgisi

Hata ayıklama amacıyla, son çalıştırılan 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();           // çalışma süresini saniye cinsinden yazdırır

Sonucu bir HTML tablosu olarak göstermek için şunu kullanabilirsiniz:

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

ResultSet, sütun türleri hakkında bilgi sağlar:

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

foreach ($types as $column => $type) {
	echo "$column is of type $type"; // örneğin 'id is of type int'
}

Sorgu Günlükleme

Kendi sorgu günlüklememizi gerçekleştirebiliriz. onQuery olayı, çalıştırılan her sorgudan sonra çağrılan callback'lerden oluşan bir dizidir:

$database->onQuery[] = function ($database, $result) use ($logger) {
	$logger->info('Query: ' . $result->getQueryString());
	$logger->info('Time: ' . $result->getTime());

	if ($result->getRowCount() > 1000) {
		$logger->warning('Large result set: ' . $result->getRowCount() . ' rows');
	}
};