Schema: Veri Doğrulama

Akıllı ve anlaşılması kolay bir API ile veri yapılarının belirli bir şemaya göre doğrulanması ve normalleştirilmesi için pratik bir kütüphane.

Kurulum:

composer require nette/schema

Temel Kullanım

$schema değişkeninde bir doğrulama şemamız var (bunun tam olarak ne anlama geldiğini ve nasıl oluşturulacağını daha sonra anlatacağız) ve $data değişkeninde doğrulamak ve normalleştirmek istediğimiz bir veri yapımız var. Bu, örneğin, kullanıcı tarafından bir API, yapılandırma dosyası vb. aracılığıyla gönderilen veriler olabilir.

Görev, girdiyi işleyen ve normalleştirilmiş verileri döndüren ya da hata durumunda bir Nette\Schema\ValidationException istisnası atan Nette\Schema\Processor sınıfı tarafından gerçekleştirilir.

$processor = new Nette\Schema\Processor;

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

$e->getMessages() yöntemi tüm mesaj dizelerinin dizisini döndürür ve $e->getMessageObjects() tüm mesajları Nette\Schema\Message nesneleri olarak döndürür.

Şema Tanımlama

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

use Nette\Schema\Expect;

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

Şema tanımının ilk kez görseniz bile net göründüğüne inanıyoruz.

Doğrulama için aşağıdaki verileri gönderelim:

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

$normalized = $processor->process($schema, $data); // Tamam, geçiyor

Çıktı, yani $normalized değeri, stdClass nesnesidir. Çıktının bir dizi olmasını istiyorsak, şemaya bir döküm ekleriz Expect::structure([...])->castTo('array').

Yapının tüm elemanları isteğe bağlıdır ve null varsayılan değerine sahiptir. Örnek:

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

$normalized = $processor->process($schema, $data); // Tamam, geçiyor
// $normalized = {'processRefund' => null, 'refundAmount' => 17}

Varsayılan değerin null olması, 'processRefund' => null girdi verisinde kabul edileceği anlamına gelmez. Hayır, girdi boolean olmalıdır, yani yalnızca true veya false. null 'ye Expect::bool()->nullable() aracılığıyla açıkça izin vermemiz gerekir.

Bir öğe Expect::bool()->required() kullanılarak zorunlu hale getirilebilir. Varsayılan değeri Expect::bool()->default(false) kullanarak false olarak veya Expect::bool(false) kullanarak kısaca değiştiriyoruz.

Peki ya boolean dışında 1 and 0 adresini de kabul etmek istersek? O zaman izin verilen değerleri listeleriz ve bunları da boolean olarak normalleştiririz:

$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 şemanın nasıl tanımlandığına ve yapının tek tek öğelerinin nasıl davrandığına ilişkin temel bilgileri biliyorsunuz. Şimdi bir şemanın tanımlanmasında kullanılabilecek diğer tüm unsurların neler olduğunu göstereceğiz.

Veri Türleri: type()

Tüm standart PHP veri türleri şemada listelenebilir:

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

Ve daha sonra Doğrulayıcılar tarafından Expect::type('scalar') veya kısaltılmış Expect::scalar() aracılığıyla desteklenen tüm türler. Ayrıca sınıf veya arayüz adları da kabul edilir, örneğin Expect::type('AddressEntity').

Birlik gösterimini de kullanabilirsiniz:

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

Varsayılan değer, boş bir dizi olan array ve list dışında her zaman null şeklindedir. (Liste, sıfırdan itibaren sayısal anahtarların artan sırasına göre dizinlenmiş bir dizidir, yani ilişkisel olmayan bir dizidir).

Değerler Dizisi: arrayOf() listOf()

Dizi çok genel bir yapıdır, tam olarak hangi elemanları içerebileceğini belirtmek daha yararlıdır. Örneğin, elemanları yalnızca string olabilen bir dizi:

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

$processor->process($schema, ['hello', 'world']); // Tamam
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // Tamam
$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string

İkinci parametre anahtarları belirtmek için kullanılabilir (sürüm 1.2'den beri):

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

$processor->process($schema, ['hello', 'world']); // Tamam
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int

Liste, dizinlenmiş bir dizidir:

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

$processor->process($schema, ['a', 'b']); // Tamam
$processor->process($schema, ['a', 123]); // ERROR: 123 bir dize değil
$processor->process($schema, ['key' => 'a']); // ERROR: is not a list
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list

Parametre bir şema da olabilir, böylece yazabiliriz:

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

Varsayılan değer boş bir dizidir. Varsayılan değeri belirtirseniz, aktarılan verilerle birleştirilecektir. Bu, mergeDefaults(false) kullanılarak devre dışı bırakılabilir (sürüm 1.1'den beri).

Numaralandırma: anyOf()

anyOf() bir değerin olabileceği değerler veya şemalar kümesidir. Burada, 'a', true veya null olabilen elemanlardan oluşan bir dizinin nasıl yazılacağı gösterilmektedir:

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

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

Numaralandırma elemanları şemalar da olabilir:

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

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

anyOf() yöntemi varyantları dizi olarak değil, ayrı parametreler olarak kabul eder. Bir değer dizisi iletmek için, anyOf(...$variants) paket açma operatörünü kullanın.

Varsayılan değer null'dur. İlk öğeyi varsayılan yapmak için firstIsDefault() yöntemini kullanın:

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

Yapılar

Yapılar, tanımlanmış anahtarları olan nesnelerdir. Bu anahtar ⇒ değer çiftlerinin her biri “özellik” olarak adlandırılır:

Yapılar dizileri ve nesneleri kabul eder ve stdClass nesnelerini döndürür ( castTo('array'), vb. ile değiştirmediğiniz sürece).

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

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

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

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

Özelliklerin çıktısını yalnızca varsayılan bir değerle almak istemiyorsanız skipDefaults() adresini kullanın:

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

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

null, optional özelliğinin varsayılan değeri olmasına rağmen, girdi verilerinde buna izin verilmez (değer bir dize olmalıdır). null adresini kabul eden özellikler nullable() kullanılarak tanımlanır:

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

$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' string olması bekleniyor, null verildi.

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

Varsayılan olarak, giriş verilerinde fazladan öğe bulunamaz:

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

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

Bunu otherItems() ile değiştirebiliriz. Parametre olarak, her ekstra eleman için şema belirteceğiz:

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

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

Kullanımdan kaldırmalar

kullanarak özelliği kullanımdan kaldırabilirsiniz. deprecated([string $message]) yöntem. Kullanımdan kaldırma bildirimleri $processor->getWarnings() tarafından döndürülür:

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

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

Aralıklar: min() max()

Dizilerin öğe sayısını sınırlamak için min() ve max() adreslerini kullanın:

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

Dizeler için uzunluklarını sınırlayın:

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

Sayılar için değerlerini sınırlayın:

// tamsayı, 10 ile 20 arasında, dahil
Expect::int()->min(10)->max(20);

Elbette sadece min() veya sadece max() adreslerinden bahsetmek mümkündür:

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

Düzenli İfadeler: pattern()

pattern() adresini kullanarak, tüm girdi dizesinin eşleşmesi gereken bir düzenli ifade belirtebilirsiniz (yani, ^ a $ karakterlerine sarılmış gibi):

// sadece 9 hane
Expect::string()->pattern('\d{9}');

Özel İfadeler: assert()

assert(callable $fn) adresini kullanarak başka kısıtlamalar ekleyebilirsiniz.

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

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // sayı çift olmalıdır

$processor->process($schema, ['a', 'b']); // Tamam
$processor->process($schema, ['a', 'b', 'c']); // HATA: 3 eşit değil

Ya da

Expect::string()->assert('is_file'); // dosya mevcut olmalıdır

Her bir assertion için kendi açıklamanızı ekleyebilirsiniz. Hata mesajının bir parçası olacaktır.

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

$processor->process($schema, ['a', 'b', 'c']);
// array değerine sahip öğe için "Even items in array" iddiası başarısız oldu.

Yöntem, birden fazla kısıtlama eklemek için tekrar tekrar çağrılabilir. transform() ve castTo() çağrıları ile karıştırılabilir.

Dönüşüm: transform()

Başarıyla doğrulanan veriler özel bir işlev kullanılarak değiştirilebilir:

// conversion to uppercase:
Expect::string()->transform(fn(string $s) => strtoupper($s));

Yöntem, birden fazla dönüşüm eklemek için tekrar tekrar çağrılabilir. assert() ve castTo() çağrıları ile karıştırılabilir. İşlemler bildirildikleri sırayla yürütülecektir:

Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'All characters must be lowercased')
	->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase

transform() yöntemi değeri aynı anda hem dönüştürebilir hem de doğrulayabilir. Bu genellikle transform() ve assert() zincirlemesinden daha basit ve daha az gereksizdir. Bu amaçla, fonksiyon, doğrulama sorunları hakkında bilgi eklemek için kullanılabilecek bir addError() yöntemine 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öküm: castTo()

Başarıyla doğrulanan veriler dökülebilir:

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

Yerel PHP türlerine ek olarak, sınıflara da döküm yapabilirsiniz. Yapıcısı olmayan basit bir sınıf mı yoksa yapıcısı olan bir sınıf mı olduğu ayırt edilir. Sınıfın kurucusu yoksa, bir örneği oluşturulur ve yapının tüm elemanları özelliklerine yazılır:

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

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

// creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount

Sınıfın bir yapıcısı varsa, yapının elemanları yapıcıya adlandırılmış parametreler olarak geçirilir:

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

// creates $obj = new Info(processRefund: ..., refundAmount: ...)

Bir skaler parametre ile birlikte döküm bir nesne oluşturur ve değeri yapıcıya tek parametre olarak geçirir:

Expect::string()->castTo(DateTime::class);
// creates new DateTime(...)

Normalleştirme: before()

Doğrulama işleminden önce, veriler before() yöntemi kullanılarak normalleştirilebilir. Örnek olarak, dizelerden oluşan bir dizi olması gereken bir öğeye sahip olalım (örn. ['a', 'b', 'c']), ancak a b c dizesi biçiminde bir girdi alır:

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

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

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

Nesnelere Eşleme: from()

Sınıftan yapı şeması oluşturabilirsiniz. Örnek:

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

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

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

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

Anonim sınıflar da desteklenmektedir:

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

Sınıf tanımından elde edilen bilgiler yeterli olmayabileceğinden, ikinci parametre ile elemanlar için özel bir şema ekleyebilirsiniz:

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