mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-10-31 10:39:59 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			830 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			830 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| Walidacja danych wejściowych
 | |
| ============================
 | |
| 
 | |
| Jedna z głównych zasad mówi, że nigdy nie należy ufać danym otrzymanym od użytkownika oraz że zawsze należy walidować je przed użyciem.
 | |
| 
 | |
| Rozważmy [model](structure-models.md) wypełniony danymi pobranymi od użytkownika. Możemy zweryfikować je poprzez wywołanie metody [[yii\base\Model::validate()|validate()]].
 | |
| Metoda zwróci wartość `boolean` wskazującą, czy walidacja się powiodła, czy też nie. Jeśli nie, można pobrać informacje o błędach za pomocą właściwości
 | |
| [[yii\base\Model::errors|errors]].
 | |
| Dla przykładu,
 | |
| 
 | |
| ```php
 | |
| $model = new \app\models\ContactForm();
 | |
| 
 | |
| // uzupełniamy model danymi od użytkownika
 | |
| $model->load(\Yii::$app->request->post());
 | |
| // ten zapis jest tożsamy z poniższą metodą
 | |
| // $model->attributes = \Yii::$app->request->post('ContactForm');
 | |
| 
 | |
| if ($model->validate()) {
 | |
|     // akcja w przypadku poprawnej walidacji
 | |
| } else {
 | |
|     // akcja w przypadku niepoprawnej walidacji. Zmienna $errors jest tablicą zawierającą wiadomości błędów
 | |
|     $errors = $model->errors;
 | |
| }
 | |
| ```
 | |
| 
 | |
| 
 | |
| ## Deklaracja zasad <span id="declaring-rules"></span>
 | |
| 
 | |
| Aby metoda [[yii\base\Model::validate()|validate()]] naprawdę zadziałała, należy zdefiniować zasady walidacji dla atrybutów, które mają jej podlegać.
 | |
| Powinno zostać to zrobione przez nadpisanie metody [[yii\base\Model::rules()|rules()]]. Poniższy przykład pokazuje jak zostały zadeklarowane zasady walidacji dla modelu
 | |
| `ContactForm`:
 | |
| 
 | |
| ```php
 | |
| public function rules()
 | |
| {
 | |
|     return [
 | |
|         // atrybuty name, email, subject oraz body są wymagane
 | |
|         [['name', 'email', 'subject', 'body'], 'required'],
 | |
| 
 | |
|         // atrybut email powinien być poprawnym adresem email
 | |
|         ['email', 'email'],
 | |
|     ];
 | |
| }
 | |
| ```
 | |
| 
 | |
| Metoda [[yii\base\Model::rules()|rules()]] powinna zwracać tablicę zasad, gdzie każda zasada jest również tablicą o następującym formacie:
 | |
| 
 | |
| ```php
 | |
| [
 | |
|     // wymagane, określa atrybut który powinien zostać zwalidowany przez tę zasadę.
 | |
|     // Dla pojedyńczego atrybutu możemy użyć bezpośrednio jego nazwy, bez osadzania go w tablicy
 | |
|     ['attribute1', 'attribute2', ...],
 | |
| 
 | |
|     // wymagane, określa rodzaj walidacji
 | |
|     // Może to być nazwa klasy, alias walidatora lub nazwa metody walidacji
 | |
|     'validator',
 | |
| 
 | |
|     // opcjonalne, określa, w którym scenariuszu/scenariuszach ta zasada powinna zostać użyta
 | |
|     // w przypadku nie podania żadnego argumentu zasada zostanie zaaplikowana do wszystkich scenariuszy
 | |
|     // Możesz również skonfigurować opcję "except", jeśli chcesz użyć tej zasady dla wszystkich scenariuszy, z wyjątkiem wymienionych
 | |
|     'on' => ['scenario1', 'scenario2', ...],
 | |
| 
 | |
|     // opcjonalne, określa dodatkową konfigurację dla obiektu walidatora
 | |
|     'property1' => 'value1', 'property2' => 'value2', ...
 | |
| ]
 | |
| ```
 | |
| 
 | |
| Dla każdej z zasad musisz określić co najmniej jeden atrybut, którego ma ona dotyczyć, oraz określić rodzaj zasady jako
 | |
| jedną z następujących form:
 | |
| 
 | |
| * alias walidatora podstawowego, np. `required`, `in`, `date` itd. Zajrzyj do sekcji [Podstawowe walidatory](tutorial-core-validators.md),
 | |
|   aby uzyskać pełną listę walidatorów podstawowych.
 | |
| * nazwa metody walidacji w klasie modelu lub funkcja anonimowa. Po więcej szczegółów zajrzyj do sekcji [Walidatory wbudowane](#inline-validators).
 | |
| * pełna nazwa klasy walidatora. Po więcej szczegółów zajrzyj do sekcji [Walidatory niezależne](#standalone-validators).
 | |
| 
 | |
| Zasada może zostać użyta do walidacji jednego lub wielu atrybutów, a atrybut może być walidowany przez jedną lub wiele zasad.
 | |
| Zasada może zostać użyta dla konkretnych [scenariuszy](structure-models.md#scenarios) przez dodanie opcji `on`.
 | |
| Jeśli nie dodasz opcji `on` oznacza to, że zasada zostanie użyta w każdym scenariuszu.
 | |
| 
 | |
| Wywołanie metody [[yii\base\Model::validate()|validate()]] powoduje podjęcie następujących kroków w celu wykonania walidacji:
 | |
| 
 | |
| 1. Określenie, które atrybuty powinny zostać zweryfikowane poprzez pobranie ich listy z metody [[yii\base\Model::scenarios()|scenarios()]], używając aktualnego
 | |
|    [[yii\base\Model::scenario|scenariusza]]. Wybrane atrybuty nazywane są *atrybutami aktywnymi*.
 | |
| 2. Określenie, które zasady walidacji powinny zostać użyte przez pobranie ich listy z metody [[yii\base\Model::rules()|rules()]], używając aktualnego
 | |
|    [[yii\base\Model::scenario|scenariusza]]. Wybrane zasady nazywane są *zasadami aktywnymi*.
 | |
| 3. Użycie każdej aktywnej zasady do walidacji każdego aktywnego atrybutu, który jest powiązany z konkretną zasadą. Zasady walidacji są wykonywane w kolejności,
 | |
|    w jakiej zostały zapisane.
 | |
| 
 | |
| Odnosząc się do powyższych kroków, atrybut zostanie zwalidowany wtedy i tylko wtedy, gdy jest on aktywnym atrybutem zadeklarowanym w
 | |
| [[yii\base\Model::scenarios()|scenarios()]] oraz jest powiązany z jedną lub wieloma aktywnymi zasadami zadeklarowanymi w [[yii\base\Model::rules()|rules()]].
 | |
| 
 | |
| > Note: Czasem użyteczne jest nadanie nazwy zasadzie np.
 | |
| >
 | |
| > ```php
 | |
| > public function rules()
 | |
| > {
 | |
| >     return [
 | |
| >         // ...
 | |
| >         'password' => [['password'], 'string', 'max' => 60],
 | |
| >     ];
 | |
| > }
 | |
| > ```
 | |
| >
 | |
| > W modelu potomnym można to wykorzystać:
 | |
| >
 | |
| > ```php
 | |
| > public function rules()
 | |
| > {
 | |
| >     $rules = parent::rules();
 | |
| >     unset($rules['password']);
 | |
| >     return $rules;
 | |
| > }
 | |
| 
 | |
| 
 | |
| ### Dostosowywanie wiadomości błedów <span id="customizing-error-messages"></span>
 | |
| 
 | |
| Większość walidatorów posiada domyślne wiadomości błędów, które zostają dodane do poddanego walidacji modelu, kiedy któryś z atrybutów nie przejdzie pomyślnie walidacji.
 | |
| Dla przykładu, walidator [[yii\validators\RequiredValidator|required]] dodaje komunikat "Username cannot be blank.", kiedy atrybut `username` nie przejdzie walidacji tej zasady.
 | |
| 
 | |
| Możesz dostosować wiadomość błędu zasady przez określenie właściwości `message` podczas jej deklaracji.
 | |
| Dla przykładu,
 | |
| 
 | |
| ```php
 | |
| public function rules()
 | |
| {
 | |
|     return [
 | |
|         ['username', 'required', 'message' => 'Proszę wybrać login.'],
 | |
|     ];
 | |
| }
 | |
| ```
 | |
| 
 | |
| Niektóre walidatory mogą wspierać dodatkowe wiadomości błedów, aby bardziej precyzyjnie określić problemy powstałe przy walidacji.
 | |
| Dla przykładu, walidator [[yii\validators\NumberValidator|number]] dodaje [[yii\validators\NumberValidator::tooBig|tooBig]] oraz
 | |
| [[yii\validators\NumberValidator::tooSmall|tooSmall]] do opisania sytuacji, kiedy poddawana walidacji liczba jest za duża lub za mała.
 | |
| Możesz skonfigurować te wiadomości tak, jak pozostałe właściwości walidatorów podczas deklaracji zasady.
 | |
| 
 | |
| 
 | |
| ### Zdarzenia walidacji <span id="validation-events"></span>
 | |
| 
 | |
| Podczas wywołania metody [[yii\base\Model::validate()|validate()]] zostaną wywołane dwie metody, które możesz nadpisać, aby dostosować proces walidacji:
 | |
| 
 | |
| * [[yii\base\Model::beforeValidate()|beforeValidate()]]: domyślna implementacja wywoła zdarzenie [[yii\base\Model::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]].
 | |
|   Możesz nadpisać tę metodę lub odnieść się do zdarzenia, aby wykonać dodatkowe operacje przed walidacją.
 | |
|   Metoda powinna zwracać wartość `boolean` wskazującą, czy walidacja powinna zostać przeprowadzona, czy też nie.
 | |
| * [[yii\base\Model::afterValidate()|afterValidate()]]: domyślna implementacja wywoła zdarzenie [[yii\base\Model::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]].
 | |
|   Możesz nadpisać tę metodę lub odnieść się do zdarzenia, aby wykonać dodatkowe operacje po zakończonej walidacji.
 | |
| 
 | |
| 
 | |
| ### Walidacja warunkowa <span id="conditional-validation"></span>
 | |
| 
 | |
| Aby zwalidować atrybuty tylko wtedy, gdy zostaną spełnione pewne założenia, np. walidacja jednego atrybutu zależy od wartości drugiego atrybutu, możesz użyć właściwości
 | |
|  [[yii\validators\Validator::when|when]], aby zdefiniować taki warunek. Dla przykładu,
 | |
| 
 | |
| ```php
 | |
| [
 | |
|     ['state', 'required', 'when' => function($model) {
 | |
|         return $model->country == 'USA';
 | |
|     }],
 | |
| ]
 | |
| ```
 | |
| 
 | |
| Właściwość [[yii\validators\Validator::when|when]] pobiera możliwą do wywołania funkcję PHP z następującą definicją:
 | |
| 
 | |
| ```php
 | |
| /**
 | |
|  * @param Model $model model, który podlega walidacji
 | |
|  * @param string $attribute atrybut, który podlega walidacji
 | |
|  * @return bool wartość zwrotna; czy reguła powinna zostać zastosowana
 | |
|  */
 | |
| function ($model, $attribute)
 | |
| ```
 | |
| 
 | |
| Jeśli potrzebujesz również wsparcia walidacji warunkowej po stronie użytkownika, powinieneś skonfigurować właściwość [[yii\validators\Validator::whenClient|whenClient]],
 | |
| która przyjmuje wartość `string` reprezentującą funkcję JavaScript, zwracającą wartość `boolean`, która będzie określała, czy zasada powinna zostać zastosowana, czy nie.
 | |
| Dla przykładu,
 | |
| 
 | |
| ```php
 | |
| [
 | |
|     ['state', 'required', 'when' => function ($model) {
 | |
|         return $model->country == 'USA';
 | |
|     }, 'whenClient' => "function (attribute, value) {
 | |
|         return $('#country').val() == 'USA';
 | |
|     }"],
 | |
| ]
 | |
| ```
 | |
| 
 | |
| 
 | |
| ### Filtrowanie danych <span id="data-filtering"></span>
 | |
| 
 | |
| Dane od użytkownika często muszą zostać przefiltrowane. Dla przykładu, możesz chcieć wyciąć znaki spacji na początku i na końcu pola `username`.
 | |
| Aby osiągnąć ten cel, możesz również użyć zasad walidacji.
 | |
| 
 | |
| Poniższy przykład pokazuje, jak wyciąć znaki spacji z pola oraz zmienić puste pole na wartość `null` przy użyciu podstawowych walidatorów
 | |
| [trim](tutorial-core-validators.md#trim) oraz [default](tutorial-core-validators.md#default):
 | |
| 
 | |
| ```php
 | |
| [
 | |
|     [['username', 'email'], 'trim'],
 | |
|     [['username', 'email'], 'default'],
 | |
| ]
 | |
| ```
 | |
| 
 | |
| Możesz użyć również bardziej ogólnego walidatora [filter](tutorial-core-validators.md#filter), aby przeprowadzić złożone filtrowanie.
 | |
| 
 | |
| Jak pewnie zauważyłeś, te zasady walidacji tak naprawdę nie walidują danych. Zamiast tego przetwarzają wartości, a następnie przypisują je do atrybutów,
 | |
| które zostały poddane walidacji.
 | |
| 
 | |
| 
 | |
| ### Obsługa pustych danych wejściowych <span id="handling-empty-inputs"></span>
 | |
| 
 | |
| Kiedy dane wejściowe są wysłane przez formularz HTML, często zachodzi potrzeba przypisania im domyślnych wartości, jeśli są puste.
 | |
| Możesz to osiągnąć przez użycie walidatora [default](tutorial-core-validators.md#default). Dla przykładu,
 | |
| 
 | |
| ```php
 | |
| [
 | |
|     // ustawia atrybuty "username" oraz "email" jako `null` jeśli są puste
 | |
|     [['username', 'email'], 'default'],
 | |
| 
 | |
|     // ustawia atrybut "level" równy "1", jeśli jest pusty
 | |
|     ['level', 'default', 'value' => 1],
 | |
| ]
 | |
| ```
 | |
| 
 | |
| Domyślnie pole uważane jest za puste, jeśli jego wartość to pusty łańcuch znaków, pusta tablica lub `null`.
 | |
| Możesz dostosować domyślną logikę wykrywania pustych pól przez skonfigurowanie parametru [[yii\validators\Validator::isEmpty|isEmpty]], przekazując mu funkcję PHP.
 | |
| Dla przykładu,
 | |
| 
 | |
| ```php
 | |
| [
 | |
|     ['agree', 'required', 'isEmpty' => function ($value) {
 | |
|         return empty($value);
 | |
|     }],
 | |
| ]
 | |
| ```
 | |
| 
 | |
| > Note: Większość walidatorów nie obsługuje pustych pól, jeśli ich właściwość [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] przyjmuje domyślnie wartość `true`.
 | |
| > Zostaną one po prostu pominięte podczas walidacji, jeśli ich powiązany atrybut otrzyma wartość uznawaną za pustą.
 | |
| > Wśród [podstawowych walidatorów](tutorial-core-validators.md), tylko walidatory `captcha`, `default`, `filter`, `required` oraz `trim` obsługują puste pola.
 | |
| 
 | |
| ## Walidacja "Ad Hoc" <span id="ad-hoc-validation"></span>
 | |
| 
 | |
| Czasami potrzebna będzie walidacja *ad hoc* dla wartości które nie są powiązane z żadnym modelem.
 | |
| 
 | |
| Jeśli potrzebujesz wykonać tylko jeden typ walidacji (np. walidację adresu email), możesz wywołać metodę
 | |
| [[yii\validators\Validator::validate()|validate()]] wybranego walidatora, tak jak poniżej:
 | |
| 
 | |
| ```php
 | |
| $email = 'test@example.com';
 | |
| $validator = new yii\validators\EmailValidator();
 | |
| 
 | |
| if ($validator->validate($email, $error)) {
 | |
|     echo 'Email is valid.';
 | |
| } else {
 | |
|     echo $error;
 | |
| }
 | |
| ```
 | |
| 
 | |
| > Note: Nie każdy walidator wspiera tego typu walidację. Dla przykładu, podstawowy walidator [unique](tutorial-core-validators.md#unique)
 | |
| > został zaprojektowany do pracy wyłącznie z modelami.
 | |
| 
 | |
| Jeśli potrzebujesz przeprowadzić wielokrotne walidacje, możesz użyć modelu [[yii\base\DynamicModel|DynamicModel]],
 | |
| który wspiera deklarację atrybutów oraz zasad walidacji "w locie".
 | |
| Dla przykładu,
 | |
| 
 | |
| ```php
 | |
| public function actionSearch($name, $email)
 | |
| {
 | |
|     $model = DynamicModel::validateData(compact('name', 'email'), [
 | |
|         [['name', 'email'], 'string', 'max' => 128],
 | |
|         ['email', 'email'],
 | |
|     ]);
 | |
| 
 | |
|     if ($model->hasErrors()) {
 | |
|         // validation fails
 | |
|     } else {
 | |
|         // validation succeeds
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Metoda [[yii\base\DynamicModel::validateData()|validateData()]] tworzy instancję `DynamicModel`, definiuje atrybuty używając przekazanych danych
 | |
| (`name` oraz `email` w tym przykładzie), a następnie wywołuje metodę [[yii\base\Model::validate()|validate()]] z podanymi zasadami walidacji.
 | |
| 
 | |
| Alternatywnie, możesz użyć bardziej "klasycznego" zapisu to przeprowadzenia tego typu walidacji:
 | |
| 
 | |
| ```php
 | |
| public function actionSearch($name, $email)
 | |
| {
 | |
|     $model = new DynamicModel(compact('name', 'email'));
 | |
|     $model->addRule(['name', 'email'], 'string', ['max' => 128])
 | |
|         ->addRule('email', 'email')
 | |
|         ->validate();
 | |
| 
 | |
|     if ($model->hasErrors()) {
 | |
|         // validation fails
 | |
|     } else {
 | |
|         // validation succeeds
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Po walidacji możesz sprawdzić, czy przebiegła ona poprawnie, poprzez wywołanie metody [[yii\base\DynamicModel::hasErrors()|hasErrors()]],
 | |
| a następnie pobrać błędy walidacji z właściwości [[yii\base\DynamicModel::errors|errors]], tak jak w przypadku zwykłego modelu.
 | |
| Możesz również uzyskać dostęp do dynamicznych atrybutów tej instancji, np. `$model->name` i `$model->email`.
 | |
| 
 | |
| 
 | |
| ## Tworzenie walidatorów <span id="creating-validators"></span>
 | |
| 
 | |
| Oprócz używania [podstawowych walidatorów](tutorial-core-validators.md) dołączonych do wydania Yii, możesz dodatkowo utworzyć własne; wbudowane lub niezależne.
 | |
| 
 | |
| ### Walidatory wbudowane <span id="inline-validators"></span>
 | |
| 
 | |
| Wbudowany walidator jest zdefiniowaną w modelu metodą lub funkcją anonimową. Jej definicja jest następująca:
 | |
| 
 | |
| ```php
 | |
| /**
 | |
|  * @param string $attribute atrybut podlegający walidacji
 | |
|  * @param mixed $params wartość parametru podanego w zasadzie walidacji
 | |
|  * @param \yii\validators\InlineValidator $validator powiązana instancja InlineValidator
 | |
|  * Ten parametr jest dostępny od wersji 2.0.11.
 | |
|  * @param mixed $current aktualnie walidowana wartość atrybutu.
 | |
|  * Ten parametr jest dostępny od wersji 2.0.36.
 | |
|  */
 | |
| function ($attribute, $params, $validator, $current)
 | |
| ```
 | |
| 
 | |
| Jeśli atrybut nie przejdzie walidacji, metoda/funkcja powinna wywołać metodę [[yii\base\Model::addError()|addError()]] do zapisania wiadomości o błędzie w modelu,
 | |
| która może zostać później pobrana i zaprezentowana użytkownikowi.
 | |
| 
 | |
| Poniżej znajduje się kilka przykładów:
 | |
| 
 | |
| ```php
 | |
| use yii\base\Model;
 | |
| 
 | |
| class MyForm extends Model
 | |
| {
 | |
|     public $country;
 | |
|     public $token;
 | |
| 
 | |
|     public function rules()
 | |
|     {
 | |
|         return [
 | |
|             // Wbudowany walidator zdefiniowany jako metoda validateCountry() w modelu
 | |
|             ['country', 'validateCountry'],
 | |
| 
 | |
|             // Wbudowany walidator zdefiniowany jako funkcja anonimowa
 | |
|             ['token', function ($attribute, $params, $validator) {
 | |
|                 if (!ctype_alnum($this->$attribute)) {
 | |
|                     $this->addError($attribute, 'Token musi zawierać litery lub cyfry.');
 | |
|                 }
 | |
|             }],
 | |
|         ];
 | |
|     }
 | |
| 
 | |
|     public function validateCountry($attribute, $params, $validator)
 | |
|     {
 | |
|         if (!in_array($this->$attribute, ['USA', 'Web'])) {
 | |
|             $this->addError($attribute, 'Wybrany kraj musi być jednym z: "USA", "Web".');
 | |
|         }
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| > Note: Począwszy od wersji 2.0.11 możesz użyć [[yii\validators\InlineValidator::addError()]], aby dodać błędy bezpośrednio. W tym sposobie treść błędu 
 | |
| > może być sformatowana bezpośrednio za pomocą [[yii\i18n\I18N::format()]]. Użyj `{attribute}` i `{value}` w treści błędu, aby odwołać się odpowiednio 
 | |
| > do etykiety atrybutu (bez konieczności pobierania jej ręcznie) i wartości atrybutu:
 | |
| >
 | |
| > ```php
 | |
| > $validator->addError($this, $attribute, 'Wartość "{value}" nie jest poprawna dla {attribute}.');
 | |
| > ```
 | |
| 
 | |
| > Note: Domyślnie wbudowane walidatory nie zostaną zastosowane, jeśli ich powiązane atrybuty otrzymają puste wartości lub wcześniej nie przeszły którejś z zasad walidacji.
 | |
| > Jeśli chcesz się upewnić, że zasada zawsze zostanie zastosowana, możesz skonfigurować właściwość [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] i/lub
 | |
| > [[yii\validators\Validator::skipOnError|skipOnError]], przypisując jej wartość `false` w deklaracji zasady walidacji. Dla przykładu:
 | |
| >
 | |
| > ```php
 | |
| > [
 | |
| >     ['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false],
 | |
| > ]
 | |
| > ```
 | |
| 
 | |
| 
 | |
| ### Walidatory niezależne <span id="standalone-validators"></span>
 | |
| 
 | |
| Walidator niezależy jest klasą rozszerzającą [[yii\validators\Validator|Validator]] lub klasy po nim dziedziczące.
 | |
| Możesz zaimplementować jego logikę walidacji poprzez nadpisanie metody [[yii\validators\Validator::validateAttribute()|validateAttribute()]].
 | |
| Jeśli atrybut nie przejdzie walidacji, wywołaj metodę [[yii\base\Model::addError()|addError()]] do zapisania wiadomości błędu w modelu, tak jak w
 | |
| [walidatorach wbudowanych](#inline-validators).
 | |
| 
 | |
| 
 | |
| Dla przykładu, poprzedni wbudowany walidator mógłby zostać przeniesiony do nowej klasy `components/validators/CountryValidator`.
 | |
| 
 | |
| ```php
 | |
| namespace app\components;
 | |
| 
 | |
| use yii\validators\Validator;
 | |
| 
 | |
| class CountryValidator extends Validator
 | |
| {
 | |
|     public function validateAttribute($model, $attribute)
 | |
|     {
 | |
|         if (!in_array($model->$attribute, ['USA', 'Web'])) {
 | |
|             $this->addError($model, $attribute, 'Wybrany kraj musi być jednym z: "USA", "Web".');
 | |
|         }
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Jeśli chcesz, aby walidator wspierał walidację wartości bez modelu, powinieneś nadpisać metodę [[yii\validators\Validator::validate()|validate()]].
 | |
| Możesz nadpisać także [[yii\validators\Validator::validateValue()|validateValue()]] zamiast `validateAttribute()` oraz `validate()`,
 | |
| ponieważ domyślnie te dwie metody są implementowane użyciem metody `validateValue()`.
 | |
| 
 | |
| Poniżej znajduje się przykład użycia powyższej klasy walidatora w modelu.
 | |
| 
 | |
| ```php
 | |
| namespace app\models;
 | |
| 
 | |
| use Yii;
 | |
| use yii\base\Model;
 | |
| use app\components\validators\CountryValidator;
 | |
| 
 | |
| class EntryForm extends Model
 | |
| {
 | |
|     public $name;
 | |
|     public $email;
 | |
|     public $country;
 | |
| 
 | |
|     public function rules()
 | |
|     {
 | |
|         return [
 | |
|             [['name', 'email'], 'required'],
 | |
|             ['country', CountryValidator::class],
 | |
|             ['email', 'email'],
 | |
|         ];
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| ## Walidacja wielu atrybutów na raz <span id="multiple-attributes-validation"></span>
 | |
| 
 | |
| Zdarza się, że walidatory sprawdzają wiele atrybutów jednocześnie. Rozważmy następujący formularz:
 | |
| 
 | |
| ```php
 | |
| class MigrationForm extends \yii\base\Model
 | |
| {
 | |
|     /**
 | |
|      * Kwota minimalnych funduszy dla jednej dorosłej osoby
 | |
|      */
 | |
|     const MIN_ADULT_FUNDS = 3000;
 | |
|     /**
 | |
|      * Kwota minimalnych funduszy dla jednego dziecka
 | |
|      */
 | |
|     const MIN_CHILD_FUNDS = 1500;
 | |
| 
 | |
|     public $personalSalary;
 | |
|     public $spouseSalary;
 | |
|     public $childrenCount;
 | |
|     public $description;
 | |
| 
 | |
|     public function rules()
 | |
|     {
 | |
|         return [
 | |
|             [['personalSalary', 'description'], 'required'],
 | |
|             [['personalSalary', 'spouseSalary'], 'integer', 'min' => self::MIN_ADULT_FUNDS],
 | |
|             ['childrenCount', 'integer', 'min' => 0, 'max' => 5],
 | |
|             [['spouseSalary', 'childrenCount'], 'default', 'value' => 0],
 | |
|             ['description', 'string'],
 | |
|         ];
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| ### Tworzenie walidatora <span id="multiple-attributes-validator"></span>
 | |
| 
 | |
| Powiedzmy, że chcemy sprawdzić, czy dochód rodziny jest wystarczający do utrzymania dzieci. W tym celu możemy utworzyć wbudowany walidator
 | |
| `validateChildrenFunds`, który będzie uruchamiany tylko jeśli `childrenCount` będzie większe niż 0.
 | |
| 
 | |
| Zwróć uwagę na to, że nie możemy użyć wszystkich walidowanych atrybutów (`['personalSalary', 'spouseSalary', 'childrenCount']`) przy dołączaniu walidatora.
 | |
| Wynika to z tego, że ten sam walidator będzie uruchomiony dla każdego z atrybutów oddzielnie (łącznie 3 razy), a musimy użyć go tylko raz dla całego zestawu atrybutów.
 | |
| 
 | |
| Możesz użyć dowolnego z tych atrybutów zamiast podanego poniżej (lub też tego, który uważasz za najbardziej tu odpowiedni):
 | |
| 
 | |
| ```php
 | |
| ['childrenCount', 'validateChildrenFunds', 'when' => function ($model) {
 | |
|     return $model->childrenCount > 0;
 | |
| }],
 | |
| ```
 | |
| 
 | |
| Implementacja `validateChildrenFunds` może wyglądać następująco:
 | |
| 
 | |
| ```php
 | |
| public function validateChildrenFunds($attribute, $params)
 | |
| {
 | |
|     $totalSalary = $this->personalSalary + $this->spouseSalary;
 | |
|     // Podwój minimalny fundusz dorosłych, jeśli ustalono zarobki współmałżonka
 | |
|     $minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
 | |
|     $childFunds = $totalSalary - $minAdultFunds;
 | |
|     if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
 | |
|         $this->addError('childrenCount', 'Twoje zarobki nie są wystarczające, aby utrzymać dzieci.');
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Możesz zignorować parametr `$attribute`, ponieważ walidacja nie jest powiązana bezpośrednio tylko z jednym atrybutem.
 | |
| 
 | |
| 
 | |
| ### Dodawanie informacji o błędach <span id="multiple-attributes-errors"></span>
 | |
| 
 | |
| Dodawanie błędów walidacji w przypadku wielu atrybutów może różnić się w zależności od ustalonej metodyki pracy z formularzami:
 | |
| 
 | |
| - Można wybrać najbardziej w naszej opinii pole i dodać błąd do jego atrybutu:
 | |
| 
 | |
| ```php
 | |
| $this->addError('childrenCount', 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.');
 | |
| ```
 | |
| 
 | |
| - Można wybrać wiele ważnych odpowiednich atrybutów lub też wszystkie i dodać ten sam błąd do każdego z nich. Możemy przechować 
 | |
| treść w oddzielnej zmiennej przed przekazaniem jej do `addError`, aby nie powtarzać się w kodzie (zasada DRY - Don't Repeat Yourself).
 | |
| 
 | |
| ```php
 | |
| $message = 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.';
 | |
| $this->addError('personalSalary', $message);
 | |
| $this->addError('wifeSalary', $message);
 | |
| $this->addError('childrenCount', $message);
 | |
| ```
 | |
| 
 | |
| Lub też użyć pętli:
 | |
| 
 | |
| ```php
 | |
| $attributes = ['personalSalary, 'wifeSalary', 'childrenCount'];
 | |
| foreach ($attributes as $attribute) {
 | |
|     $this->addError($attribute, 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.');
 | |
| }
 | |
| ```
 | |
| 
 | |
| - Można też dodać ogólny błąd (niepowiązany z żadnym szczególnym atrybutem). Do tego celu możemy wykorzystać nazwę nieistniejącego atrybutu, 
 | |
| na przykład `*`, ponieważ to, czy atrybut istnieje, nie jest sprawdzane w tym kroku.
 | |
| 
 | |
| ```php
 | |
| $this->addError('*', 'Twoje zarobki nie są wystarczające dla potrzeb dzieci.');
 | |
| ```
 | |
| 
 | |
| W rezultacie takiej operacji nie zobaczymy błędu zaraz obok pól formularza. Aby go wyświetlić, możemy dodać do widoku podsumowanie błędów formularza:
 | |
| 
 | |
| ```php
 | |
| <?= $form->errorSummary($model) ?>
 | |
| ```
 | |
| 
 | |
| > Note: Tworzenie walidatora operującego na wielu atrybutach jednocześnie jest dobrze opisane w [książce kucharskiej społeczności Yii](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-validator-multiple-attributes.md).
 | |
| 
 | |
| 
 | |
| ## Walidacja po stronie klienta <span id="client-side-validation"></span>
 | |
| 
 | |
| Walidacja po stronie klienta, bazująca na kodzie JavaScript jest wskazana, kiedy użytkownicy dostarczają dane przez formularz HTML,
 | |
| ponieważ pozwala na szybszą walidację błędów, a tym samym zapewnia lepszą ich obsługę dla użytkownika. Możesz użyć lub zaimplementować walidator,
 | |
| który wspiera walidację po stronie klienta jako *dodatek* do walidacji po stronie serwera.
 | |
| 
 | |
| > Info: Walidacja po stronie klienta nie jest wymagana. Głównym jej celem jest poprawa jakości korzystania z formularzy dla użytkowników.
 | |
| > Podobnie jak w przypadku danych wejściowych pochodzących od użytkowników, nigdy nie powinieneś ufać walidacji przeprowadanej po stronie klienta.
 | |
| > Z tego powodu należy zawsze przeprowadzać główną walidację po stronie serwera wywołując metodę [[yii\base\Model::validate()|validate()]],
 | |
| > tak jak zostało to opisane w poprzednich sekcjach.
 | |
| 
 | |
| ### Używanie walidacji po stronie klienta <span id="using-client-side-validation"></span>
 | |
| 
 | |
| Wiele [podstawowych walidatorów](tutorial-core-validators.md) domyślnie wspiera walidację po stronie klienta. Wszystko, co musisz zrobić, to użyć widżetu
 | |
| [[yii\widgets\ActiveForm|ActiveForm]] do zbudowania formularza HTML. Dla przykładu, model `LoginForm` poniżej deklaruje dwie zasady: jedną, używającą podstawowego walidatora
 | |
| [required](tutorial-core-validators.md#required), który wspiera walidację po stronie klienta i serwera, oraz drugą, w której użyto walidatora wbudowanego `validatePassword`,
 | |
| który wspiera tylko walidację po stronie serwera.
 | |
| 
 | |
| ```php
 | |
| namespace app\models;
 | |
| 
 | |
| use yii\base\Model;
 | |
| use app\models\User;
 | |
| 
 | |
| class LoginForm extends Model
 | |
| {
 | |
|     public $username;
 | |
|     public $password;
 | |
| 
 | |
|     public function rules()
 | |
|     {
 | |
|         return [
 | |
|             // atrybuty username oraz password są wymagane
 | |
|             [['username', 'password'], 'required'],
 | |
| 
 | |
|             // atrybut password jest walidowany przez validatePassword()
 | |
|             ['password', 'validatePassword'],
 | |
|         ];
 | |
|     }
 | |
| 
 | |
|     public function validatePassword()
 | |
|     {
 | |
|         $user = User::findByUsername($this->username);
 | |
| 
 | |
|         if (!$user || !$user->validatePassword($this->password)) {
 | |
|             $this->addError('password', 'Nieprawidłowa nazwa użytkownika lub hasło.');
 | |
|         }
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Formularz HTML zbudowany przez następujący kod zawiera dwa pola: `username` oraz `password`.
 | |
| Jeśli wyślesz formularz bez wpisywania jakichkolwiek danych, otrzymasz komunikaty błędów o ich braku, bez konieczności przeprowadzania komunikacji z serwerem.
 | |
| 
 | |
| ```php
 | |
| <?php $form = yii\widgets\ActiveForm::begin(); ?>
 | |
|     <?= $form->field($model, 'username') ?>
 | |
|     <?= $form->field($model, 'password')->passwordInput() ?>
 | |
|     <?= Html::submitButton('Login') ?>
 | |
| <?php yii\widgets\ActiveForm::end(); ?>
 | |
| ```
 | |
| 
 | |
| "Za kulisami", widżet [[yii\widgets\ActiveForm|ActiveForm]] odczyta wszystkie zasady walidacji zadeklarowane w modelu i wygeneruje odpowiedni kod JavaScript
 | |
| dla walidatorów wspierających walidację po stronie klienta. Kiedy użytkownik zmieni wartość w polu lub spróbuje wysłać formularz, zostanie wywołana walidacja po stronie klienta.
 | |
| 
 | |
| Jeśli chcesz wyłączyć całkowicie walidację po stronie klienta, możesz ustawić właściwość [[yii\widgets\ActiveForm::enableClientValidation|enableClientValidation]] na `false`.
 | |
| Możesz również wyłączyć ten rodzaj walidacji dla konkretnego pola, przez ustawienie jego właściwości
 | |
| [[yii\widgets\ActiveField::enableClientValidation|enableClientValidation]] na `false`. Jeśli właściwość `enableClientValidation` zostanie skonfigurowana na poziomie pola
 | |
| formularza i w samym formularzu jednocześnie, pierwszeństwo będzie miała opcja określona w formularzu.
 | |
| 
 | |
| > Info: Od wersji 2.0.11 wszystkie walidatory rozszerzające klasę [[yii\validators\Validator]] używają opcji klienta przekazywanych 
 | |
| > z oddzielnej metody - [[yii\validators\Validator::getClientOptions()]]. Możesz jej użyć:
 | |
| >
 | |
| > - jeśli chcesz zaimplementować swoją własną walidację po stronie klienta, ale pozostawić synchronizację z opcjami walidatora po stronie serwera;
 | |
| > - do rozszerzenia lub zmodyfikowania dla uzyskania specjalnych korzyści:
 | |
| >
 | |
| > ```php
 | |
| > public function getClientOptions($model, $attribute)
 | |
| > {
 | |
| >     $options = parent::getClientOptions($model, $attribute);
 | |
| >     // Zmodyfikuj $options w tym miejscu
 | |
| >
 | |
| >     return $options;
 | |
| > }
 | |
| > ```
 | |
| 
 | |
| 
 | |
| ### Implementacja walidacji po stronie klienta <span id="implementing-client-side-validation"></span>
 | |
| 
 | |
| Aby utworzyć walidator wspierający walidację po stronie klienta, powinieneś zaimplementować metodę
 | |
| [[yii\validators\Validator::clientValidateAttribute()|clientValidateAttribute()]], która zwraca kod JavaScript, odpowiedzialny za przeprowadzenie walidacji.
 | |
| W kodzie JavaScript możesz użyć następujących predefiniowanych zmiennych:
 | |
| 
 | |
| - `attribute`: nazwa atrybutu podlegającego walidacji.
 | |
| - `value`: wartość atrybutu podlegająca walidacji.
 | |
| - `messages`: tablica używana do przechowywania wiadomości błędów dla danego atrybutu.
 | |
| - `deferred`: tablica, do której można dodać zakolejkowane obiekty (wyjaśnione w późniejszej podsekcji).
 | |
| 
 | |
| W poniższym przykładzie, tworzymy walidator `StatusValidator`, który sprawdza, czy wartość danego atrybutu jest wartością znajdującą się na liście statusów w bazie danych.
 | |
| Walidator wspiera obydwa typy walidacji; po stronie klienta oraz serwerową.
 | |
| 
 | |
| ```php
 | |
| namespace app\components;
 | |
| 
 | |
| use yii\validators\Validator;
 | |
| use app\models\Status;
 | |
| 
 | |
| class StatusValidator extends Validator
 | |
| {
 | |
|     public function init()
 | |
|     {
 | |
|         parent::init();
 | |
|         $this->message = 'Niepoprawna wartość pola status.';
 | |
|     }
 | |
| 
 | |
|     public function validateAttribute($model, $attribute)
 | |
|     {
 | |
|         $value = $model->$attribute;
 | |
|         if (!Status::find()->where(['id' => $value])->exists()) {
 | |
|             $model->addError($attribute, $this->message);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function clientValidateAttribute($model, $attribute, $view)
 | |
|     {
 | |
|         $statuses = json_encode(Status::find()->select('id')->asArray()->column());
 | |
|         $message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
 | |
|         return <<<JS
 | |
| if ($.inArray(value, $statuses) === -1) {
 | |
|     messages.push($message);
 | |
| }
 | |
| JS;
 | |
|     }
 | |
| }
 | |
| ```
 | |
| 
 | |
| > Tip: Powyższy kod został podany głównie do zademonstrowania, jak wspierać walidację po stronie klienta.
 | |
| > W praktyce można użyć podstawowego walidatora [in](tutorial-core-validators.md#in), aby osiągnąć ten sam cel.
 | |
| > Możesz napisać taką zasadę walidacji następująco:
 | |
| >
 | |
| > ```php
 | |
| > [
 | |
| >     ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()],
 | |
| > ]
 | |
| > ```
 | |
| 
 | |
| > Tip: Jeśli musisz dodać ręcznie walidację po stronie klienta np. podczas dynamicznego dodawania pól formularza lub przeprowadzania specjalnej logiki w obrębie interfejsu
 | |
| > użytkownika, zapoznaj się z rozdziałem [Praca z ActiveForm za pomocą JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md)
 | |
| > w Yii 2.0 Cookbook.
 | |
| 
 | |
| ### Kolejkowa walidacja <span id="deferred-validation"></span>
 | |
| 
 | |
| Jeśli potrzebujesz przeprowadzić asynchroniczną walidację po stronie klienta, możesz utworzyć [obiekt kolejkujący](https://api.jquery.com/category/deferred-object/).
 | |
| Dla przykładu, aby przeprowadzić niestandardową walidację AJAX, możesz użyć następującego kodu:
 | |
| 
 | |
| ```php
 | |
| public function clientValidateAttribute($model, $attribute, $view)
 | |
| {
 | |
|     return <<<JS
 | |
|         deferred.push($.get("/check", {value: value}).done(function(data) {
 | |
|             if ('' !== data) {
 | |
|                 messages.push(data);
 | |
|             }
 | |
|         }));
 | |
| JS;
 | |
| }
 | |
| ```
 | |
| 
 | |
| W powyższym kodzie, zmienna `deferred` jest dostarczoną przez Yii tablicą zakolejkowanych obiektów.
 | |
| Metoda jQuery `$.get()` tworzy obiekt kolejkowy, który jest dodawany do tablicy `deferred`.
 | |
| 
 | |
| Możesz także utworzyć osobny obiekt kolejkowania i wywołać jego metodę `resolve()` po otrzymaniu asynchronicznej informacji zwrotnej.
 | |
| Poniższy przykład pokazuje, jak zwalidować wymiary przesłanego obrazka po stronie klienta.
 | |
| 
 | |
| ```php
 | |
| public function clientValidateAttribute($model, $attribute, $view)
 | |
| {
 | |
|     return <<<JS
 | |
|         var def = $.Deferred();
 | |
|         var img = new Image();
 | |
|         img.onload = function() {
 | |
|             if (this.width > 150) {
 | |
|                 messages.push('Image too wide!!');
 | |
|             }
 | |
|             def.resolve();
 | |
|         }
 | |
|         var reader = new FileReader();
 | |
|         reader.onloadend = function() {
 | |
|             img.src = reader.result;
 | |
|         }
 | |
|         reader.readAsDataURL(file);
 | |
| 
 | |
|         deferred.push(def);
 | |
| JS;
 | |
| }
 | |
| ```
 | |
| 
 | |
| > Note: Metoda `resolve()` musi być wywołana po walidacji atrybutu. W przeciwnym razie główna walidacja formularza nie zostanie ukończona.
 | |
| 
 | |
| Dla uproszczenia, tablica `deferred` jest wyposażona w skrótową metodę `add()`, która automatycznie tworzy obiekt kolejkowy i dodaje go do tej tablicy.
 | |
| Używając tej metody, możesz uprościć powyższy przykład:
 | |
| 
 | |
| ```php
 | |
| public function clientValidateAttribute($model, $attribute, $view)
 | |
| {
 | |
|     return <<<JS
 | |
|         deferred.add(function(def) {
 | |
|             var img = new Image();
 | |
|             img.onload = function() {
 | |
|                 if (this.width > 150) {
 | |
|                     messages.push('Image too wide!!');
 | |
|                 }
 | |
|                 def.resolve();
 | |
|             }
 | |
|             var reader = new FileReader();
 | |
|             reader.onloadend = function() {
 | |
|                 img.src = reader.result;
 | |
|             }
 | |
|             reader.readAsDataURL(file);
 | |
|         });
 | |
| JS;
 | |
| }
 | |
| ```
 | |
| 
 | |
| 
 | |
| ### Walidacja przy użyciu AJAX <span id="ajax-validation"></span>
 | |
| 
 | |
| Niektóre walidacje mogą zostać wykonane tylko po stronie serwera, ponieważ tylko serwer posiada niezbędne informacje do ich przeprowadzenia.
 | |
| Dla przykładu, aby sprawdzić, czy login został już zajęty, musimy sprawdzić tabelę użytkowników w bazie danych.
 | |
| W tym właśnie przypadku możesz użyć walidacji AJAX. Wywoła ona żądanie AJAX w tle, aby spradzić to pole.
 | |
| 
 | |
| Aby uaktywnić walidację AJAX dla pojedyńczego pola formularza, ustaw właściwość [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] na `true`
 | |
| oraz zdefiniuj unikalne `id` formularza:
 | |
| 
 | |
| ```php
 | |
| use yii\widgets\ActiveForm;
 | |
| 
 | |
| $form = ActiveForm::begin([
 | |
|     'id' => 'registration-form',
 | |
| ]);
 | |
| 
 | |
| echo $form->field($model, 'username', ['enableAjaxValidation' => true]);
 | |
| 
 | |
| // ...
 | |
| 
 | |
| ActiveForm::end();
 | |
| ```
 | |
| 
 | |
| Aby uaktywnić walidację AJAX dla całego formularza, ustaw właściwość [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] na `true` na poziomie formularza:
 | |
| 
 | |
| ```php
 | |
| $form = ActiveForm::begin([
 | |
|     'id' => 'contact-form',
 | |
|     'enableAjaxValidation' => true,
 | |
| ]);
 | |
| ```
 | |
| 
 | |
| > Note: Jeśli właściwość [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] zostanie skonfigurowana na poziomie pola formularza i jednocześnie w samym formularzu,
 | |
| > pierwszeństwo będzie miała opcja określona w formularzu.
 | |
| 
 | |
| 
 | |
| Musisz również przygotować serwer na obsłużenie AJAXowego zapytanie o walidację. Możesz to osiągnąć przez następujący fragment kodu w akcji kontrolera:
 | |
| 
 | |
| ```php
 | |
| if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
 | |
|     Yii::$app->response->format = Response::FORMAT_JSON;
 | |
|     return ActiveForm::validate($model);
 | |
| }
 | |
| ```
 | |
| 
 | |
| Powyższy kod sprawdzi, czy zapytanie zostało wysłane przy użyciu AJAXa. Jeśli tak, w odpowiedzi zwróci wynik walidacji w formacie JSON.
 | |
| 
 | |
| > Info: Możesz również użyć [walidacji kolejkowej](#deferred-validation) do wykonania walidacji AJAX,
 | |
| > jednakże walidacja AJAXowa opisana w tej sekcji jest bardziej systematyczna i wymaga mniej wysiłku przy kodowaniu.
 | |
| 
 | |
| Kiedy zarówno `enableClientValidation`, jak i `enableAjaxValidation` ustawione są na `true`, walidacja za pomocą AJAX zostanie uruchomiona dopiero po udanej
 | |
| walidacji po stronie klienta.
 | 
