Nette Documentation Preview

syntax
Nette Schema
************

.[perex]
Veri yapılarını verilen bir şemaya karşı doğrulamak ve normalleştirmek için, akıllı ve anlaşılması kolay bir API sunan pratik bir kütüphane.

Kurulum:

```shell
composer require nette/schema
```


Temel Kullanım
--------------

`$schema` değişkeninde bir doğrulama şemamız var (bunun ne demek olduğunu ve nasıl oluşturulacağını birazdan açıklayacağız), `$data` değişkeninde ise doğrulamak ve normalleştirmek istediğimiz veri yapısı var. Bu örneğin bir kullanıcının API üzerinden gönderdiği veri, bir yapılandırma dosyası vb. olabilir.

İşi [api:Nette\Schema\Processor] sınıfı üstlenir; o, girdiyi işler ve ya normalleştirilmiş veriyi döndürür ya da bir hata olursa [api:Nette\Schema\ValidationException] istisnası fırlatır.

```php
$processor = new Nette\Schema\Processor;

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Data is invalid: ' . $e->getMessage();
}
```

`$e->getMessages()` metodu tüm mesajları dize olarak içeren bir dizi, `$e->getMessageObjects()` ise tüm mesajları "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html nesneleri olarak döndürür.


Şemayı Tanımlama
----------------

Ve şimdi şemayı oluşturalım. Onu tanımlamak için [api:Nette\Schema\Expect] sınıfı kullanılır; özünde verinin nasıl görünmesi gerektiğine ilişkin beklentileri tanımlarız. Diyelim ki girdi verisi, bool tipinde `processRefund` ve int tipinde `refundAmount` öğelerini içeren bir yapı (örneğin bir dizi) olmalı.

```php
use Nette\Schema\Expect;

$schema = Expect::structure([
	'processRefund' => Expect::bool(),
	'refundAmount' => Expect::int(),
]);
```

Şema tanımının, onu ilk kez görüyor olsanız bile anlaşılır göründüğüne inanıyoruz.

Doğrulama için şu veriyi gönderelim:

```php
$data = [
	'processRefund' => true,
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, doğrulamayı geçer
```

Çıktı, yani `$normalized` değeri bir `stdClass` nesnesidir. Çıktının bir dizi olmasını isteseydik, şemaya `->castTo('array')` dönüşümünü eklerdik.

Yapının tüm öğeleri isteğe bağlıdır ve varsayılan değerleri `null` olur. Örnek:

```php
$data = [
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, doğrulamayı geçer
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
```

Varsayılan değerin `null` olması, girdi verisinde `'processRefund' => null` değerini kabul edeceği anlamına gelmez. Hayır, girdi bir mantıksal değer, yani yalnızca `true` ya da `false` olmalıdır. `null` değerine açıkça izin vermek için `Expect::bool()->nullable()` kullanmamız gerekirdi.

Bir öğe, `Expect::bool()->required()` kullanılarak zorunlu kılınabilir. Varsayılan değeri örneğin `false` olarak `Expect::bool()->default(false)` ile ya da kısaca `Expect::bool(false)` ile değiştirebiliriz.

Peki mantıksal değerlerin yanı sıra `1` ve `0` değerlerini de kabul etmek isteseydik? O zaman mantıksal değere normalleştirmek istediğimiz değerleri listeleriz:

```php
$schema = Expect::structure([
	'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'),
	'refundAmount' => Expect::int(),
]);

$normalized = $processor->process($schema, $data);
is_bool($normalized->processRefund); // true
```

Artık bir şema tanımlamanın temellerini ve yapı öğelerinin nasıl davrandığını biliyorsunuz. Şimdi bir şema tanımlarken başka hangi öğeleri kullanabileceğinizi göstereceğiz.


Veri Tipleri: type()
--------------------

Şemada tüm standart PHP veri tipleri belirtilebilir:

```php
Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])
Expect::list($default = [])
```

Ayrıca [Validators sınıfının desteklediği |utils:validators#Beklenen Türler] tüm tipler de, örneğin `Expect::type('scalar')` ya da kısaca `Expect::scalar()`. Sınıf ya da arayüz adları da, örneğin `Expect::type('AddressEntity')`.

Union sözdizimi de kullanılabilir:

```php
Expect::type('bool|string|array')
```

Varsayılan değer, boş dizi olduğu `array` ve `list` dışında her zaman `null` değeridir. (Liste, sıfırdan başlayan sayısal anahtarlar dizisiyle indekslenen bir dizidir, yani ilişkisel olmayan bir dizi.)


Değer Dizisi: arrayOf() listOf()
--------------------------------

Dizi çok genel bir yapıyı temsil eder; hangi öğeleri içerebileceğini tam olarak belirtmek daha yararlıdır. Örneğin öğeleri yalnızca dize olabilen bir dizi:

```php
$schema = Expect::arrayOf('string');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // HATA: 123 bir dize değil
```

İkinci parametre anahtarları belirtebilir (1.2 sürümünden beri):

```php
$schema = Expect::arrayOf('string', 'int');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // HATA: 'a' bir int değil
```

Liste, indeksli bir dizidir:

```php
$schema = Expect::listOf('string');

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // HATA: 123 bir dize değil
$processor->process($schema, ['key' => 'a']); // HATA: liste değil
$processor->process($schema, [1 => 'a', 0 => 'b']); // HATA: bu da liste değil
```

Parametre bir şema da olabilir, dolayısıyla şöyle yazabiliriz:

```php
Expect::arrayOf(Expect::bool())
```

Varsayılan değer boş bir dizidir. Bir varsayılan değer belirtirseniz, o aktarılan veriyle birleştirilir. Bu, `mergeDefaults(false)` ile kapatılabilir (1.1 sürümünden beri).


Sıralama: anyOf()
-----------------

`anyOf()`, bir değerin alabileceği değerlerden ya da şemalardan oluşan bir kümeyi temsil eder. Öğeleri `'a'`, `true` ya da `null` olabilen bir diziyi şöyle yazarsınız:

```php
$schema = Expect::listOf(
	Expect::anyOf('a', true, null),
);

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // HATA: false oraya ait değil
```

Sıralamanın öğeleri şema da olabilir:

```php
$schema = Expect::listOf(
	Expect::anyOf(Expect::string(), true, null),
);

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // HATA
```

`anyOf()` metodu çeşitleri bir dizi olarak değil, ayrı parametreler olarak kabul eder. Ona bir değer dizisi aktarmak için `anyOf(...$variants)` unpack operatörünü kullanın.

Varsayılan değer `null` değeridir. İlk öğeyi varsayılan yapmak için `firstIsDefault()` metodunu kullanın:

```php
// varsayılan 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
```


Yapılar
-------

Yapılar, tanımlı anahtarları olan nesnelerdir. Her anahtar-değer çiftine "özellik" denir.

Yapılar dizileri ve nesneleri kabul eder, `stdClass` nesneleri döndürür.

Varsayılan olarak tüm özellikler isteğe bağlıdır ve varsayılan değerleri `null` olur. Zorunlu özellikleri `required()` kullanarak tanımlayabilirsiniz:

```php
$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // varsayılan değer null
]);

$processor->process($schema, ['optional' => '']);
// HATA: 'required' seçeneği eksik

$processor->process($schema, ['required' => 'foo']);
// OK, {'required' => 'foo', 'optional' => null} döndürür
```

Bir yapının kendisi zorunludur. Bu yüzden başka bir yapının içine gömülüyse ve girdi onu içermiyorsa, yine de oluşturulur ve zorunlu bir özellik içerdiğinde hata bildirir. İç içe yapının tamamını isteğe bağlı kılmak için `required(false)` kullanın. Girdide eksikse çıktıda `null` görünür, ama varsa zorunlu özellikleri zorunlu tutulur:

```php
$schema = Expect::structure([
	'db' => Expect::structure([
		'dsn' => Expect::string()->required(),
	])->required(false),
]);

$processor->process($schema, []);
// OK, {'db' => null} döndürür

$processor->process($schema, ['db' => []]);
// HATA: 'db › dsn' eksik
```

Varsayılan değere sahip özelliklerin çıktıda olmasını istemiyorsanız `skipDefaults()` kullanın:

```php
$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(),
])->skipDefaults();

$processor->process($schema, ['required' => 'foo']);
// OK, {'required' => 'foo'} döndürür
```

`optional` özelliğinin varsayılan değeri `null` olsa da, girdi verisinde ona izin verilmez (değer bir dize olmalıdır). `null` kabul eden özellikler `nullable()` kullanılarak tanımlanır:

```php
$schema = Expect::structure([
	'optional' => Expect::string(),
	'nullable' => Expect::string()->nullable(),
]);

$processor->process($schema, ['optional' => null]);
// HATA: 'optional' bir dize olmasını bekler, null verildi.

$processor->process($schema, ['nullable' => null]);
// OK, {'optional' => null, 'nullable' => null} döndürür
```

Yapının tüm özelliklerinden oluşan diziyi `getShape()` metodu döndürür.

Varsayılan olarak girdi verisinde ek öğeler bulunamaz:

```php
$schema = Expect::structure([
	'key' => Expect::string(),
]);

$processor->process($schema, ['additional' => 1]);
// HATA: Beklenmeyen 'additional' öğesi
```

Bu, `otherItems()` kullanılarak değiştirilebilir. Parametre olarak, her fazladan öğeyi doğrulayacak şemayı aktarın:

```php
$schema = Expect::structure([
	'key' => Expect::string(),
])->otherItems(Expect::int());

$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // HATA
```

`extend()` kullanarak başka bir yapıyı genişleterek yeni bir yapı oluşturabilirsiniz:

```php
$dog = Expect::structure([
	'name' => Expect::string(),
	'age' => Expect::int(),
]);

$dogWithBreed = $dog->extend([
	'breed' => Expect::string(),
]);
```


Dizi .{data-version:1.3.2}
--------------------------

Tanımlı anahtarları olan bir dizi. [Yapılar |#Yapılar] için geçerli olan her şey onun için de geçerlidir.

```php
$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // varsayılan değer null
]);
```

Tuple denen indeksli bir dizi de tanımlayabilirsiniz:

```php
$schema = Expect::array([
	Expect::int(),
	Expect::string(),
	Expect::bool(),
]);

$processor->process($schema, [1, 'hello', true]); // OK
```


Deprecated Özellikler
---------------------

Bir özelliği `deprecated([string $message])` metoduyla deprecated olarak işaretleyebilirsiniz. Deprecation hakkındaki bilgi `$processor->getWarnings()` ile döndürülür:

```php
$schema = Expect::structure([
	'old' => Expect::int()->deprecated('The item %path% is deprecated'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["The item 'old' is deprecated"]
```


Aralıklar: min() max()
----------------------

Diziler için öğe sayısını sınırlamak üzere `min()` ve `max()` kullanın:

```php
// dizi, en az 10 öğe, en çok 20 öğe
Expect::array()->min(10)->max(20);
```

Dizeler için uzunluğunu sınırlar:

```php
// dize, en az 10 karakter uzunluğunda, en çok 20 karakter
Expect::string()->min(10)->max(20);
```

Sayılar için değerini sınırlar:

```php
// tam sayı, 10 ile 20 arasında, sınırlar dahil
Expect::int()->min(10)->max(20);
```

Elbette yalnızca `min()` ya da yalnızca `max()` belirtmek mümkündür:

```php
// dize, en çok 20 karakter
Expect::string()->max(20);
```


Düzenli İfadeler: pattern()
---------------------------

`pattern()` kullanarak, girdi dizesinin **tamamının** eşleşmesi gereken bir düzenli ifade belirtebilirsiniz (yani sanki `^` ve `$` karakterleriyle sarılmış gibi):

```php
// tam olarak 9 rakam
Expect::string()->pattern('\d{9}');
```


Özel Doğrulamalar: assert()
---------------------------

Başka her türlü kısıtlamayı `assert(callable $fn)` kullanarak ekleyebilirsiniz.

```php
$countIsEven = fn($v) => count($v) % 2 === 0;

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // öğe sayısı çift olmalı

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // HATA: 3 çift bir sayı değil
```

Ya da

```php
Expect::string()->assert('is_file'); // dosya var olmalı
```

Her doğrulamaya özel bir açıklama ekleyebilirsiniz. O, hata mesajının parçası olur.

```php
$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Even items in array');

$processor->process($schema, ['a', 'b', 'c']);
// Failed assertion "Even items in array" for item with value array.
```

Metot, birden çok kısıtlama eklemek için yinelemeli çağrılabilir. `transform()` ve `castTo()` çağrılarıyla iç içe geçirilebilir.


Dönüşüm: transform() .{data-version:1.2.5}
------------------------------------------

Başarıyla doğrulanan veri, özel bir fonksiyonla değiştirilebilir:

```php
// büyük harfe dönüştür:
Expect::string()->transform(fn(string $s) => strtoupper($s));
```

Metot, birden çok dönüşüm eklemek için yinelemeli çağrılabilir. `assert()` ve `castTo()` çağrılarıyla iç içe geçirilebilir. İşlemler, bildirildikleri sırayla yapılır:

```php
Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'All characters must be lowercased')
	->transform(fn(string $s) => strtoupper($s)); // büyük harfe dönüştür
```

`transform()` metodu değeri aynı anda hem dönüştürebilir hem doğrulayabilir. Bu sıklıkla `transform()` ve `assert()` zincirlemekten daha basittir ve daha az kod yinelemesi içerir. Bu amaçla fonksiyon, doğrulama sorunları hakkında bilgi eklemek için kullanılabilen `addError()` metoduna sahip bir [Context |api:Nette\Schema\Context] nesnesi alır:

```php
Expect::string()
	->transform(function (string $s, Nette\Schema\Context $context) {
		if (!ctype_lower($s)) {
			$context->addError('All characters must be lowercased', 'my.case.error');
			return null;
		}

		return strtoupper($s);
	});
```


Dönüştürme: castTo()
--------------------

Başarıyla doğrulanan veri dönüştürülebilir:

```php
Expect::scalar()->castTo('string');
```

Yerel PHP tiplerinin yanı sıra sınıflara da dönüştürebilirsiniz. Yapıcısı olmayan basit bir sınıf ile yapıcısı olan bir sınıf arasında ayrım yapar. Sınıfın yapıcısı yoksa bir örnek oluşturulur ve tüm yapı öğeleri özelliklere yazılır:

```php
class Info
{
	public bool $processRefund;
	public int $refundAmount;
}

Expect::structure([
	'processRefund' => Expect::bool(),
	'refundAmount' => Expect::int(),
])->castTo(Info::class);

// '$obj = new Info' oluşturur ve $obj->processRefund ile $obj->refundAmount alanlarına yazar
```

Sınıfın yapıcısı varsa, yapı öğeleri yapıcıya adlandırılmış argümanlar olarak aktarılır:

```php
class Info
{
	public function __construct(
		public bool $processRefund,
		public int $refundAmount,
	) {
	}
}

// $obj = new Info(processRefund: ..., refundAmount: ...) oluşturur
```

Skaler bir parametreyle birleştirilen dönüştürme, bir nesne oluşturur ve değeri yapıcıya tek argüman olarak aktarır:

```php
Expect::string()->castTo(DateTime::class);
// new DateTime(...) oluşturur
```


Normalleştirme: before()
------------------------

Doğrulamanın kendisinden önce veri, `before()` metoduyla normalleştirilebilir. Örnek olarak, bir dize dizisi olması gereken (örneğin `['a', 'b', 'c']`), ama girdiyi `a b c` dizesi biçiminde kabul eden bir öğeyi ele alalım:

```php
$explode = fn($v) => explode(' ', $v);

$schema = Expect::arrayOf('string')
	->before($explode);

$normalized = $processor->process($schema, 'a b c');
// OK ve ['a', 'b', 'c'] döndürür
```


Nesnelere Eşleme: from()
------------------------

Yapı şemasını bir sınıftan ürettirebilirsiniz. Örnek:

```php
class Config
{
	public string $name;
	public string|null $password = null;
	public bool $admin = false;
}

$schema = Expect::from(new Config);

$data = [
	'name' => 'Frank',
];

$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'Frank', 'password' => null, 'admin' => false}
```

Anonim sınıflar da desteklenir:

```php
$schema = Expect::from(new class {
	public string $name;
	public ?string $password = null;
	public bool $admin = false;
});
```

Sınıf tanımından elde edilen bilgi yeterli olmayabileceğinden, öğeleri ikinci parametre kullanarak kendi şemanızla tamamlayabilirsiniz:

```php
$schema = Expect::from(new Config, [
	'name' => Expect::string()->pattern('\w:.*'),
]);
```


Birden Çok Yapılandırmayı Birleştirme
-------------------------------------

Uygulamalar yapılandırmalarını sıklıkla katmanlar hâlinde kurar: yerleşik varsayılan değerler vardır ve onların üstüne kullanıcı, yalnızca gerçekten belirttiği öğeleri geçersiz kılması gereken kendi ayarlarını sağlar. `processMultiple()` tam da bunu yapar: birkaç veri kümesini alır, sonrakiler öncelikli olacak biçimde onları sırayla birleştirir ve nihai sonucu bir bütün olarak doğrular:

```php
$schema = Expect::structure([
	'host' => Expect::string(),
	'port' => Expect::int(),
	'logging' => Expect::bool(),
]);

$defaults = ['host' => 'localhost', 'port' => 3306, 'logging' => false];
$userConfig = ['port' => 5432, 'logging' => true];

$config = $processor->processMultiple($schema, [$defaults, $userConfig]);
// $config = {'host' => 'localhost', 'port' => 5432, 'logging' => true}
```

`host` öğesi, kullanıcı onu ayarlamadığından varsayılan değerini korur; `port` ve `logging` ise sonraki veri kümesince üzerine yazılır. Dize anahtarlar altında saklanan değerler bu şekilde birleştirilir; sayısal indeksli öğeler (listeler) ise üzerine yazılmak yerine art arda eklenir.


Kaputun Altında: normalize, merge, complete
-------------------------------------------

Her şema öğesi, ister yerleşik olsun ister kendi yazdığınız, veriyi nasıl ele aldığını birlikte tanımlayan dört metodu gerçekleştirir. Onlardan üçü işleme hattını oluşturur:

1. **normalize()** - ham girdiyi hazırlar. `before()` kancaları burada çalışır ve örneğin bir nesne burada diziye dönüştürülür. İlk olarak, her veri kümesi üzerinde ayrı ayrı çalışır.
2. **merge()** - normalleştirilmiş iki veri kümesini, sonraki öncelikli olacak biçimde birleştirir. Bu adımı yalnızca `processMultiple()` kullanır; `process()` onu atlar, çünkü elinde tek bir veri kümesi vardır.
3. **complete()** - asıl doğrulamayı yapar, eksik öğeler için varsayılan değerleri doldurur ve `assert()`, `transform()` ile `castTo()` uygular. Birleştirilmiş sonuç üzerinde en son çalışır.

Dördüncü metot **completeDefault()**, girdide tümüyle eksik olan bir öğe için ana öğe tarafından çağrılır; ya varsayılan değeri sağlar ya da bir `required()` öğesinin eksik olduğunu bildirir.

Yani `process()`, *normalize → complete* çalıştırır; `processMultiple()` ise *normalize (her veri kümesi) → merge → complete* çalıştırır. `before()` metodunun ham girdiyi, `transform()` metodunun ise zaten doğrulanmış değeri görmesinin nedeni bu sıradır.


Özel Şema Öğeleri
-----------------

`assert()`, `transform()` ve `before()` ile epey yol alabilirsiniz, dolayısıyla sıfırdan bir şey kurmanız nadiren gerekir. Ama kendi doğrulama ve birleştirme mantığı olan, yeniden kullanılabilir, kendi kendine yeten bir öğe istediğinizde, [api:Nette\Schema\Schema] arayüzünü gerçekleştirerek bir tane oluşturabilirsiniz. Onda tam olarak yukarıda anlatılan dört metot bulunur:

```php
interface Schema
{
	function normalize(mixed $value, Context $context);
	function merge(mixed $value, mixed $base);
	function complete(mixed $value, Context $context);
	function completeDefault(Context $context);
}
```

Hatalar fırlatılmaz; onun yerine onları [Context |api:Nette\Schema\Context] nesnesi aracılığıyla `$context->addError()` ile bildirir ve `null` döndürürsünüz. `Processor` tüm hataları toplar ve onları en sonda birlikte fırlatır.

Örnek olarak, bir enum'un backing değerini (örneğin `'hearts'` dizesini) kabul eden ve enum örneğini döndüren, yeniden kullanılabilir bir öğe kuralım:

```php
use Nette\Schema\Context;
use Nette\Schema\Schema;

class EnumSchema implements Schema
{
	public function __construct(
		private string $enumClass,
	) {
	}

	public function normalize(mixed $value, Context $context): mixed
	{
		return $value; // ön işleme gerekmiyor
	}

	public function merge(mixed $value, mixed $base): mixed
	{
		return $value ?? $base; // sonraki değer kazanır
	}

	public function complete(mixed $value, Context $context): mixed
	{
		$enum = is_string($value) ? ($this->enumClass)::tryFrom($value) : null;
		if ($enum === null) {
			$context->addError('The item %path% is not a valid value.', 'enum.value');
			return null;
		}

		return $enum;
	}

	public function completeDefault(Context $context): mixed
	{
		return null; // öğe girdide eksikken kullanılan değer
	}
}
```

Onu, yerleşik bir öğenin beklendiği her yerde kullanabilirsiniz: tek başına ya da daha büyük bir yapının parçası olarak:

```php
enum Suit: string
{
	case Hearts = 'hearts';
	case Spades = 'spades';
}

$schema = Expect::structure([
	'suit' => new EnumSchema(Suit::class),
]);

$processor->process($schema, ['suit' => 'hearts']);
// OK, {'suit' => Suit::Hearts} döndürür
```

Öğe arayüzün tamamını gerçekleştirdiğinden, `processMultiple()` içinde de otomatik çalışır; `Processor` onun `merge()` metodunu tıpkı başka her öğede olduğu gibi çağırır.

Nette Schema

Veri yapılarını verilen bir şemaya karşı doğrulamak ve normalleştirmek için, akıllı ve anlaşılması kolay bir API sunan pratik bir kütüphane.

Kurulum:

composer require nette/schema

Temel Kullanım

$schema değişkeninde bir doğrulama şemamız var (bunun ne demek olduğunu ve nasıl oluşturulacağını birazdan açıklayacağız), $data değişkeninde ise doğrulamak ve normalleştirmek istediğimiz veri yapısı var. Bu örneğin bir kullanıcının API üzerinden gönderdiği veri, bir yapılandırma dosyası vb. olabilir.

İşi Nette\Schema\Processor sınıfı üstlenir; o, girdiyi işler ve ya normalleştirilmiş veriyi döndürür ya da bir hata olursa Nette\Schema\ValidationException istisnası fırlatır.

$processor = new Nette\Schema\Processor;

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Data is invalid: ' . $e->getMessage();
}

$e->getMessages() metodu tüm mesajları dize olarak içeren bir dizi, $e->getMessageObjects() ise tüm mesajları Nette\Schema\Message nesneleri olarak döndürür.

Şemayı Tanımlama

Ve şimdi şemayı oluşturalım. Onu tanımlamak için Nette\Schema\Expect sınıfı kullanılır; özünde verinin nasıl görünmesi gerektiğine ilişkin beklentileri tanımlarız. Diyelim ki girdi verisi, bool tipinde processRefund ve int tipinde refundAmount öğelerini içeren bir yapı (örneğin bir dizi) olmalı.

use Nette\Schema\Expect;

$schema = Expect::structure([
	'processRefund' => Expect::bool(),
	'refundAmount' => Expect::int(),
]);

Şema tanımının, onu ilk kez görüyor olsanız bile anlaşılır göründüğüne inanıyoruz.

Doğrulama için şu veriyi gönderelim:

$data = [
	'processRefund' => true,
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, doğrulamayı geçer

Çıktı, yani $normalized değeri bir stdClass nesnesidir. Çıktının bir dizi olmasını isteseydik, şemaya ->castTo('array') dönüşümünü eklerdik.

Yapının tüm öğeleri isteğe bağlıdır ve varsayılan değerleri null olur. Örnek:

$data = [
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, doğrulamayı geçer
// $normalized = {'processRefund' => null, 'refundAmount' => 17}

Varsayılan değerin null olması, girdi verisinde 'processRefund' => null değerini kabul edeceği anlamına gelmez. Hayır, girdi bir mantıksal değer, yani yalnızca true ya da false olmalıdır. null değerine açıkça izin vermek için Expect::bool()->nullable() kullanmamız gerekirdi.

Bir öğe, Expect::bool()->required() kullanılarak zorunlu kılınabilir. Varsayılan değeri örneğin false olarak Expect::bool()->default(false) ile ya da kısaca Expect::bool(false) ile değiştirebiliriz.

Peki mantıksal değerlerin yanı sıra 1 ve 0 değerlerini de kabul etmek isteseydik? O zaman mantıksal değere normalleştirmek istediğimiz değerleri listeleriz:

$schema = Expect::structure([
	'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'),
	'refundAmount' => Expect::int(),
]);

$normalized = $processor->process($schema, $data);
is_bool($normalized->processRefund); // true

Artık bir şema tanımlamanın temellerini ve yapı öğelerinin nasıl davrandığını biliyorsunuz. Şimdi bir şema tanımlarken başka hangi öğeleri kullanabileceğinizi göstereceğiz.

Veri Tipleri: type()

Şemada tüm standart PHP veri tipleri belirtilebilir:

Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])
Expect::list($default = [])

Ayrıca Validators sınıfının desteklediği tüm tipler de, örneğin Expect::type('scalar') ya da kısaca Expect::scalar(). Sınıf ya da arayüz adları da, örneğin Expect::type('AddressEntity').

Union sözdizimi de kullanılabilir:

Expect::type('bool|string|array')

Varsayılan değer, boş dizi olduğu array ve list dışında her zaman null değeridir. (Liste, sıfırdan başlayan sayısal anahtarlar dizisiyle indekslenen bir dizidir, yani ilişkisel olmayan bir dizi.)

Değer Dizisi: arrayOf() listOf()

Dizi çok genel bir yapıyı temsil eder; hangi öğeleri içerebileceğini tam olarak belirtmek daha yararlıdır. Örneğin öğeleri yalnızca dize olabilen bir dizi:

$schema = Expect::arrayOf('string');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // HATA: 123 bir dize değil

İkinci parametre anahtarları belirtebilir (1.2 sürümünden beri):

$schema = Expect::arrayOf('string', 'int');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // HATA: 'a' bir int değil

Liste, indeksli bir dizidir:

$schema = Expect::listOf('string');

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // HATA: 123 bir dize değil
$processor->process($schema, ['key' => 'a']); // HATA: liste değil
$processor->process($schema, [1 => 'a', 0 => 'b']); // HATA: bu da liste değil

Parametre bir şema da olabilir, dolayısıyla şöyle yazabiliriz:

Expect::arrayOf(Expect::bool())

Varsayılan değer boş bir dizidir. Bir varsayılan değer belirtirseniz, o aktarılan veriyle birleştirilir. Bu, mergeDefaults(false) ile kapatılabilir (1.1 sürümünden beri).

Sıralama: anyOf()

anyOf(), bir değerin alabileceği değerlerden ya da şemalardan oluşan bir kümeyi temsil eder. Öğeleri 'a', true ya da null olabilen bir diziyi şöyle yazarsınız:

$schema = Expect::listOf(
	Expect::anyOf('a', true, null),
);

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // HATA: false oraya ait değil

Sıralamanın öğeleri şema da olabilir:

$schema = Expect::listOf(
	Expect::anyOf(Expect::string(), true, null),
);

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // HATA

anyOf() metodu çeşitleri bir dizi olarak değil, ayrı parametreler olarak kabul eder. Ona bir değer dizisi aktarmak için anyOf(...$variants) unpack operatörünü kullanın.

Varsayılan değer null değeridir. İlk öğeyi varsayılan yapmak için firstIsDefault() metodunu kullanın:

// varsayılan 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();

Yapılar

Yapılar, tanımlı anahtarları olan nesnelerdir. Her anahtar-değer çiftine „özellik“ denir.

Yapılar dizileri ve nesneleri kabul eder, stdClass nesneleri döndürür.

Varsayılan olarak tüm özellikler isteğe bağlıdır ve varsayılan değerleri null olur. Zorunlu özellikleri required() kullanarak tanımlayabilirsiniz:

$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // varsayılan değer null
]);

$processor->process($schema, ['optional' => '']);
// HATA: 'required' seçeneği eksik

$processor->process($schema, ['required' => 'foo']);
// OK, {'required' => 'foo', 'optional' => null} döndürür

Bir yapının kendisi zorunludur. Bu yüzden başka bir yapının içine gömülüyse ve girdi onu içermiyorsa, yine de oluşturulur ve zorunlu bir özellik içerdiğinde hata bildirir. İç içe yapının tamamını isteğe bağlı kılmak için required(false) kullanın. Girdide eksikse çıktıda null görünür, ama varsa zorunlu özellikleri zorunlu tutulur:

$schema = Expect::structure([
	'db' => Expect::structure([
		'dsn' => Expect::string()->required(),
	])->required(false),
]);

$processor->process($schema, []);
// OK, {'db' => null} döndürür

$processor->process($schema, ['db' => []]);
// HATA: 'db › dsn' eksik

Varsayılan değere sahip özelliklerin çıktıda olmasını istemiyorsanız skipDefaults() kullanın:

$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(),
])->skipDefaults();

$processor->process($schema, ['required' => 'foo']);
// OK, {'required' => 'foo'} döndürür

optional özelliğinin varsayılan değeri null olsa da, girdi verisinde ona izin verilmez (değer bir dize olmalıdır). null kabul eden özellikler nullable() kullanılarak tanımlanır:

$schema = Expect::structure([
	'optional' => Expect::string(),
	'nullable' => Expect::string()->nullable(),
]);

$processor->process($schema, ['optional' => null]);
// HATA: 'optional' bir dize olmasını bekler, null verildi.

$processor->process($schema, ['nullable' => null]);
// OK, {'optional' => null, 'nullable' => null} döndürür

Yapının tüm özelliklerinden oluşan diziyi getShape() metodu döndürür.

Varsayılan olarak girdi verisinde ek öğeler bulunamaz:

$schema = Expect::structure([
	'key' => Expect::string(),
]);

$processor->process($schema, ['additional' => 1]);
// HATA: Beklenmeyen 'additional' öğesi

Bu, otherItems() kullanılarak değiştirilebilir. Parametre olarak, her fazladan öğeyi doğrulayacak şemayı aktarın:

$schema = Expect::structure([
	'key' => Expect::string(),
])->otherItems(Expect::int());

$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // HATA

extend() kullanarak başka bir yapıyı genişleterek yeni bir yapı oluşturabilirsiniz:

$dog = Expect::structure([
	'name' => Expect::string(),
	'age' => Expect::int(),
]);

$dogWithBreed = $dog->extend([
	'breed' => Expect::string(),
]);

Dizi

Tanımlı anahtarları olan bir dizi. Yapılar için geçerli olan her şey onun için de geçerlidir.

$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // varsayılan değer null
]);

Tuple denen indeksli bir dizi de tanımlayabilirsiniz:

$schema = Expect::array([
	Expect::int(),
	Expect::string(),
	Expect::bool(),
]);

$processor->process($schema, [1, 'hello', true]); // OK

Deprecated Özellikler

Bir özelliği deprecated([string $message]) metoduyla deprecated olarak işaretleyebilirsiniz. Deprecation hakkındaki bilgi $processor->getWarnings() ile döndürülür:

$schema = Expect::structure([
	'old' => Expect::int()->deprecated('The item %path% is deprecated'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["The item 'old' is deprecated"]

Aralıklar: min() max()

Diziler için öğe sayısını sınırlamak üzere min() ve max() kullanın:

// dizi, en az 10 öğe, en çok 20 öğe
Expect::array()->min(10)->max(20);

Dizeler için uzunluğunu sınırlar:

// dize, en az 10 karakter uzunluğunda, en çok 20 karakter
Expect::string()->min(10)->max(20);

Sayılar için değerini sınırlar:

// tam sayı, 10 ile 20 arasında, sınırlar dahil
Expect::int()->min(10)->max(20);

Elbette yalnızca min() ya da yalnızca max() belirtmek mümkündür:

// dize, en çok 20 karakter
Expect::string()->max(20);

Düzenli İfadeler: pattern()

pattern() kullanarak, girdi dizesinin tamamının eşleşmesi gereken bir düzenli ifade belirtebilirsiniz (yani sanki ^ ve $ karakterleriyle sarılmış gibi):

// tam olarak 9 rakam
Expect::string()->pattern('\d{9}');

Özel Doğrulamalar: assert()

Başka her türlü kısıtlamayı assert(callable $fn) kullanarak ekleyebilirsiniz.

$countIsEven = fn($v) => count($v) % 2 === 0;

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // öğe sayısı çift olmalı

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // HATA: 3 çift bir sayı değil

Ya da

Expect::string()->assert('is_file'); // dosya var olmalı

Her doğrulamaya özel bir açıklama ekleyebilirsiniz. O, hata mesajının parçası olur.

$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Even items in array');

$processor->process($schema, ['a', 'b', 'c']);
// Failed assertion "Even items in array" for item with value array.

Metot, birden çok kısıtlama eklemek için yinelemeli çağrılabilir. transform() ve castTo() çağrılarıyla iç içe geçirilebilir.

Dönüşüm: transform()

Başarıyla doğrulanan veri, özel bir fonksiyonla değiştirilebilir:

// büyük harfe dönüştür:
Expect::string()->transform(fn(string $s) => strtoupper($s));

Metot, birden çok dönüşüm eklemek için yinelemeli çağrılabilir. assert() ve castTo() çağrılarıyla iç içe geçirilebilir. İşlemler, bildirildikleri sırayla yapılır:

Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'All characters must be lowercased')
	->transform(fn(string $s) => strtoupper($s)); // büyük harfe dönüştür

transform() metodu değeri aynı anda hem dönüştürebilir hem doğrulayabilir. Bu sıklıkla transform() ve assert() zincirlemekten daha basittir ve daha az kod yinelemesi içerir. Bu amaçla fonksiyon, doğrulama sorunları hakkında bilgi eklemek için kullanılabilen addError() metoduna sahip bir Context nesnesi alır:

Expect::string()
	->transform(function (string $s, Nette\Schema\Context $context) {
		if (!ctype_lower($s)) {
			$context->addError('All characters must be lowercased', 'my.case.error');
			return null;
		}

		return strtoupper($s);
	});

Dönüştürme: castTo()

Başarıyla doğrulanan veri dönüştürülebilir:

Expect::scalar()->castTo('string');

Yerel PHP tiplerinin yanı sıra sınıflara da dönüştürebilirsiniz. Yapıcısı olmayan basit bir sınıf ile yapıcısı olan bir sınıf arasında ayrım yapar. Sınıfın yapıcısı yoksa bir örnek oluşturulur ve tüm yapı öğeleri özelliklere yazılır:

class Info
{
	public bool $processRefund;
	public int $refundAmount;
}

Expect::structure([
	'processRefund' => Expect::bool(),
	'refundAmount' => Expect::int(),
])->castTo(Info::class);

// '$obj = new Info' oluşturur ve $obj->processRefund ile $obj->refundAmount alanlarına yazar

Sınıfın yapıcısı varsa, yapı öğeleri yapıcıya adlandırılmış argümanlar olarak aktarılır:

class Info
{
	public function __construct(
		public bool $processRefund,
		public int $refundAmount,
	) {
	}
}

// $obj = new Info(processRefund: ..., refundAmount: ...) oluşturur

Skaler bir parametreyle birleştirilen dönüştürme, bir nesne oluşturur ve değeri yapıcıya tek argüman olarak aktarır:

Expect::string()->castTo(DateTime::class);
// new DateTime(...) oluşturur

Normalleştirme: before()

Doğrulamanın kendisinden önce veri, before() metoduyla normalleştirilebilir. Örnek olarak, bir dize dizisi olması gereken (örneğin ['a', 'b', 'c']), ama girdiyi a b c dizesi biçiminde kabul eden bir öğeyi ele alalım:

$explode = fn($v) => explode(' ', $v);

$schema = Expect::arrayOf('string')
	->before($explode);

$normalized = $processor->process($schema, 'a b c');
// OK ve ['a', 'b', 'c'] döndürür

Nesnelere Eşleme: from()

Yapı şemasını bir sınıftan ürettirebilirsiniz. Örnek:

class Config
{
	public string $name;
	public string|null $password = null;
	public bool $admin = false;
}

$schema = Expect::from(new Config);

$data = [
	'name' => 'Frank',
];

$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'Frank', 'password' => null, 'admin' => false}

Anonim sınıflar da desteklenir:

$schema = Expect::from(new class {
	public string $name;
	public ?string $password = null;
	public bool $admin = false;
});

Sınıf tanımından elde edilen bilgi yeterli olmayabileceğinden, öğeleri ikinci parametre kullanarak kendi şemanızla tamamlayabilirsiniz:

$schema = Expect::from(new Config, [
	'name' => Expect::string()->pattern('\w:.*'),
]);

Birden Çok Yapılandırmayı Birleştirme

Uygulamalar yapılandırmalarını sıklıkla katmanlar hâlinde kurar: yerleşik varsayılan değerler vardır ve onların üstüne kullanıcı, yalnızca gerçekten belirttiği öğeleri geçersiz kılması gereken kendi ayarlarını sağlar. processMultiple() tam da bunu yapar: birkaç veri kümesini alır, sonrakiler öncelikli olacak biçimde onları sırayla birleştirir ve nihai sonucu bir bütün olarak doğrular:

$schema = Expect::structure([
	'host' => Expect::string(),
	'port' => Expect::int(),
	'logging' => Expect::bool(),
]);

$defaults = ['host' => 'localhost', 'port' => 3306, 'logging' => false];
$userConfig = ['port' => 5432, 'logging' => true];

$config = $processor->processMultiple($schema, [$defaults, $userConfig]);
// $config = {'host' => 'localhost', 'port' => 5432, 'logging' => true}

host öğesi, kullanıcı onu ayarlamadığından varsayılan değerini korur; port ve logging ise sonraki veri kümesince üzerine yazılır. Dize anahtarlar altında saklanan değerler bu şekilde birleştirilir; sayısal indeksli öğeler (listeler) ise üzerine yazılmak yerine art arda eklenir.

Kaputun Altında: normalize, merge, complete

Her şema öğesi, ister yerleşik olsun ister kendi yazdığınız, veriyi nasıl ele aldığını birlikte tanımlayan dört metodu gerçekleştirir. Onlardan üçü işleme hattını oluşturur:

  1. normalize() – ham girdiyi hazırlar. before() kancaları burada çalışır ve örneğin bir nesne burada diziye dönüştürülür. İlk olarak, her veri kümesi üzerinde ayrı ayrı çalışır.
  2. merge() – normalleştirilmiş iki veri kümesini, sonraki öncelikli olacak biçimde birleştirir. Bu adımı yalnızca processMultiple() kullanır; process() onu atlar, çünkü elinde tek bir veri kümesi vardır.
  3. complete() – asıl doğrulamayı yapar, eksik öğeler için varsayılan değerleri doldurur ve assert(), transform() ile castTo() uygular. Birleştirilmiş sonuç üzerinde en son çalışır.

Dördüncü metot completeDefault(), girdide tümüyle eksik olan bir öğe için ana öğe tarafından çağrılır; ya varsayılan değeri sağlar ya da bir required() öğesinin eksik olduğunu bildirir.

Yani process(), normalize → complete çalıştırır; processMultiple() ise normalize (her veri kümesi) → merge → complete çalıştırır. before() metodunun ham girdiyi, transform() metodunun ise zaten doğrulanmış değeri görmesinin nedeni bu sıradır.

Özel Şema Öğeleri

assert(), transform() ve before() ile epey yol alabilirsiniz, dolayısıyla sıfırdan bir şey kurmanız nadiren gerekir. Ama kendi doğrulama ve birleştirme mantığı olan, yeniden kullanılabilir, kendi kendine yeten bir öğe istediğinizde, Nette\Schema\Schema arayüzünü gerçekleştirerek bir tane oluşturabilirsiniz. Onda tam olarak yukarıda anlatılan dört metot bulunur:

interface Schema
{
	function normalize(mixed $value, Context $context);
	function merge(mixed $value, mixed $base);
	function complete(mixed $value, Context $context);
	function completeDefault(Context $context);
}

Hatalar fırlatılmaz; onun yerine onları Context nesnesi aracılığıyla $context->addError() ile bildirir ve null döndürürsünüz. Processor tüm hataları toplar ve onları en sonda birlikte fırlatır.

Örnek olarak, bir enum'un backing değerini (örneğin 'hearts' dizesini) kabul eden ve enum örneğini döndüren, yeniden kullanılabilir bir öğe kuralım:

use Nette\Schema\Context;
use Nette\Schema\Schema;

class EnumSchema implements Schema
{
	public function __construct(
		private string $enumClass,
	) {
	}

	public function normalize(mixed $value, Context $context): mixed
	{
		return $value; // ön işleme gerekmiyor
	}

	public function merge(mixed $value, mixed $base): mixed
	{
		return $value ?? $base; // sonraki değer kazanır
	}

	public function complete(mixed $value, Context $context): mixed
	{
		$enum = is_string($value) ? ($this->enumClass)::tryFrom($value) : null;
		if ($enum === null) {
			$context->addError('The item %path% is not a valid value.', 'enum.value');
			return null;
		}

		return $enum;
	}

	public function completeDefault(Context $context): mixed
	{
		return null; // öğe girdide eksikken kullanılan değer
	}
}

Onu, yerleşik bir öğenin beklendiği her yerde kullanabilirsiniz: tek başına ya da daha büyük bir yapının parçası olarak:

enum Suit: string
{
	case Hearts = 'hearts';
	case Spades = 'spades';
}

$schema = Expect::structure([
	'suit' => new EnumSchema(Suit::class),
]);

$processor->process($schema, ['suit' => 'hearts']);
// OK, {'suit' => Suit::Hearts} döndürür

Öğe arayüzün tamamını gerçekleştirdiğinden, processMultiple() içinde de otomatik çalışır; Processor onun merge() metodunu tıpkı başka her öğede olduğu gibi çağırır.