-
-
Notifications
You must be signed in to change notification settings - Fork 282
/
@home.texy
500 lines (346 loc) · 15.9 KB
/
@home.texy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
Schema: Validação de dados
**************************
.[perex]
Uma biblioteca prática para validação e normalização de estruturas de dados contra um determinado esquema com uma API inteligente e fácil de entender.
Instalação:
```shell
composer require nette/schema
```
Utilização básica .[#toc-basic-usage]
-------------------------------------
Na variável `$schema` temos um esquema de validação (o que exatamente isto significa e como criá-lo diremos mais tarde) e na variável `$data` temos uma estrutura de dados que queremos validar e normalizar. Estes podem ser, por exemplo, dados enviados pelo usuário através de uma API, arquivo de configuração, etc.
A tarefa é tratada pela classe [api:Nette\Schema\Processor], que processa a entrada e ou retorna dados normalizados ou lança uma exceção [api:Nette\Schema\ValidationException] sobre erro.
```php
$processor = new Nette\Schema\Processor;
try {
$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
echo 'Data is invalid: ' . $e->getMessage();
}
```
O método `$e->getMessages()` retorna a matriz de todas as cadeias de mensagens e `$e->getMessageObjects()` retorna todas as mensagens como objetos "Nette\Schema\Message:https://api.nette.org/schema/master/Nette/Schema/Message.html ".
Definindo o esquema .[#toc-defining-schema]
-------------------------------------------
E agora vamos criar um esquema. A classe [api:Nette\Schema\Expect] é usada para defini-lo, na verdade definimos expectativas de como os dados devem ser. Digamos que os dados de entrada devem ser uma estrutura (por exemplo, uma matriz) contendo elementos `processRefund` do tipo bool e `refundAmount` do tipo int.
```php
use Nette\Schema\Expect;
$schema = Expect::structure([
'processRefund' => Expect::bool(),
'refundAmount' => Expect::int(),
]);
```
Acreditamos que a definição do esquema parece clara, mesmo que você a veja pela primeira vez.
Vamos enviar os seguintes dados para validação:
```php
$data = [
'processRefund' => true,
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // OK, ele passa
```
A saída, ou seja, o valor `$normalized`, é o objeto `stdClass`. Se quisermos que a saída seja uma matriz, adicionamos um elenco ao esquema `Expect::structure([...])->castTo('array')`.
Todos os elementos da estrutura são opcionais e têm um valor padrão `null`. Exemplo:
```php
$data = [
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // OK, ele passa
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
```
O fato de o valor padrão ser `null` não significa que ele seria aceito nos dados de entrada `'processRefund' => null`. Não, a entrada deve ser booleana, ou seja, apenas `true` ou `false`. Teríamos que permitir explicitamente `null` via `Expect::bool()->nullable()`.
Um item pode ser tornado obrigatório usando `Expect::bool()->required()`. Mudamos o valor padrão para `false` usando `Expect::bool()->default(false)` ou em breve usando `Expect::bool(false)`.
E se quiséssemos aceitar `1` and `0` além de booleanos? Então listamos os valores permitidos, que também normalizaremos para booleanos:
```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
```
Agora você conhece as bases de como o esquema é definido e como os elementos individuais da estrutura se comportam. Agora vamos mostrar o que todos os outros elementos podem ser usados na definição de um esquema.
Tipos de dados: tipo() .[#toc-data-types-type]
----------------------------------------------
Todos os tipos de dados padrão PHP podem ser listados no esquema:
```php
Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])
```
E depois todos os tipos [suportados pelos validadores |utils:validators#Expected Types] via `Expect::type('scalar')` ou abreviado `Expect::scalar()`. Também são aceitos nomes de classes ou interfaces, por exemplo `Expect::type('AddressEntity')`.
Você também pode usar a notação sindical:
```php
Expect::type('bool|string|array')
```
O valor padrão é sempre `null` exceto para `array` e `list`, onde é uma matriz vazia. (Uma lista é um array indexado em ordem ascendente de chaves numéricas a partir de zero, ou seja, um array não-associativo).
Conjunto de valores: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof]
------------------------------------------------------------------------------
A matriz é estrutura muito geral, é mais útil especificar exatamente quais elementos ela pode conter. Por exemplo, uma matriz cujos elementos só podem ser cordas:
```php
$schema = Expect::arrayOf('string');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string
```
O segundo parâmetro pode ser usado para especificar chaves (desde a versão 1.2):
```php
$schema = Expect::arrayOf('string', 'int');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int
```
A lista é uma matriz indexada:
```php
$schema = Expect::listOf('string');
$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string
$processor->process($schema, ['key' => 'a']); // ERROR: is not a list
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list
```
O parâmetro também pode ser um esquema, para que possamos escrever:
```php
Expect::arrayOf(Expect::bool())
```
O valor padrão é uma matriz vazia. Se você especificar o valor padrão, ele será fundido com os dados passados. Isto pode ser desabilitado usando `mergeDefaults(false)` (desde a versão 1.1).
Enumeração: anyOf() .[#toc-enumeration-anyof]
---------------------------------------------
`anyOf()` é um conjunto de valores ou esquemas que um valor pode ser. Eis como escrever um conjunto de elementos que podem ser `'a'`, `true`, ou `null`:
```php
$schema = Expect::listOf(
Expect::anyOf('a', true, null),
);
$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ERROR: false does not belong there
```
Os elementos de enumeração também podem ser esquemas:
```php
$schema = Expect::listOf(
Expect::anyOf(Expect::string(), true, null),
);
$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // ERROR
```
O método `anyOf()` aceita variantes como parâmetros individuais, não como array. Para passar-lhe uma matriz de valores, use o operador de desembalagem `anyOf(...$variants)`.
O valor padrão é `null`. Use o método `firstIsDefault()` para tornar o primeiro elemento o padrão:
```php
// o padrão é 'olá'.
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
```
Estruturas .[#toc-structures]
-----------------------------
As estruturas são objetos com chaves definidas. Cada uma destas chaves => pares de valores é chamada de "propriedade":
As estruturas aceitam matrizes e objetos e retornam objetos `stdClass` (a menos que você altere com `castTo('array')`, etc.).
Por padrão, todas as propriedades são opcionais e têm um valor padrão de `null`. Você pode definir propriedades obrigatórias usando `required()`:
```php
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // o valor padrão é nulo
]);
$processor->process($schema, ['optional' => '']);
// ERROR: falta a opção 'necessário'.
$processor->process($schema, ['required' => 'foo']);
// OK, retorna {'required' => 'foo', 'optional' => null}
```
Se você não quiser emitir propriedades com apenas um valor padrão, use `skipDefaults()`:
```php
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(),
])->skipDefaults();
$processor->process($schema, ['required' => 'foo']);
// OK, retorna {'necessário' => 'foo'})
```
Embora `null` seja o valor padrão da propriedade `optional`, ele não é permitido nos dados de entrada (o valor deve ser uma string). As propriedades que aceitam `null` são definidas usando `nullable()`:
```php
$schema = Expect::structure([
'optional' => Expect::string(),
'nullable' => Expect::string()->nullable(),
]);
$processor->process($schema, ['optional' => null]);
// ERROR: 'opcional' espera ser string, dado nulo.
$processor->process($schema, ['nullable' => null]);
// OK, retorna {'opcional' => nulo, 'anulável' => nulo}
```
Por padrão, não pode haver itens extras nos dados de entrada:
```php
$schema = Expect::structure([
'key' => Expect::string(),
]);
$processor->process($schema, ['additional' => 1]);
// ERRO: Item inesperado 'adicional'.
```
Que podemos mudar com `otherItems()`. Como parâmetro, especificaremos o esquema para cada elemento extra:
```php
$schema = Expect::structure([
'key' => Expect::string(),
])->otherItems(Expect::int());
$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // ERROR
```
Depreciações .[#toc-deprecations]
---------------------------------
Você pode depreciar os bens usando o `deprecated([string $message])` método. Os avisos de depreciação são devolvidos por `$processor->getWarnings()`:
```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"]
```
Faixas: min() max() .[#toc-ranges-min-max]
------------------------------------------
Use `min()` e `max()` para limitar o número de elementos para arrays:
```php
// matriz, pelo menos 10 itens, máximo 20 itens
Expect::array()->min(10)->max(20);
```
Para as cordas, limite seu comprimento:
```php
// string, com pelo menos 10 caracteres, máximo 20 caracteres
Expect::string()->min(10)->max(20);
```
Para os números, limite seu valor:
```php
// inteiro, entre 10 e 20 inclusive
Expect::int()->min(10)->max(20);
```
Naturalmente, é possível mencionar apenas `min()`, ou apenas `max()`:
```php
// string, máximo 20 caracteres
Expect::string()->max(20);
```
Expressões regulares: padrão() .[#toc-regular-expressions-pattern]
------------------------------------------------------------------
Usando `pattern()`, você pode especificar uma expressão regular que a string de entrada **whole** deve combinar (ou seja, como se estivesse embrulhada em caracteres `^` a `$`):
```php
// apenas 9 dígitos
Expect::string()->pattern('\d{9}');
```
Asserções personalizadas: assert() .[#toc-custom-assertions-assert]
-------------------------------------------------------------------
Você pode adicionar quaisquer outras restrições usando `assert(callable $fn)`.
```php
$countIsEven = fn($v) => count($v) % 2 === 0;
$schema = Expect::arrayOf('string')
->assert($countIsEven); // a contagem deve ser uniforme
$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 não é nem
```
Ou
```php
Expect::string()->assert('is_file'); // o arquivo deve existir
```
Você pode acrescentar sua própria descrição para cada asserção. Ela será parte da mensagem de erro.
```php
$schema = Expect::arrayOf('string')
->assert($countIsEven, 'Even items in array');
$processor->process($schema, ['a', 'b', 'c']);
// afirmação falhada "Itens pares na matriz" para item com matriz de valores.
```
O método pode ser chamado repetidamente para adicionar várias restrições. Ele pode ser misturado com chamadas para `transform()` e `castTo()`.
Transformação: transform() .[#toc-transformation-transform]
-----------------------------------------------------------
Os dados validados com sucesso podem ser modificados usando uma função personalizada:
```php
// conversion to uppercase:
Expect::string()->transform(fn(string $s) => strtoupper($s));
```
O método pode ser chamado repetidamente para adicionar várias transformações. Ele pode ser misturado com chamadas para `assert()` e `castTo()`. As operações serão executadas na ordem em que forem declaradas:
```php
Expect::type('string|int')
->castTo('string')
->assert('ctype_lower', 'All characters must be lowercased')
->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase
```
O método `transform()` pode transformar e validar o valor simultaneamente. Isso geralmente é mais simples e menos redundante do que encadear `transform()` e `assert()`. Para esse fim, a função recebe um objeto [Context |api:Nette\Schema\Context] com um método `addError()`, que pode ser usado para adicionar informações sobre problemas de validação:
```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);
});
```
Transmissão: castTo() .[#toc-casting-castto]
--------------------------------------------
Os dados validados com sucesso podem ser convertidos:
```php
Expect::scalar()->castTo('string');
```
Além dos tipos nativos do PHP, você também pode converter para classes. Ele distingue se é uma classe simples sem um construtor ou uma classe com um construtor. Se a classe não tiver um construtor, será criada uma instância dela e todos os elementos da estrutura serão gravados em suas propriedades:
```php
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
```
Se a classe tiver um construtor, os elementos da estrutura serão passados como parâmetros nomeados para o construtor:
```php
class Info
{
public function __construct(
public bool $processRefund,
public int $refundAmount,
) {
}
}
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
```
A conversão combinada com um parâmetro escalar cria um objeto e passa o valor como o único parâmetro para o construtor:
```php
Expect::string()->castTo(DateTime::class);
// creates new DateTime(...)
```
Normalização: before() .[#toc-normalization-before]
---------------------------------------------------
Antes da validação propriamente dita, os dados podem ser normalizados usando o método `before()`. Como exemplo, vamos ter um elemento que deve ser um conjunto de cordas (por exemplo `['a', 'b', 'c']`), mas recebe entrada sob a forma de um cordel `a b c`:
```php
$explode = fn($v) => explode(' ', $v);
$schema = Expect::arrayOf('string')
->before($explode);
$normalized = $processor->process($schema, 'a b c');
// OK, retorna ['a', 'b', 'c']
```
Mapeamento para objetos: from() .[#toc-mapping-to-objects-from]
---------------------------------------------------------------
Você pode gerar um esquema de estrutura a partir da classe. Exemplo:
```php
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}
```
Também há suporte para classes anônimas:
```php
$schema = Expect::from(new class {
public string $name;
public ?string $password;
public bool $admin = false;
});
```
Como as informações obtidas da definição da classe podem não ser suficientes, você pode adicionar um esquema personalizado para os elementos com o segundo parâmetro:
```php
$schema = Expect::from(new Config, [
'name' => Expect::string()->pattern('\w:.*'),
]);
```
{{leftbar: nette:@menu-topics}}