Nette Documentation Preview

syntax
SQL Yolu
********

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

.[note]
Veritabanı bağlantısı ve yapılandırmasının ayrıntıları [Bağlantı ve yapılandırma |guide#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 |api:Nette\Database\ResultSet] nesnesi döndürür. Sorgu başarısız olursa metot [istisna fırlatır|exceptions]. Sorgu sonucunu bir `foreach` döngüsüyle dolaşabilir ya da [yardımcı metotlardan |#Veri Alma] birini kullanabilirsiniz.

```php
$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:

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

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

```php
$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:

```php
$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.

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

```php
// ❌ 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 |security] öğ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.

```php
$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:

```php
$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.

```php
$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:

```php
$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 |#SQL Kurma İpuçları] 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:

```php
$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.

```php
$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:

```php
$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 |security#Sütunlarla Güvenli Çalışma] öğrenin.


Veri Güncelleme (UPDATE)
------------------------

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

```php
// 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:

```php
$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:

```php
$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:

```php
$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).

```php
$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ı |security#Dinamik Tanımlayıcılar] 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:

```php
$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

```php
$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|configuration] `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.

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

Alternatif olarak:

```php
$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:

```php
$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:

```php
$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 |#fetch()] 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:

```php
// 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|api:Nette\Database\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.

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

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

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


fetch(): ?Row .[method]
-----------------------

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.

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


fetchAll(): array .[method]
---------------------------

`ResultSet` içinde kalan tüm satırları `Row` nesnelerinden oluşan bir dizi olarak döndürür.

```php
$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 .[method]
---------------------------------------------------------------------------------------

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:

```php
$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:

```php
$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:

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


fetchPairs(Closure $callback): array .[method]
----------------------------------------------

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.

```php
$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 .[method]
-----------------------------

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.

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


fetchList(): ?array .[method]
-----------------------------

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.

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


getRowCount(): ?int .[method]
-----------------------------

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 .[method]
--------------------------------

`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:

```php
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:

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

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

```php
$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:

```php
$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');
	}
};
```

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');
	}
};