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 nesneleri döndürür stdClass
.
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
Tüm yapı özelliklerinin dizisi getShape()
yöntemi tarafından döndürülü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
extend()
adresini 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(),
]);
Dizi
Tanımlanmış anahtarları olan bir dizi. Yapılar için geçerli olan kuralların aynısı geçerlidir.
$schema = Expect::array([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // default value is null
]);
Ayrıca tuple olarak bilinen indeksli bir dizi de tanımlayabilirsiniz:
$schema = Expect::array([
Expect::int(),
Expect::string(),
Expect::bool(),
]);
$processor->process($schema, [1, 'hello', true]); // OK
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:.*'),
]);