Schema: Veri Doğrulama
Akıllı, anlaşılır bir API ile belirli bir şemaya göre veri yapılarının doğrulanması ve normalleştirilmesi için pratik bir kütüphane.
Kurulum:
composer require nette/schema
Temel Kullanım
$schema
değişkeninde doğrulama şemamız var (bunun tam olarak ne anlama geldiğini ve böyle bir şemanın
nasıl oluşturulacağını birazdan açıklayacağız) ve $data
değişkeninde doğrulamak ve normalleştirmek
istediğimiz veri yapısı var. Bu, örneğin bir API arayüzü aracılığıyla kullanıcı tarafından gönderilen veriler, bir
yapılandırma dosyası vb. olabilir.
Görevi, girişi işleyen ve ya normalleştirilmiş verileri döndüren ya da bir hata durumunda bir Nette\Schema\ValidationException istisnası fırlatan Nette\Schema\Processor sınıfı üstlenir.
$processor = new Nette\Schema\Processor;
try {
$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
echo 'Veriler geçerli değil: ' . $e->getMessage();
}
$e->getMessages()
metodu tüm mesajların dizisini dizeler olarak döndürür ve
$e->getMessageObjects()
tüm mesajları Nette\Schema\Message nesneleri olarak döndürür.
Şema Tanımlama
Ve şimdi şemayı oluşturalım. Tanımlamak için Nette\Schema\Expect sınıfı kullanılır, aslında
verilerin nasıl görünmesi gerektiğine dair beklentileri tanımlarız. Giriş verilerinin, processRefund
türünde
bool ve refundAmount
türünde int öğeleri içeren bir yapı (örneğin bir dizi) oluşturması gerektiğini
varsayalım.
use Nette\Schema\Expect;
$schema = Expect::structure([
'processRefund' => Expect::bool(),
'refundAmount' => Expect::int(),
]);
Şema tanımının, ilk kez görseniz bile anlaşılır 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); // OK, doğrulamadan geçer
Çıktı, yani $normalized
değeri, bir stdClass
nesnesidir. Çıktının bir dizi olmasını
isteseydik, şemayı Expect::structure([...])->castTo('array')
tür dönüşümüyle tamamlardık.
Yapının tüm öğeleri isteğe bağlıdır ve varsayılan değeri null
'dur. Örnek:
$data = [
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // OK, doğrulamadan geçer
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
Varsayılan değerin null
olması, giriş verilerinde 'processRefund' => null
'ın kabul edileceği
anlamına gelmez. Hayır, giriş bir boolean olmalıdır, yani yalnızca true
veya false
.
null
'a izin vermek için bunu Expect::bool()->nullable()
ile açıkça belirtmemiz gerekirdi.
Bir öğeyi Expect::bool()->required()
ile zorunlu kılabiliriz. Varsayılan değeri örneğin
Expect::bool()->default(false)
ile false
olarak veya kısaca Expect::bool(false)
ile
değiştirebiliriz.
Peki ya boolean'a ek olarak 1
ve 0
'ı da kabul etmek isteseydik? O zaman ayrıca boolean'a
normalleştirilmesini sağlayacağımız değerlerin bir listesini belirtiriz:
$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ığının ve yapının bireysel öğelerinin nasıl davrandığının temellerini biliyorsunuz. Şimdi şema tanımlarken kullanılabilecek diğer tüm öğeleri 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 = [])
Ve ayrıca Validators sınıfı tarafından desteklenen
tüm türler, örneğin Expect::type('scalar')
veya kısaca Expect::scalar()
. Ayrıca sınıf veya
arayüz adları, örneğin Expect::type('AddressEntity')
.
Union gösterimi de kullanılabilir:
Expect::type('bool|string|array')
Varsayılan değer, boş bir dizi olduğu array
ve list
hariç her zaman null
'dur.
(Liste, sıfırdan başlayarak artan bir dizi sayısal anahtara göre indekslenmiş bir dizidir, yani ilişkisel olmayan
bir dizi).
Değer Dizileri: arrayOf() listOf()
Dizi çok genel bir yapıdır, tam olarak hangi öğeleri içerebileceğini belirtmek daha kullanışlıdır. Örneğin, öğeleri yalnızca dizeler 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 ile anahtarlar belirtilebilir (sürüm 1.2'den itibaren):
$schema = Expect::arrayOf('string', 'int');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // HATA: 'a' bir int değil
Liste, indekslenmiş 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: ayrıca liste değil
Parametre bir şema da olabilir, bu yüzden yazabiliriz:
Expect::arrayOf(Expect::bool())
Varsayılan değer boş bir dizidir. Bir varsayılan değer belirtirseniz, iletilen verilerle birleştirilecektir. Bu,
mergeDefaults(false)
ile devre dışı bırakılabilir (sürüm 1.1'den itibaren).
Enum: anyOf()
anyOf()
, bir değerin alabileceği değerlerin veya şemaların bir listesini temsil eder. Bu şekilde, öğeleri
'a'
, true
veya null
olabilen bir öğe dizisi yazarı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
Enum öğeleri şemalar 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, varyantları bir dizi olarak değil, bireysel parametreler olarak kabul eder. Ona bir değer
dizisi iletmek istiyorsanız, unpacking operatörünü anyOf(...$variants)
kullanın.
Varsayılan değer null
'dur. firstIsDefault()
metoduyla ilk öğeyi varsayılan yaparız:
// varsayılan 'hello'dur
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
Yapılar
Yapılar, tanımlanmış anahtarlara sahip nesnelerdir. Her anahtar ⇒ değer çifti “özellik” olarak adlandırılır:
Yapılar dizileri ve nesneleri kabul eder ve stdClass
nesneleri döndürür.
Varsayılan olarak, tüm özellikler isteğe bağlıdır ve varsayılan değeri null
'dur. Zorunlu özellikleri
required()
ile tanımlayabilirsiniz:
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // varsayılan değer null'dur
]);
$processor->process($schema, ['optional' => '']);
// HATA: 'required' seçeneği eksik
$processor->process($schema, ['required' => 'foo']);
// OK, {'required' => 'foo', 'optional' => null} döndürür
Çıktıda varsayılan değere sahip özellikleri 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
null
, optional
özelliğinin varsayılan değeri olmasına rağmen, giriş verilerinde izin verilmez
(değer bir dize olmalıdır). null
kabul eden özellikleri nullable()
ile tanımlarız:
$schema = Expect::structure([
'optional' => Expect::string(),
'nullable' => Expect::string()->nullable(),
]);
$processor->process($schema, ['optional' => null]);
// HATA: 'optional' dize olması bekleniyor, null verildi.
$processor->process($schema, ['nullable' => null]);
// OK, {'optional' => null, 'nullable' => null} döndürür
Yapının tüm özelliklerinin dizisini getShape()
metodu döndürür.
Varsayılan olarak, giriş verilerinde ekstra öğe olamaz:
$schema = Expect::structure([
'key' => Expect::string(),
]);
$processor->process($schema, ['additional' => 1]);
// HATA: Beklenmeyen öğe 'additional'
Bunu otherItems()
ile değiştirebiliriz. Parametre olarak, ekstra öğelerin doğrulanacağı şemayı
belirtiriz:
$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ıdan türeterek yeni bir yapı oluşturabilirsiniz:
$dog = Expect::structure([
'name' => Expect::string(),
'age' => Expect::int(),
]);
$dogWithBreed = $dog->extend([
'breed' => Expect::string(),
]);
Diziler
Tanımlanmış anahtarlara sahip 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'dur
]);
Tuple olarak bilinen indekslenmiş bir dizi de tanımlanabilir:
$schema = Expect::array([
Expect::int(),
Expect::string(),
Expect::bool(),
]);
$processor->process($schema, [1, 'hello', true]); // OK
Kullanımdan Kaldırılmış Özellikler
Bir özelliği deprecated([string $message])
metoduyla kullanımdan kaldırılmış olarak işaretleyebilirsiniz.
Destek sonu bilgileri $processor->getWarnings()
ile döndürülür:
$schema = Expect::structure([
'old' => Expect::int()->deprecated('Öğe %path% kullanımdan kaldırılmıştır'),
]);
$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["Öğe 'old' kullanımdan kaldırılmıştır"]
Aralıklar: min() max()
min()
ve max()
kullanarak dizilerdeki öğe sayısını sınırlayabiliriz:
// dizi, en az 10 öğe, en fazla 20 öğe
Expect::array()->min(10)->max(20);
Dizeler için uzunluklarını sınırlayabiliriz:
// 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ırlayabiliriz:
// tam sayı, 10 ile 20 arasında (dahil)
Expect::int()->min(10)->max(20);
Elbette, yalnızca min()
veya yalnızca max()
belirtmek mümkündür:
// en fazla 20 karakterlik dize
Expect::string()->max(20);
Düzenli İfadeler: pattern()
pattern()
kullanarak, tüm giriş dizesinin eşleşmesi gereken bir düzenli ifade belirtebiliriz (yani,
^
ve $
karakterleriyle sarılmış gibi):
// tam olarak 9 rakam
Expect::string()->pattern('\d{9}');
Özel Kısıtlamalar: assert()
Diğer tüm kısıtlamaları assert(callable $fn)
kullanarak belirtebiliriz.
$countIsEven = fn($v) => count($v) % 2 === 0;
$schema = Expect::arrayOf('string')
->assert($countIsEven); // sayı çift olmalıdır
$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // HATA: 3 çift sayı değil
Veya
Expect::string()->assert('is_file'); // dosya mevcut olmalıdır
Her kısıtlamaya kendi açıklamanızı ekleyebilirsiniz. Bu, hata mesajının bir parçası olacaktır.
$schema = Expect::arrayOf('string')
->assert($countIsEven, 'Dizideki çift öğeler');
$processor->process($schema, ['a', 'b', 'c']);
// Değeri dizi olan öğe için "Dizideki çift öğeler" iddiası başarısız oldu.
Metot tekrar tekrar çağrılarak daha fazla kısıtlama eklenebilir. transform()
ve castTo()
çağrılarıyla serpiştirilebilir.
Dönüşümler: transform()
Başarıyla doğrulanmış veriler özel bir fonksiyon kullanılarak değiştirilebilir:
// büyük harfe dönüştürme:
Expect::string()->transform(fn(string $s) => strtoupper($s));
Metot tekrar tekrar çağrılarak daha fazla dönüşüm eklenebilir. assert()
ve castTo()
çağrılarıyla serpiştirilebilir. İşlemler, bildirildikleri sırayla gerçekleştirilir:
Expect::type('string|int')
->castTo('string')
->assert('ctype_lower', 'Tüm karakterler küçük harf olmalıdır')
->transform(fn(string $s) => strtoupper($s)); // büyük harfe dönüştürme
transform()
metodu aynı anda değeri hem dönüştürebilir hem de doğrulayabilir. Bu genellikle
transform()
ve assert()
zincirlemesinden daha basit ve daha az tekrarlıdır. Bu amaçla, fonksiyon,
doğrulama sorunları hakkında bilgi eklemek için kullanılabilecek addError()
metoduna sahip bir Context nesnesi alır:
Expect::string()
->transform(function (string $s, Nette\Schema\Context $context) {
if (!ctype_lower($s)) {
$context->addError('Tüm karakterler küçük harf olmalıdır', 'my.case.error');
return null;
}
return strtoupper($s);
});
Tür Dönüştürme: castTo()
Başarıyla doğrulanmış veriler tür dönüştürülebilir:
Expect::scalar()->castTo('string');
Yerel PHP türlerine ek olarak, sınıflara da tür dönüştürme yapılabilir. Bu, basit bir yapıcı olmayan sınıf mı yoksa yapıcı olan bir sınıf mı olduğuna bağlıdır. Sınıfın bir yapıcısı yoksa, örneği oluşturulur ve yapının tüm öğ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 ve $obj->refundAmount'a yazar
Sınıfın bir yapıcısı varsa, yapının öğeleri yapıcıya adlandırılmış parametreler olarak iletilir:
class Info
{
public function __construct(
public bool $processRefund,
public int $refundAmount,
) {
}
}
// $obj = new Info(processRefund: ..., refundAmount: ...) oluşturur
Skaler bir parametre ile birleştirilmiş tür dönüştürme, bir nesne oluşturur ve değeri yapıcıya tek bir parametre olarak iletir:
Expect::string()->castTo(DateTime::class);
// new DateTime(...) oluşturur
Normalizasyon: before()
Doğrulamadan önce veriler before()
metodu kullanılarak normalleştirilebilir. Örnek olarak, dize dizisi
olması gereken (örneğin ['a', 'b', 'c']
) ancak a b c
dizesi biçiminde girdi 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 oluşturabiliriz. Örnek:
class Config
{
public string $name;
public string|null $password;
public bool $admin = false;
}
$schema = Expect::from(new Config);
$data = [
'name' => 'franta',
];
$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false}
Anonim sınıflar da desteklenir:
$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 öğelere kendi şemanızı ekleyebilirsiniz:
$schema = Expect::from(new Config, [
'name' => Expect::string()->pattern('\w:.*'),
]);