Nette Documentation Preview

syntax
Φόρμες σε Παρουσιαστές
**********************

.[perex]
Η Nette Forms διευκολύνει δραματικά τη δημιουργία και την επεξεργασία φορμών ιστού. Σε αυτό το κεφάλαιο, θα μάθετε πώς να χρησιμοποιείτε φόρμες μέσα στους παρουσιαστές.

Αν σας ενδιαφέρει να τις χρησιμοποιήσετε εντελώς αυτόνομα χωρίς το υπόλοιπο πλαίσιο, υπάρχει ένας οδηγός για [αυτόνομες φόρμες |standalone].


Πρώτη φόρμα .[#toc-first-form]
==============================

Θα προσπαθήσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα μοιάζει ως εξής:

```php
use Nette\Application\UI\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');
$form->onSuccess[] = [$this, 'formSucceeded'];
```

και στο πρόγραμμα περιήγησης το αποτέλεσμα θα πρέπει να μοιάζει με αυτό:

[* form-en.webp *]

Η φόρμα στον παρουσιαστή είναι ένα αντικείμενο της κλάσης `Nette\Application\UI\Form`, ο προκάτοχός της `Nette\Forms\Form` προορίζεται για αυτόνομη χρήση. Προσθέσαμε σε αυτήν τα πεδία name, password και το κουμπί αποστολής. Τέλος, η γραμμή με το `$form->onSuccess` λέει ότι μετά την υποβολή και την επιτυχή επικύρωση, θα πρέπει να κληθεί η μέθοδος `$this->formSucceeded()`.

Από τη σκοπιά του παρουσιαστή, η φόρμα είναι ένα κοινό συστατικό. Ως εκ τούτου, αντιμετωπίζεται ως συστατικό και ενσωματώνεται στον παρουσιαστή χρησιμοποιώντας τη [μέθοδο factory |application:components#Factory Methods]. Αυτό θα έχει την εξής μορφή:

```php .{file:app/Presenters/HomePresenter.php}
use Nette;
use Nette\Application\UI\Form;

class HomePresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentRegistrationForm(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addPassword('password', 'Password:');
		$form->addSubmit('send', 'Sign up');
		$form->onSuccess[] = [$this, 'formSucceeded'];
		return $form;
	}

	public function formSucceeded(Form $form, $data): void
	{
		// εδώ θα επεξεργαστούμε τα δεδομένα που αποστέλλονται από τη φόρμα
		// $data->name περιέχει όνομα
		// $data->password περιέχει κωδικό πρόσβασης
		$this->flashMessage('You have successfully signed up.');
		$this->redirect('Home:');
	}
}
```

Και η απόδοση στο πρότυπο γίνεται με τη χρήση της ετικέτας `{control}`:

```latte .{file:app/Presenters/templates/Home/default.latte}
<h1>Registration</h1>

{control registrationForm}
```

Και αυτό είναι όλο :-) Έχουμε μια λειτουργική και απόλυτα [ασφαλής |#Vulnerability Protection] φόρμα.

Τώρα πιθανόν να σκέφτεστε ότι ήταν πολύ γρήγορα, αναρωτιέστε πώς είναι δυνατόν να καλείται η μέθοδος `formSucceeded()` και ποιες είναι οι παράμετροι που παίρνει. Σίγουρα, έχετε δίκιο, αυτό αξίζει μια εξήγηση.

Η Nette καταλήγει σε έναν ωραίο μηχανισμό, τον οποίο ονομάζουμε [Hollywood style |application:components#Hollywood style]. Αντί να χρειάζεται να ρωτάτε διαρκώς αν έχει συμβεί κάτι ("υποβλήθηκε η φόρμα;", "υποβλήθηκε έγκυρα;" ή "δεν πλαστογραφήθηκε;"), λέτε στο πλαίσιο "όταν η φόρμα ολοκληρωθεί έγκυρα, καλέστε αυτή τη μέθοδο" και αφήνετε την περαιτέρω εργασία σε αυτό. Αν προγραμματίζετε σε JavaScript, είστε εξοικειωμένοι με αυτό το στυλ προγραμματισμού. Γράφετε συναρτήσεις που καλούνται όταν συμβαίνει ένα συγκεκριμένο [γεγονός |nette:glossary#Events]. Και η γλώσσα περνάει σε αυτές τα κατάλληλα ορίσματα.

Με αυτόν τον τρόπο είναι δομημένος ο παραπάνω κώδικας του παρουσιαστή. Η συστοιχία `$form->onSuccess` αντιπροσωπεύει τη λίστα με τα callbacks της PHP που θα καλέσει η Nette όταν η φόρμα υποβληθεί και συμπληρωθεί σωστά.
Μέσα [στον κύκλο ζωής του παρουσιαστή |application:presenters#Life Cycle of Presenter] είναι ένα λεγόμενο σήμα, οπότε καλούνται μετά τη μέθοδο `action*` και πριν τη μέθοδο `render*`.
Και περνάει σε κάθε callback την ίδια τη φόρμα στην πρώτη παράμετρο και τα δεδομένα που αποστέλλονται ως αντικείμενο [ArrayHash |utils:arrays#ArrayHash] στη δεύτερη. Μπορείτε να παραλείψετε την πρώτη παράμετρο αν δεν χρειάζεστε το αντικείμενο της φόρμας. Η δεύτερη παράμετρος μπορεί να είναι ακόμη πιο χρήσιμη, αλλά γι' αυτό [αργότερα |#Mapping to Classes].

Το αντικείμενο `$data` περιέχει τις ιδιότητες `name` και `password` με τα δεδομένα που εισήγαγε ο χρήστης. Συνήθως στέλνουμε τα δεδομένα απευθείας για περαιτέρω επεξεργασία, η οποία μπορεί να είναι, για παράδειγμα, η εισαγωγή στη βάση δεδομένων. Ωστόσο, μπορεί να προκύψει κάποιο σφάλμα κατά την επεξεργασία, για παράδειγμα, το όνομα χρήστη είναι ήδη κατειλημμένο. Σε αυτή την περίπτωση, περνάμε το σφάλμα πίσω στη φόρμα χρησιμοποιώντας το `addError()` και αφήνουμε να ξανασχεδιαστεί, με ένα μήνυμα σφάλματος:

```php
$form->addError('Sorry, username is already in use.');
```

Εκτός από το `onSuccess`, υπάρχει και το `onSubmit`: τα callbacks καλούνται πάντα μετά την υποβολή της φόρμας, ακόμη και αν αυτή δεν έχει συμπληρωθεί σωστά. Και τέλος `onError`: τα callbacks καλούνται μόνο αν η υποβολή δεν είναι έγκυρη. Καλούνται ακόμη και αν ακυρώσουμε τη φόρμα στο `onSuccess` ή στο `onSubmit` χρησιμοποιώντας το `addError()`.

Μετά την επεξεργασία της φόρμας, θα κάνουμε ανακατεύθυνση στην επόμενη σελίδα. Αυτό αποτρέπει την ακούσια επανυποβολή της φόρμας με το πάτημα του κουμπιού *refresh*, *back*, ή την μετακίνηση του ιστορικού του προγράμματος περιήγησης.

Δοκιμάστε να προσθέσετε περισσότερα [στοιχεία ελέγχου φόρμας |controls].


Πρόσβαση στα στοιχεία ελέγχου .[#toc-access-to-controls]
========================================================

Η φόρμα είναι ένα συστατικό του παρουσιαστή, στην περίπτωσή μας με το όνομα `registrationForm` (από το όνομα της μεθόδου κατασκευής `createComponentRegistrationForm`), οπότε οπουδήποτε στον παρουσιαστή μπορείτε να φτάσετε στη φόρμα χρησιμοποιώντας:

```php
$form = $this->getComponent('registrationForm');
// εναλλακτική σύνταξη: Φόρμα εγγραφής'],
```

Επίσης, τα μεμονωμένα στοιχεία ελέγχου της φόρμας είναι συστατικά, οπότε μπορείτε να έχετε πρόσβαση σε αυτά με τον ίδιο τρόπο:

```php
$input = $form->getComponent('name'); // ή $input = $form['name'],
$button = $form->getComponent('send'); // ή $button = $form['send'],
```

Τα στοιχεία ελέγχου αφαιρούνται με τη χρήση unset:

```php
unset($form['name']);
```


Κανόνες επικύρωσης .[#toc-validation-rules]
===========================================

Η λέξη *valid* χρησιμοποιήθηκε αρκετές φορές, αλλά η φόρμα δεν έχει ακόμη κανόνες επικύρωσης. Ας το διορθώσουμε.

Το όνομα θα είναι υποχρεωτικό, οπότε θα το επισημάνουμε με τη μέθοδο `setRequired()`, της οποίας το όρισμα είναι το κείμενο του μηνύματος λάθους που θα εμφανιστεί αν ο χρήστης δεν το συμπληρώσει. Αν δεν δοθεί κανένα όρισμα, χρησιμοποιείται το προεπιλεγμένο μήνυμα σφάλματος.

```php
$form->addText('name', 'Name:')
	->setRequired('Please fill your name.');
```

Δοκιμάστε να στείλετε τη φόρμα χωρίς να έχει συμπληρωθεί το όνομα και θα δείτε ότι θα εμφανιστεί ένα μήνυμα σφάλματος και το πρόγραμμα περιήγησης ή ο διακομιστής θα την απορρίψει μέχρι να το συμπληρώσετε.

Ταυτόχρονα, δεν θα μπορείτε να εξαπατήσετε το σύστημα πληκτρολογώντας μόνο κενά στην είσοδο, για παράδειγμα. Με κανέναν τρόπο. Η Nette κόβει αυτόματα τα κενά αριστερά και δεξιά. Δοκιμάστε το. Είναι κάτι που πρέπει να κάνετε πάντα με κάθε είσοδο μιας γραμμής, αλλά συχνά ξεχνιέται. Η Nette το κάνει αυτόματα. (Μπορείτε να προσπαθήσετε να ξεγελάσετε τις φόρμες και να στείλετε μια πολυγραμμή συμβολοσειρά ως όνομα. Ακόμα και σε αυτή την περίπτωση, η Nette δεν θα ξεγελαστεί και τα διαλείμματα γραμμής θα μετατραπούν σε κενά).

Η φόρμα επικυρώνεται πάντα από την πλευρά του διακομιστή, αλλά δημιουργείται επίσης επικύρωση μέσω JavaScript, η οποία είναι γρήγορη και ο χρήστης γνωρίζει το σφάλμα αμέσως, χωρίς να χρειάζεται να στείλει τη φόρμα στον διακομιστή. Αυτό το χειρίζεται η δέσμη ενεργειών `netteForms.js`.
Εισάγεται στο πρότυπο διάταξης:

```latte
<script src="https://unpkg.com/nette-forms@3/src/assets/netteForms.js"></script>
```

Αν κοιτάξετε στον πηγαίο κώδικα της σελίδας με τη φόρμα, μπορεί να παρατηρήσετε ότι η Nette εισάγει τα απαιτούμενα πεδία σε στοιχεία με κλάση CSS `required`. Δοκιμάστε να προσθέσετε το ακόλουθο στυλ στο πρότυπο, και η ετικέτα "Όνομα" θα είναι κόκκινη. Κομψά, επισημαίνουμε τα υποχρεωτικά πεδία για τους χρήστες:

```latte
<style>
.required label { color: maroon }
</style>
```

Πρόσθετοι κανόνες επικύρωσης θα προστεθούν με τη μέθοδο `addRule()`. Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι και πάλι το κείμενο του μηνύματος σφάλματος και μπορεί να ακολουθήσει το προαιρετικό όρισμα του κανόνα επικύρωσης. Τι σημαίνει αυτό;

Η φόρμα θα λάβει μια άλλη προαιρετική είσοδο *ηλικία* με την προϋπόθεση, ότι πρέπει να είναι αριθμός (`addInteger()`) και σε συγκεκριμένα όρια (`$form::Range`). Και εδώ θα χρησιμοποιήσουμε το τρίτο όρισμα του `addRule()`, το ίδιο το εύρος:

```php
$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);
```

.[tip]
Εάν ο χρήστης δεν συμπληρώσει το πεδίο, οι κανόνες επικύρωσης δεν θα επαληθευτούν, επειδή το πεδίο είναι προαιρετικό.

Προφανώς υπάρχει περιθώριο για ένα μικρό refactoring. Στο μήνυμα σφάλματος και στην τρίτη παράμετρο, οι αριθμοί παρατίθενται εις διπλούν, πράγμα που δεν είναι ιδανικό. Αν δημιουργούσαμε μια [πολύγλωσση φόρμα |rendering#translating] και το μήνυμα που περιέχει αριθμούς θα έπρεπε να μεταφραστεί σε πολλές γλώσσες, θα δυσκόλευε την αλλαγή των τιμών. Για το λόγο αυτό, μπορούν να χρησιμοποιηθούν οι υποκατάστατοι χαρακτήρες `%d`:

```php
	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);
```

Ας επιστρέψουμε στο πεδίο *password*, ας το κάνουμε *απαιτούμενο* και ας ελέγξουμε το ελάχιστο μήκος του κωδικού πρόσβασης (`$form::MinLength`), χρησιμοποιώντας και πάλι τους χαρακτήρες αντικατάστασης στο μήνυμα:

```php
$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);
```

Θα προσθέσουμε ένα πεδίο `passwordVerify` στη φόρμα, όπου ο χρήστης θα εισάγει ξανά τον κωδικό πρόσβασης, για έλεγχο. Χρησιμοποιώντας κανόνες επικύρωσης, ελέγχουμε αν και οι δύο κωδικοί πρόσβασης είναι ίδιοι (`$form::Equal`). Και ως όρισμα δίνουμε μια αναφορά στον πρώτο κωδικό πρόσβασης χρησιμοποιώντας [αγκύλες |#Access to Controls]:

```php
$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();
```

Χρησιμοποιώντας το `setOmitted()`, επισημαίνουμε ένα στοιχείο του οποίου η τιμή δεν μας ενδιαφέρει πραγματικά και το οποίο υπάρχει μόνο για επικύρωση. Η τιμή του δεν περνάει στο `$data`.

Έχουμε μια πλήρως λειτουργική φόρμα με επικύρωση σε PHP και JavaScript. Οι δυνατότητες επικύρωσης της Nette είναι πολύ ευρύτερες, μπορείτε να δημιουργήσετε συνθήκες, να εμφανίσετε και να αποκρύψετε τμήματα μιας σελίδας σύμφωνα με αυτές, κ.λπ. Μπορείτε να μάθετε τα πάντα στο κεφάλαιο για την [επικύρωση φορμών |validation].


Προεπιλεγμένες τιμές .[#toc-default-values]
===========================================

Συχνά ορίζουμε προεπιλεγμένες τιμές για τα στοιχεία ελέγχου φόρμας:

```php
$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);
```

Συχνά είναι χρήσιμο να ορίσουμε προεπιλεγμένες τιμές για όλα τα στοιχεία ελέγχου ταυτόχρονα. Για παράδειγμα, όταν η φόρμα χρησιμοποιείται για την επεξεργασία εγγραφών. Διαβάζουμε την εγγραφή από τη βάση δεδομένων και την ορίζουμε ως προεπιλεγμένες τιμές:

```php
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
```

Καλέστε το `setDefaults()` μετά τον ορισμό των στοιχείων ελέγχου.


Απεικόνιση της φόρμας .[#toc-rendering-the-form]
================================================

Από προεπιλογή, η φόρμα απεικονίζεται ως πίνακας. Τα επιμέρους στοιχεία ελέγχου ακολουθούν τις βασικές οδηγίες προσβασιμότητας ιστού. Όλες οι ετικέτες παράγονται ως `<label>` στοιχεία και συνδέονται με τις εισόδους τους, κάνοντας κλικ στην ετικέτα μετακινείται ο δρομέας στην είσοδο.

Μπορούμε να ορίσουμε οποιαδήποτε χαρακτηριστικά HTML για κάθε στοιχείο. Για παράδειγμα, να προσθέσουμε ένα placeholder:

```php
$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');
```

Υπάρχουν πραγματικά πολλοί τρόποι για την απόδοση μιας φόρμας, γι' αυτό και είναι αφιερωμένο [κεφάλαιο για την απόδοση |rendering].


Χαρτογράφηση σε κλάσεις .[#toc-mapping-to-classes]
==================================================

Ας επιστρέψουμε στη μέθοδο `formSucceeded()`, η οποία στη δεύτερη παράμετρο `$data` λαμβάνει τα δεδομένα που αποστέλλονται ως αντικείμενο `ArrayHash`. Επειδή πρόκειται για μια γενική κλάση, κάτι σαν το `stdClass`, θα μας λείψουν κάποιες ευκολίες κατά την εργασία με αυτήν, όπως η συμπλήρωση κώδικα για ιδιότητες σε επεξεργαστές ή στατική ανάλυση κώδικα. Αυτό θα μπορούσε να λυθεί με την ύπαρξη μιας συγκεκριμένης κλάσης για κάθε φόρμα, της οποίας οι ιδιότητες αντιπροσωπεύουν τα επιμέρους στοιχεία ελέγχου. Π.χ:

```php
class RegistrationFormData
{
	public string $name;
	public int $age;
	public string $password;
}
```

Από την PHP 8.0, μπορείτε να χρησιμοποιήσετε αυτόν τον κομψό συμβολισμό που χρησιμοποιεί έναν κατασκευαστή:

```php
class RegistrationFormData
{
	public function __construct(
		public string $name,
		public int $age,
		public string $password,
	) {
	}
}
```

Πώς μπορείτε να πείτε στη Nette να επιστρέφει δεδομένα ως αντικείμενα αυτής της κλάσης; Πιο εύκολα απ' ό,τι νομίζετε. Το μόνο που έχετε να κάνετε είναι να καθορίσετε την κλάση ως τύπο της παραμέτρου `$data` στον χειριστή:

```php
public function formSucceeded(Form $form, RegistrationFormData $data): void
{
	// $name είναι instance of RegistrationFormData
	$name = $data->name;
	// ...
}
```

Μπορείτε επίσης να καθορίσετε το `array` ως τύπο και τότε θα περάσει τα δεδομένα ως πίνακα.

Με παρόμοιο τρόπο, μπορείτε να χρησιμοποιήσετε τη μέθοδο `getValues()`, την οποία περνάμε ως όνομα κλάσης ή αντικειμένου προς ενυδάτωση ως παράμετρο:

```php
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
```

Εάν οι φόρμες αποτελούνται από μια δομή πολλαπλών επιπέδων που αποτελείται από δοχεία, δημιουργήστε μια ξεχωριστή κλάση για κάθε ένα από αυτά:

```php
$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */

class PersonFormData
{
	public string $firstName;
	public string $lastName;
}

class RegistrationFormData
{
	public PersonFormData $person;
	public int $age;
	public string $password;
}
```

Η αντιστοίχιση γνωρίζει τότε από τον τύπο της ιδιότητας `$person` ότι πρέπει να αντιστοιχίσει το δοχείο στην κλάση `PersonFormData`. Εάν η ιδιότητα θα περιέχει έναν πίνακα από δοχεία, δώστε τον τύπο `array` και περάστε την κλάση που θα αντιστοιχιστεί απευθείας στο δοχείο:

```php
$person->setMappedType(PersonFormData::class);
```

Μπορείτε να δημιουργήσετε μια πρόταση για την κλάση δεδομένων μιας φόρμας χρησιμοποιώντας τη μέθοδο `Nette\Forms\Blueprint::dataClass($form)`, η οποία θα την εκτυπώσει στη σελίδα του προγράμματος περιήγησης. Στη συνέχεια, μπορείτε απλά να κάνετε κλικ για να επιλέξετε και να αντιγράψετε τον κώδικα στο έργο σας. .{data-version:3.1.15}


Πολλαπλά κουμπιά υποβολής .[#toc-multiple-submit-buttons]
=========================================================

Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως πρέπει να διακρίνουμε ποιο από αυτά έχει πατηθεί. Μπορούμε να δημιουργήσουμε δική μας συνάρτηση για κάθε κουμπί. Ορίστε την ως χειριστή για το [συμβάν |nette:glossary#Events] `onClick`:

```php
$form->addSubmit('save', 'Save')
	->onClick[] = [$this, 'saveButtonPressed'];

$form->addSubmit('delete', 'Delete')
	->onClick[] = [$this, 'deleteButtonPressed'];
```

Αυτοί οι χειριστές καλούνται επίσης μόνο στην περίπτωση που η φόρμα είναι έγκυρη, όπως στην περίπτωση του συμβάντος `onSuccess`. Η διαφορά είναι ότι η πρώτη παράμετρος μπορεί να είναι το αντικείμενο submit button αντί της φόρμας, ανάλογα με τον τύπο που καθορίζετε:

```php
public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data)
{
	$form = $button->getForm();
	// ...
}
```

Όταν μια φόρμα υποβάλλεται με το πλήκτρο <kbd>Enter</kbd>, αντιμετωπίζεται σαν να είχε υποβληθεί με το πρώτο κουμπί.


Γεγονός onAnchor .[#toc-event-onanchor]
=======================================

Όταν κατασκευάζετε μια φόρμα σε μια εργοστασιακή μέθοδο (όπως η `createComponentRegistrationForm`), δεν γνωρίζει ακόμα αν έχει υποβληθεί ή τα δεδομένα με τα οποία υποβλήθηκε. Υπάρχουν όμως περιπτώσεις όπου πρέπει να γνωρίζουμε τις τιμές που έχουν υποβληθεί, ίσως εξαρτάται από αυτές το πώς θα είναι η μορφή της φόρμας, ή χρησιμοποιούνται για εξαρτημένα selectboxes, κλπ.

Επομένως, μπορείτε να έχετε τον κώδικα που κατασκευάζει τη φόρμα να καλείται όταν αυτή είναι αγκυρωμένη, δηλαδή είναι ήδη συνδεδεμένη με τον παρουσιαστή και γνωρίζει τα υποβληθέντα δεδομένα της. Θα τοποθετήσουμε έναν τέτοιο κώδικα στον πίνακα `$onAnchor`:

```php
$country = $form->addSelect('country', 'Country:', $this->model->getCountries());
$city = $form->addSelect('city', 'City:');

$form->onAnchor[] = function () use ($country, $city) {
	// αυτή η συνάρτηση θα κληθεί όταν η φόρμα γνωρίζει τα δεδομένα με τα οποία υποβλήθηκε
	// έτσι ώστε να μπορείτε να χρησιμοποιήσετε τη μέθοδο getValue()
	$val = $country->getValue();
	$city->setItems($val ? $this->model->getCities($val): []);
};
```


Vulnerability Protection .[#toc-vulnerability-protection]
=========================================================

Το Nette Framework καταβάλλει μεγάλη προσπάθεια για να είναι ασφαλές και δεδομένου ότι οι φόρμες είναι η πιο συνηθισμένη είσοδος του χρήστη, οι φόρμες του Nette είναι σχεδόν αδιαπέραστες. Όλα διατηρούνται δυναμικά και διαφανώς, τίποτα δεν χρειάζεται να ρυθμιστεί χειροκίνητα.

Εκτός από την προστασία των φορμών από επιθέσεις που στοχεύουν σε γνωστές ευπάθειες όπως [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] και [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf]), κάνει πολλές μικρές εργασίες ασφαλείας που δεν χρειάζεται πλέον να σκέφτεστε.

Για παράδειγμα, φιλτράρει όλους τους χαρακτήρες ελέγχου από τις εισόδους και ελέγχει την εγκυρότητα της κωδικοποίησης UTF-8, ώστε τα δεδομένα από τη φόρμα να είναι πάντα καθαρά. Για τα πλαίσια επιλογής και τις λίστες επιλογής, επαληθεύει ότι τα επιλεγμένα στοιχεία ήταν όντως από τα προσφερόμενα και ότι δεν υπήρξε πλαστογράφηση. Έχουμε ήδη αναφέρει ότι για την εισαγωγή κειμένου σε μία γραμμή, αφαιρεί τους χαρακτήρες τέλους γραμμής που θα μπορούσε να στείλει εκεί ένας εισβολέας. Για εισόδους πολλαπλών γραμμών, ομαλοποιεί τους χαρακτήρες στο τέλος της γραμμής. Και ούτω καθεξής.

Η Nette διορθώνει για εσάς ευπάθειες ασφαλείας που οι περισσότεροι προγραμματιστές δεν έχουν ιδέα ότι υπάρχουν.

Η επίθεση CSRF που αναφέρθηκε είναι ότι ένας επιτιθέμενος παρασύρει το θύμα να επισκεφθεί μια σελίδα που εκτελεί σιωπηλά ένα αίτημα στο πρόγραμμα περιήγησης του θύματος προς τον διακομιστή όπου το θύμα είναι συνδεδεμένο αυτή τη στιγμή και ο διακομιστής πιστεύει ότι το αίτημα έγινε από το θύμα κατά βούληση. Ως εκ τούτου, η Nette εμποδίζει την υποβολή της φόρμας μέσω POST από άλλο τομέα. Αν για κάποιο λόγο θέλετε να απενεργοποιήσετε την προστασία και να επιτρέψετε την υποβολή της φόρμας από άλλο τομέα, χρησιμοποιήστε:

```php
$form->allowCrossOrigin(); // ΠΡΟΣΟΧΗ! Απενεργοποιεί την προστασία!
```

Αυτή η προστασία χρησιμοποιεί ένα cookie SameSite με όνομα `_nss`. Η προστασία των cookies SameSite μπορεί να μην είναι 100% αξιόπιστη, οπότε είναι καλή ιδέα να ενεργοποιήσετε την προστασία token:

```php
$form->addProtection();
```

Συνιστάται ιδιαίτερα να εφαρμόζετε αυτή την προστασία στις φόρμες σε ένα διοικητικό τμήμα της εφαρμογής σας που αλλάζει ευαίσθητα δεδομένα. Το πλαίσιο προστατεύει από μια επίθεση CSRF δημιουργώντας και επικυρώνοντας το διακριτικό ελέγχου ταυτότητας που αποθηκεύεται σε μια περίοδο λειτουργίας (το επιχείρημα είναι το μήνυμα σφάλματος που εμφανίζεται εάν το διακριτικό έχει λήξει). Γι' αυτό είναι απαραίτητο να έχει ξεκινήσει μια συνεδρία πριν από την εμφάνιση της φόρμας. Στο τμήμα διαχείρισης του ιστότοπου, η σύνοδος συνήθως έχει ήδη ξεκινήσει, λόγω της σύνδεσης του χρήστη.
Διαφορετικά, ξεκινήστε τη σύνοδο με τη μέθοδο `Nette\Http\Session::start()`.


Χρήση μιας φόρμας σε πολλούς παρουσιαστές .[#toc-using-one-form-in-multiple-presenters]
=======================================================================================

Εάν πρέπει να χρησιμοποιήσετε μια φόρμα σε περισσότερους από έναν παρουσιαστές, σας συνιστούμε να δημιουργήσετε ένα εργοστάσιο για αυτήν, το οποίο στη συνέχεια θα μεταβιβάσετε στον παρουσιαστή. Μια κατάλληλη τοποθεσία για μια τέτοια κλάση είναι, για παράδειγμα, ο κατάλογος `app/Forms`.

Η κλάση factory θα μπορούσε να μοιάζει ως εξής:

```php
use Nette\Application\UI\Form;

class SignInFormFactory
{
	public function create(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addSubmit('send', 'Log in');
		return $form;
	}
}
```

Ζητάμε από την κλάση να παράγει τη φόρμα στη μέθοδο factory για τα στοιχεία στον παρουσιαστή:

```php
public function __construct(
	private SignInFormFactory $formFactory,
) {
}

protected function createComponentSignInForm(): Form
{
	$form = $this->formFactory->create();
	// μπορούμε να αλλάξουμε τη φόρμα, εδώ για παράδειγμα αλλάζουμε την ετικέτα στο κουμπί
	$form['login']->setCaption('Continue');
	$form->onSuccess[] = [$this, 'signInFormSubmitted']; // και προσθέτουμε χειριστή
	return $form;
}
```

Ο χειριστής επεξεργασίας της φόρμας μπορεί επίσης να παραδοθεί από το εργοστάσιο:

```php
use Nette\Application\UI\Form;

class SignInFormFactory
{
	public function create(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addSubmit('send', 'Log in');
		$form->onSuccess[] = function (Form $form, $data): void {
			// επεξεργαζόμαστε την υποβληθείσα φόρμα μας εδώ
		};
		return $form;
	}
}
```

Έτσι, έχουμε μια γρήγορη εισαγωγή στις φόρμες στη Nette. Δοκιμάστε να κοιτάξετε στον κατάλογο [παραδειγμάτων |https://github.com/nette/forms/tree/master/examples] της διανομής για περισσότερη έμπνευση.

Φόρμες σε Παρουσιαστές

Η Nette Forms διευκολύνει δραματικά τη δημιουργία και την επεξεργασία φορμών ιστού. Σε αυτό το κεφάλαιο, θα μάθετε πώς να χρησιμοποιείτε φόρμες μέσα στους παρουσιαστές.

Αν σας ενδιαφέρει να τις χρησιμοποιήσετε εντελώς αυτόνομα χωρίς το υπόλοιπο πλαίσιο, υπάρχει ένας οδηγός για αυτόνομες φόρμες.

Πρώτη φόρμα

Θα προσπαθήσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα μοιάζει ως εξής:

use Nette\Application\UI\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');
$form->onSuccess[] = [$this, 'formSucceeded'];

και στο πρόγραμμα περιήγησης το αποτέλεσμα θα πρέπει να μοιάζει με αυτό:

Η φόρμα στον παρουσιαστή είναι ένα αντικείμενο της κλάσης Nette\Application\UI\Form, ο προκάτοχός της Nette\Forms\Form προορίζεται για αυτόνομη χρήση. Προσθέσαμε σε αυτήν τα πεδία name, password και το κουμπί αποστολής. Τέλος, η γραμμή με το $form->onSuccess λέει ότι μετά την υποβολή και την επιτυχή επικύρωση, θα πρέπει να κληθεί η μέθοδος $this->formSucceeded().

Από τη σκοπιά του παρουσιαστή, η φόρμα είναι ένα κοινό συστατικό. Ως εκ τούτου, αντιμετωπίζεται ως συστατικό και ενσωματώνεται στον παρουσιαστή χρησιμοποιώντας τη μέθοδο factory. Αυτό θα έχει την εξής μορφή:

use Nette;
use Nette\Application\UI\Form;

class HomePresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentRegistrationForm(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addPassword('password', 'Password:');
		$form->addSubmit('send', 'Sign up');
		$form->onSuccess[] = [$this, 'formSucceeded'];
		return $form;
	}

	public function formSucceeded(Form $form, $data): void
	{
		// εδώ θα επεξεργαστούμε τα δεδομένα που αποστέλλονται από τη φόρμα
		// $data->name περιέχει όνομα
		// $data->password περιέχει κωδικό πρόσβασης
		$this->flashMessage('You have successfully signed up.');
		$this->redirect('Home:');
	}
}

Και η απόδοση στο πρότυπο γίνεται με τη χρήση της ετικέτας {control}:

<h1>Registration</h1>

{control registrationForm}

Και αυτό είναι όλο :-) Έχουμε μια λειτουργική και απόλυτα ασφαλής φόρμα.

Τώρα πιθανόν να σκέφτεστε ότι ήταν πολύ γρήγορα, αναρωτιέστε πώς είναι δυνατόν να καλείται η μέθοδος formSucceeded() και ποιες είναι οι παράμετροι που παίρνει. Σίγουρα, έχετε δίκιο, αυτό αξίζει μια εξήγηση.

Η Nette καταλήγει σε έναν ωραίο μηχανισμό, τον οποίο ονομάζουμε Hollywood style. Αντί να χρειάζεται να ρωτάτε διαρκώς αν έχει συμβεί κάτι („υποβλήθηκε η φόρμα;“, „υποβλήθηκε έγκυρα;“ ή „δεν πλαστογραφήθηκε;“), λέτε στο πλαίσιο „όταν η φόρμα ολοκληρωθεί έγκυρα, καλέστε αυτή τη μέθοδο“ και αφήνετε την περαιτέρω εργασία σε αυτό. Αν προγραμματίζετε σε JavaScript, είστε εξοικειωμένοι με αυτό το στυλ προγραμματισμού. Γράφετε συναρτήσεις που καλούνται όταν συμβαίνει ένα συγκεκριμένο γεγονός. Και η γλώσσα περνάει σε αυτές τα κατάλληλα ορίσματα.

Με αυτόν τον τρόπο είναι δομημένος ο παραπάνω κώδικας του παρουσιαστή. Η συστοιχία $form->onSuccess αντιπροσωπεύει τη λίστα με τα callbacks της PHP που θα καλέσει η Nette όταν η φόρμα υποβληθεί και συμπληρωθεί σωστά. Μέσα στον κύκλο ζωής του παρουσιαστή είναι ένα λεγόμενο σήμα, οπότε καλούνται μετά τη μέθοδο action* και πριν τη μέθοδο render*. Και περνάει σε κάθε callback την ίδια τη φόρμα στην πρώτη παράμετρο και τα δεδομένα που αποστέλλονται ως αντικείμενο ArrayHash στη δεύτερη. Μπορείτε να παραλείψετε την πρώτη παράμετρο αν δεν χρειάζεστε το αντικείμενο της φόρμας. Η δεύτερη παράμετρος μπορεί να είναι ακόμη πιο χρήσιμη, αλλά γι' αυτό αργότερα.

Το αντικείμενο $data περιέχει τις ιδιότητες name και password με τα δεδομένα που εισήγαγε ο χρήστης. Συνήθως στέλνουμε τα δεδομένα απευθείας για περαιτέρω επεξεργασία, η οποία μπορεί να είναι, για παράδειγμα, η εισαγωγή στη βάση δεδομένων. Ωστόσο, μπορεί να προκύψει κάποιο σφάλμα κατά την επεξεργασία, για παράδειγμα, το όνομα χρήστη είναι ήδη κατειλημμένο. Σε αυτή την περίπτωση, περνάμε το σφάλμα πίσω στη φόρμα χρησιμοποιώντας το addError() και αφήνουμε να ξανασχεδιαστεί, με ένα μήνυμα σφάλματος:

$form->addError('Sorry, username is already in use.');

Εκτός από το onSuccess, υπάρχει και το onSubmit: τα callbacks καλούνται πάντα μετά την υποβολή της φόρμας, ακόμη και αν αυτή δεν έχει συμπληρωθεί σωστά. Και τέλος onError: τα callbacks καλούνται μόνο αν η υποβολή δεν είναι έγκυρη. Καλούνται ακόμη και αν ακυρώσουμε τη φόρμα στο onSuccess ή στο onSubmit χρησιμοποιώντας το addError().

Μετά την επεξεργασία της φόρμας, θα κάνουμε ανακατεύθυνση στην επόμενη σελίδα. Αυτό αποτρέπει την ακούσια επανυποβολή της φόρμας με το πάτημα του κουμπιού refresh, back, ή την μετακίνηση του ιστορικού του προγράμματος περιήγησης.

Δοκιμάστε να προσθέσετε περισσότερα στοιχεία ελέγχου φόρμας.

Πρόσβαση στα στοιχεία ελέγχου

Η φόρμα είναι ένα συστατικό του παρουσιαστή, στην περίπτωσή μας με το όνομα registrationForm (από το όνομα της μεθόδου κατασκευής createComponentRegistrationForm), οπότε οπουδήποτε στον παρουσιαστή μπορείτε να φτάσετε στη φόρμα χρησιμοποιώντας:

$form = $this->getComponent('registrationForm');
// εναλλακτική σύνταξη: Φόρμα εγγραφής'],

Επίσης, τα μεμονωμένα στοιχεία ελέγχου της φόρμας είναι συστατικά, οπότε μπορείτε να έχετε πρόσβαση σε αυτά με τον ίδιο τρόπο:

$input = $form->getComponent('name'); // ή $input = $form['name'],
$button = $form->getComponent('send'); // ή $button = $form['send'],

Τα στοιχεία ελέγχου αφαιρούνται με τη χρήση unset:

unset($form['name']);

Κανόνες επικύρωσης

Η λέξη valid χρησιμοποιήθηκε αρκετές φορές, αλλά η φόρμα δεν έχει ακόμη κανόνες επικύρωσης. Ας το διορθώσουμε.

Το όνομα θα είναι υποχρεωτικό, οπότε θα το επισημάνουμε με τη μέθοδο setRequired(), της οποίας το όρισμα είναι το κείμενο του μηνύματος λάθους που θα εμφανιστεί αν ο χρήστης δεν το συμπληρώσει. Αν δεν δοθεί κανένα όρισμα, χρησιμοποιείται το προεπιλεγμένο μήνυμα σφάλματος.

$form->addText('name', 'Name:')
	->setRequired('Please fill your name.');

Δοκιμάστε να στείλετε τη φόρμα χωρίς να έχει συμπληρωθεί το όνομα και θα δείτε ότι θα εμφανιστεί ένα μήνυμα σφάλματος και το πρόγραμμα περιήγησης ή ο διακομιστής θα την απορρίψει μέχρι να το συμπληρώσετε.

Ταυτόχρονα, δεν θα μπορείτε να εξαπατήσετε το σύστημα πληκτρολογώντας μόνο κενά στην είσοδο, για παράδειγμα. Με κανέναν τρόπο. Η Nette κόβει αυτόματα τα κενά αριστερά και δεξιά. Δοκιμάστε το. Είναι κάτι που πρέπει να κάνετε πάντα με κάθε είσοδο μιας γραμμής, αλλά συχνά ξεχνιέται. Η Nette το κάνει αυτόματα. (Μπορείτε να προσπαθήσετε να ξεγελάσετε τις φόρμες και να στείλετε μια πολυγραμμή συμβολοσειρά ως όνομα. Ακόμα και σε αυτή την περίπτωση, η Nette δεν θα ξεγελαστεί και τα διαλείμματα γραμμής θα μετατραπούν σε κενά).

Η φόρμα επικυρώνεται πάντα από την πλευρά του διακομιστή, αλλά δημιουργείται επίσης επικύρωση μέσω JavaScript, η οποία είναι γρήγορη και ο χρήστης γνωρίζει το σφάλμα αμέσως, χωρίς να χρειάζεται να στείλει τη φόρμα στον διακομιστή. Αυτό το χειρίζεται η δέσμη ενεργειών netteForms.js. Εισάγεται στο πρότυπο διάταξης:

<script src="https://unpkg.com/nette-forms@3/src/assets/netteForms.js"></script>

Αν κοιτάξετε στον πηγαίο κώδικα της σελίδας με τη φόρμα, μπορεί να παρατηρήσετε ότι η Nette εισάγει τα απαιτούμενα πεδία σε στοιχεία με κλάση CSS required. Δοκιμάστε να προσθέσετε το ακόλουθο στυλ στο πρότυπο, και η ετικέτα „Όνομα“ θα είναι κόκκινη. Κομψά, επισημαίνουμε τα υποχρεωτικά πεδία για τους χρήστες:

<style>
.required label { color: maroon }
</style>

Πρόσθετοι κανόνες επικύρωσης θα προστεθούν με τη μέθοδο addRule(). Η πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι και πάλι το κείμενο του μηνύματος σφάλματος και μπορεί να ακολουθήσει το προαιρετικό όρισμα του κανόνα επικύρωσης. Τι σημαίνει αυτό;

Η φόρμα θα λάβει μια άλλη προαιρετική είσοδο ηλικία με την προϋπόθεση, ότι πρέπει να είναι αριθμός (addInteger()) και σε συγκεκριμένα όρια ($form::Range). Και εδώ θα χρησιμοποιήσουμε το τρίτο όρισμα του addRule(), το ίδιο το εύρος:

$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);

Εάν ο χρήστης δεν συμπληρώσει το πεδίο, οι κανόνες επικύρωσης δεν θα επαληθευτούν, επειδή το πεδίο είναι προαιρετικό.

Προφανώς υπάρχει περιθώριο για ένα μικρό refactoring. Στο μήνυμα σφάλματος και στην τρίτη παράμετρο, οι αριθμοί παρατίθενται εις διπλούν, πράγμα που δεν είναι ιδανικό. Αν δημιουργούσαμε μια πολύγλωσση φόρμα και το μήνυμα που περιέχει αριθμούς θα έπρεπε να μεταφραστεί σε πολλές γλώσσες, θα δυσκόλευε την αλλαγή των τιμών. Για το λόγο αυτό, μπορούν να χρησιμοποιηθούν οι υποκατάστατοι χαρακτήρες %d:

	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);

Ας επιστρέψουμε στο πεδίο password, ας το κάνουμε απαιτούμενο και ας ελέγξουμε το ελάχιστο μήκος του κωδικού πρόσβασης ($form::MinLength), χρησιμοποιώντας και πάλι τους χαρακτήρες αντικατάστασης στο μήνυμα:

$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);

Θα προσθέσουμε ένα πεδίο passwordVerify στη φόρμα, όπου ο χρήστης θα εισάγει ξανά τον κωδικό πρόσβασης, για έλεγχο. Χρησιμοποιώντας κανόνες επικύρωσης, ελέγχουμε αν και οι δύο κωδικοί πρόσβασης είναι ίδιοι ($form::Equal). Και ως όρισμα δίνουμε μια αναφορά στον πρώτο κωδικό πρόσβασης χρησιμοποιώντας αγκύλες:

$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();

Χρησιμοποιώντας το setOmitted(), επισημαίνουμε ένα στοιχείο του οποίου η τιμή δεν μας ενδιαφέρει πραγματικά και το οποίο υπάρχει μόνο για επικύρωση. Η τιμή του δεν περνάει στο $data.

Έχουμε μια πλήρως λειτουργική φόρμα με επικύρωση σε PHP και JavaScript. Οι δυνατότητες επικύρωσης της Nette είναι πολύ ευρύτερες, μπορείτε να δημιουργήσετε συνθήκες, να εμφανίσετε και να αποκρύψετε τμήματα μιας σελίδας σύμφωνα με αυτές, κ.λπ. Μπορείτε να μάθετε τα πάντα στο κεφάλαιο για την επικύρωση φορμών.

Προεπιλεγμένες τιμές

Συχνά ορίζουμε προεπιλεγμένες τιμές για τα στοιχεία ελέγχου φόρμας:

$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);

Συχνά είναι χρήσιμο να ορίσουμε προεπιλεγμένες τιμές για όλα τα στοιχεία ελέγχου ταυτόχρονα. Για παράδειγμα, όταν η φόρμα χρησιμοποιείται για την επεξεργασία εγγραφών. Διαβάζουμε την εγγραφή από τη βάση δεδομένων και την ορίζουμε ως προεπιλεγμένες τιμές:

//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);

Καλέστε το setDefaults() μετά τον ορισμό των στοιχείων ελέγχου.

Απεικόνιση της φόρμας

Από προεπιλογή, η φόρμα απεικονίζεται ως πίνακας. Τα επιμέρους στοιχεία ελέγχου ακολουθούν τις βασικές οδηγίες προσβασιμότητας ιστού. Όλες οι ετικέτες παράγονται ως <label> στοιχεία και συνδέονται με τις εισόδους τους, κάνοντας κλικ στην ετικέτα μετακινείται ο δρομέας στην είσοδο.

Μπορούμε να ορίσουμε οποιαδήποτε χαρακτηριστικά HTML για κάθε στοιχείο. Για παράδειγμα, να προσθέσουμε ένα placeholder:

$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');

Υπάρχουν πραγματικά πολλοί τρόποι για την απόδοση μιας φόρμας, γι' αυτό και είναι αφιερωμένο κεφάλαιο για την απόδοση.

Χαρτογράφηση σε κλάσεις

Ας επιστρέψουμε στη μέθοδο formSucceeded(), η οποία στη δεύτερη παράμετρο $data λαμβάνει τα δεδομένα που αποστέλλονται ως αντικείμενο ArrayHash. Επειδή πρόκειται για μια γενική κλάση, κάτι σαν το stdClass, θα μας λείψουν κάποιες ευκολίες κατά την εργασία με αυτήν, όπως η συμπλήρωση κώδικα για ιδιότητες σε επεξεργαστές ή στατική ανάλυση κώδικα. Αυτό θα μπορούσε να λυθεί με την ύπαρξη μιας συγκεκριμένης κλάσης για κάθε φόρμα, της οποίας οι ιδιότητες αντιπροσωπεύουν τα επιμέρους στοιχεία ελέγχου. Π.χ:

class RegistrationFormData
{
	public string $name;
	public int $age;
	public string $password;
}

Από την PHP 8.0, μπορείτε να χρησιμοποιήσετε αυτόν τον κομψό συμβολισμό που χρησιμοποιεί έναν κατασκευαστή:

class RegistrationFormData
{
	public function __construct(
		public string $name,
		public int $age,
		public string $password,
	) {
	}
}

Πώς μπορείτε να πείτε στη Nette να επιστρέφει δεδομένα ως αντικείμενα αυτής της κλάσης; Πιο εύκολα απ' ό,τι νομίζετε. Το μόνο που έχετε να κάνετε είναι να καθορίσετε την κλάση ως τύπο της παραμέτρου $data στον χειριστή:

public function formSucceeded(Form $form, RegistrationFormData $data): void
{
	// $name είναι instance of RegistrationFormData
	$name = $data->name;
	// ...
}

Μπορείτε επίσης να καθορίσετε το array ως τύπο και τότε θα περάσει τα δεδομένα ως πίνακα.

Με παρόμοιο τρόπο, μπορείτε να χρησιμοποιήσετε τη μέθοδο getValues(), την οποία περνάμε ως όνομα κλάσης ή αντικειμένου προς ενυδάτωση ως παράμετρο:

$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;

Εάν οι φόρμες αποτελούνται από μια δομή πολλαπλών επιπέδων που αποτελείται από δοχεία, δημιουργήστε μια ξεχωριστή κλάση για κάθε ένα από αυτά:

$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */

class PersonFormData
{
	public string $firstName;
	public string $lastName;
}

class RegistrationFormData
{
	public PersonFormData $person;
	public int $age;
	public string $password;
}

Η αντιστοίχιση γνωρίζει τότε από τον τύπο της ιδιότητας $person ότι πρέπει να αντιστοιχίσει το δοχείο στην κλάση PersonFormData. Εάν η ιδιότητα θα περιέχει έναν πίνακα από δοχεία, δώστε τον τύπο array και περάστε την κλάση που θα αντιστοιχιστεί απευθείας στο δοχείο:

$person->setMappedType(PersonFormData::class);

Μπορείτε να δημιουργήσετε μια πρόταση για την κλάση δεδομένων μιας φόρμας χρησιμοποιώντας τη μέθοδο Nette\Forms\Blueprint::dataClass($form), η οποία θα την εκτυπώσει στη σελίδα του προγράμματος περιήγησης. Στη συνέχεια, μπορείτε απλά να κάνετε κλικ για να επιλέξετε και να αντιγράψετε τον κώδικα στο έργο σας.

Πολλαπλά κουμπιά υποβολής

Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως πρέπει να διακρίνουμε ποιο από αυτά έχει πατηθεί. Μπορούμε να δημιουργήσουμε δική μας συνάρτηση για κάθε κουμπί. Ορίστε την ως χειριστή για το συμβάν onClick:

$form->addSubmit('save', 'Save')
	->onClick[] = [$this, 'saveButtonPressed'];

$form->addSubmit('delete', 'Delete')
	->onClick[] = [$this, 'deleteButtonPressed'];

Αυτοί οι χειριστές καλούνται επίσης μόνο στην περίπτωση που η φόρμα είναι έγκυρη, όπως στην περίπτωση του συμβάντος onSuccess. Η διαφορά είναι ότι η πρώτη παράμετρος μπορεί να είναι το αντικείμενο submit button αντί της φόρμας, ανάλογα με τον τύπο που καθορίζετε:

public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data)
{
	$form = $button->getForm();
	// ...
}

Όταν μια φόρμα υποβάλλεται με το πλήκτρο Enter, αντιμετωπίζεται σαν να είχε υποβληθεί με το πρώτο κουμπί.

Γεγονός onAnchor

Όταν κατασκευάζετε μια φόρμα σε μια εργοστασιακή μέθοδο (όπως η createComponentRegistrationForm), δεν γνωρίζει ακόμα αν έχει υποβληθεί ή τα δεδομένα με τα οποία υποβλήθηκε. Υπάρχουν όμως περιπτώσεις όπου πρέπει να γνωρίζουμε τις τιμές που έχουν υποβληθεί, ίσως εξαρτάται από αυτές το πώς θα είναι η μορφή της φόρμας, ή χρησιμοποιούνται για εξαρτημένα selectboxes, κλπ.

Επομένως, μπορείτε να έχετε τον κώδικα που κατασκευάζει τη φόρμα να καλείται όταν αυτή είναι αγκυρωμένη, δηλαδή είναι ήδη συνδεδεμένη με τον παρουσιαστή και γνωρίζει τα υποβληθέντα δεδομένα της. Θα τοποθετήσουμε έναν τέτοιο κώδικα στον πίνακα $onAnchor:

$country = $form->addSelect('country', 'Country:', $this->model->getCountries());
$city = $form->addSelect('city', 'City:');

$form->onAnchor[] = function () use ($country, $city) {
	// αυτή η συνάρτηση θα κληθεί όταν η φόρμα γνωρίζει τα δεδομένα με τα οποία υποβλήθηκε
	// έτσι ώστε να μπορείτε να χρησιμοποιήσετε τη μέθοδο getValue()
	$val = $country->getValue();
	$city->setItems($val ? $this->model->getCities($val): []);
};

Vulnerability Protection

Το Nette Framework καταβάλλει μεγάλη προσπάθεια για να είναι ασφαλές και δεδομένου ότι οι φόρμες είναι η πιο συνηθισμένη είσοδος του χρήστη, οι φόρμες του Nette είναι σχεδόν αδιαπέραστες. Όλα διατηρούνται δυναμικά και διαφανώς, τίποτα δεν χρειάζεται να ρυθμιστεί χειροκίνητα.

Εκτός από την προστασία των φορμών από επιθέσεις που στοχεύουν σε γνωστές ευπάθειες όπως Cross-Site Scripting (XSS) και Cross-Site Request Forgery (CSRF), κάνει πολλές μικρές εργασίες ασφαλείας που δεν χρειάζεται πλέον να σκέφτεστε.

Για παράδειγμα, φιλτράρει όλους τους χαρακτήρες ελέγχου από τις εισόδους και ελέγχει την εγκυρότητα της κωδικοποίησης UTF-8, ώστε τα δεδομένα από τη φόρμα να είναι πάντα καθαρά. Για τα πλαίσια επιλογής και τις λίστες επιλογής, επαληθεύει ότι τα επιλεγμένα στοιχεία ήταν όντως από τα προσφερόμενα και ότι δεν υπήρξε πλαστογράφηση. Έχουμε ήδη αναφέρει ότι για την εισαγωγή κειμένου σε μία γραμμή, αφαιρεί τους χαρακτήρες τέλους γραμμής που θα μπορούσε να στείλει εκεί ένας εισβολέας. Για εισόδους πολλαπλών γραμμών, ομαλοποιεί τους χαρακτήρες στο τέλος της γραμμής. Και ούτω καθεξής.

Η Nette διορθώνει για εσάς ευπάθειες ασφαλείας που οι περισσότεροι προγραμματιστές δεν έχουν ιδέα ότι υπάρχουν.

Η επίθεση CSRF που αναφέρθηκε είναι ότι ένας επιτιθέμενος παρασύρει το θύμα να επισκεφθεί μια σελίδα που εκτελεί σιωπηλά ένα αίτημα στο πρόγραμμα περιήγησης του θύματος προς τον διακομιστή όπου το θύμα είναι συνδεδεμένο αυτή τη στιγμή και ο διακομιστής πιστεύει ότι το αίτημα έγινε από το θύμα κατά βούληση. Ως εκ τούτου, η Nette εμποδίζει την υποβολή της φόρμας μέσω POST από άλλο τομέα. Αν για κάποιο λόγο θέλετε να απενεργοποιήσετε την προστασία και να επιτρέψετε την υποβολή της φόρμας από άλλο τομέα, χρησιμοποιήστε:

$form->allowCrossOrigin(); // ΠΡΟΣΟΧΗ! Απενεργοποιεί την προστασία!

Αυτή η προστασία χρησιμοποιεί ένα cookie SameSite με όνομα _nss. Η προστασία των cookies SameSite μπορεί να μην είναι 100% αξιόπιστη, οπότε είναι καλή ιδέα να ενεργοποιήσετε την προστασία token:

$form->addProtection();

Συνιστάται ιδιαίτερα να εφαρμόζετε αυτή την προστασία στις φόρμες σε ένα διοικητικό τμήμα της εφαρμογής σας που αλλάζει ευαίσθητα δεδομένα. Το πλαίσιο προστατεύει από μια επίθεση CSRF δημιουργώντας και επικυρώνοντας το διακριτικό ελέγχου ταυτότητας που αποθηκεύεται σε μια περίοδο λειτουργίας (το επιχείρημα είναι το μήνυμα σφάλματος που εμφανίζεται εάν το διακριτικό έχει λήξει). Γι' αυτό είναι απαραίτητο να έχει ξεκινήσει μια συνεδρία πριν από την εμφάνιση της φόρμας. Στο τμήμα διαχείρισης του ιστότοπου, η σύνοδος συνήθως έχει ήδη ξεκινήσει, λόγω της σύνδεσης του χρήστη. Διαφορετικά, ξεκινήστε τη σύνοδο με τη μέθοδο Nette\Http\Session::start().

Χρήση μιας φόρμας σε πολλούς παρουσιαστές

Εάν πρέπει να χρησιμοποιήσετε μια φόρμα σε περισσότερους από έναν παρουσιαστές, σας συνιστούμε να δημιουργήσετε ένα εργοστάσιο για αυτήν, το οποίο στη συνέχεια θα μεταβιβάσετε στον παρουσιαστή. Μια κατάλληλη τοποθεσία για μια τέτοια κλάση είναι, για παράδειγμα, ο κατάλογος app/Forms.

Η κλάση factory θα μπορούσε να μοιάζει ως εξής:

use Nette\Application\UI\Form;

class SignInFormFactory
{
	public function create(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addSubmit('send', 'Log in');
		return $form;
	}
}

Ζητάμε από την κλάση να παράγει τη φόρμα στη μέθοδο factory για τα στοιχεία στον παρουσιαστή:

public function __construct(
	private SignInFormFactory $formFactory,
) {
}

protected function createComponentSignInForm(): Form
{
	$form = $this->formFactory->create();
	// μπορούμε να αλλάξουμε τη φόρμα, εδώ για παράδειγμα αλλάζουμε την ετικέτα στο κουμπί
	$form['login']->setCaption('Continue');
	$form->onSuccess[] = [$this, 'signInFormSubmitted']; // και προσθέτουμε χειριστή
	return $form;
}

Ο χειριστής επεξεργασίας της φόρμας μπορεί επίσης να παραδοθεί από το εργοστάσιο:

use Nette\Application\UI\Form;

class SignInFormFactory
{
	public function create(): Form
	{
		$form = new Form;
		$form->addText('name', 'Name:');
		$form->addSubmit('send', 'Log in');
		$form->onSuccess[] = function (Form $form, $data): void {
			// επεξεργαζόμαστε την υποβληθείσα φόρμα μας εδώ
		};
		return $form;
	}
}

Έτσι, έχουμε μια γρήγορη εισαγωγή στις φόρμες στη Nette. Δοκιμάστε να κοιτάξετε στον κατάλογο παραδειγμάτων της διανομής για περισσότερη έμπνευση.