Φόρμες στους presenters
Οι Nette Forms διευκολύνουν κατά πολύ τη δημιουργία και την επεξεργασία φορμών ιστού. Σε αυτό το κεφάλαιο, θα μάθετε πώς να χρησιμοποιείτε φόρμες μέσα στους presenters.
Αν ενδιαφέρεστε για το πώς να τις χρησιμοποιήσετε εντελώς αυτόνομα χωρίς το υπόλοιπο framework, ο οδηγός για αυτόνομη χρήση είναι για εσάς.
Η πρώτη φόρμα
Ας δοκιμάσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα είναι ο εξής:
use Nette\Application\UI\Form;
$form = new Form;
$form->addText('name', 'Όνομα:');
$form->addPassword('password', 'Κωδικός πρόσβασης:');
$form->addSubmit('send', 'Εγγραφή');
$form->onSuccess[] = [$this, 'formSucceeded'];
και στον περιηγητή θα εμφανιστεί έτσι:

Μια φόρμα σε έναν presenter είναι ένα αντικείμενο της κλάσης
Nette\Application\UI\Form
, ο προκάτοχός της Nette\Forms\Form
προορίζεται για
αυτόνομη χρήση. Προσθέσαμε σε αυτήν τα λεγόμενα στοιχεία όνομα, κωδικό
πρόσβασης και ένα κουμπί υποβολής. Και τέλος, η γραμμή με το
$form->onSuccess
λέει ότι μετά την υποβολή και την επιτυχή επικύρωση,
πρέπει να κληθεί η μέθοδος $this->formSucceeded()
.
Από την οπτική γωνία του presenter, η φόρμα είναι ένα συνηθισμένο component. Επομένως, αντιμετωπίζεται ως component και την ενσωματώνουμε στον presenter χρησιμοποιώντας factory methods. Θα μοιάζει κάπως έτσι:
use Nette;
use Nette\Application\UI\Form;
class HomePresenter extends Nette\Application\UI\Presenter
{
protected function createComponentRegistrationForm(): Form
{
$form = new Form;
$form->addText('name', 'Όνομα:');
$form->addPassword('password', 'Κωδικός πρόσβασης:');
$form->addSubmit('send', 'Εγγραφή');
$form->onSuccess[] = [$this, 'formSucceeded'];
return $form;
}
public function formSucceeded(Form $form, $data): void
{
// εδώ επεξεργαζόμαστε τα δεδομένα που υποβλήθηκαν από τη φόρμα
// το $data->name περιέχει το όνομα
// το $data->password περιέχει τον κωδικό πρόσβασης
$this->flashMessage('Εγγραφήκατε με επιτυχία.');
$this->redirect('Home:');
}
}
Και στο template, αποδίδουμε τη φόρμα με την ετικέτα {control}
:
<h1>Εγγραφή</h1>
{control registrationForm}
Και αυτό είναι όλο :-) Έχουμε μια λειτουργική και τέλεια ασφαλή φόρμα.
Και τώρα πιθανότατα σκέφτεστε ότι αυτό ήταν πολύ γρήγορο,
αναρωτιέστε πώς είναι δυνατόν να κληθεί η μέθοδος formSucceeded()
και
ποιες είναι οι παράμετροι που λαμβάνει. Σίγουρα, έχετε δίκιο, αυτό
αξίζει εξήγηση.
Η Nette εισάγει έναν φρέσκο μηχανισμό, τον οποίο ονομάζουμε Hollywood style. Αντί εσείς, ως προγραμματιστής, να πρέπει συνεχώς να ρωτάτε αν συνέβη κάτι («υποβλήθηκε η φόρμα;», «υποβλήθηκε έγκυρα;» και «δεν παραποιήθηκε;»), λέτε στο framework «όταν η φόρμα συμπληρωθεί έγκυρα, κάλεσε αυτή τη μέθοδο» και αφήνετε την υπόλοιπη δουλειά σε αυτό. Αν προγραμματίζετε σε JavaScript, αυτό το στυλ προγραμματισμού σας είναι πολύ οικείο. Γράφετε συναρτήσεις που καλούνται όταν συμβεί ένα συγκεκριμένο γεγονός. Και η γλώσσα τους περνά τα κατάλληλα ορίσματα.
Ακριβώς έτσι είναι δομημένος και ο παραπάνω κώδικας του presenter. Ο
πίνακας $form->onSuccess
αντιπροσωπεύει μια λίστα από PHP callbacks που η
Nette καλεί τη στιγμή που η φόρμα υποβάλλεται και συμπληρώνεται σωστά
(δηλαδή είναι έγκυρη). Στο πλαίσιο του κύκλου ζωής του presenter
πρόκειται για ένα λεγόμενο σήμα, οπότε καλούνται μετά τη μέθοδο
action*
και πριν από τη μέθοδο render*
. Και σε κάθε callback, περνά ως
πρώτη παράμετρο την ίδια τη φόρμα και ως δεύτερη τα υποβληθέντα
δεδομένα με τη μορφή ενός αντικειμένου ArrayHash. Μπορείτε να παραλείψετε την
πρώτη παράμετρο αν δεν χρειάζεστε το αντικείμενο της φόρμας. Και η
δεύτερη παράμετρος μπορεί να είναι πιο έξυπνη, αλλά γι' αυτό θα
μιλήσουμε αργότερα.
Το αντικείμενο $data
περιέχει τα κλειδιά name
και
password
με τα δεδομένα που συμπλήρωσε ο χρήστης. Συνήθως, στέλνουμε
αμέσως τα δεδομένα για περαιτέρω επεξεργασία, η οποία μπορεί να είναι,
για παράδειγμα, η εισαγωγή στη βάση δεδομένων. Ωστόσο, κατά την
επεξεργασία μπορεί να προκύψει σφάλμα, για παράδειγμα, το όνομα χρήστη
είναι ήδη κατειλημμένο. Σε αυτή την περίπτωση, επιστρέφουμε το σφάλμα
στη φόρμα χρησιμοποιώντας την addError()
και την αφήνουμε να
αποδοθεί ξανά, μαζί με το μήνυμα σφάλματος.
$form->addError('Λυπούμαστε, το όνομα χρήστη χρησιμοποιείται ήδη.');
Εκτός από το onSuccess
, υπάρχει επίσης το onSubmit
: τα callbacks
καλούνται πάντα μετά την υποβολή της φόρμας, ακόμη και αν δεν έχει
συμπληρωθεί σωστά. Και επίσης το onError
: τα callbacks καλούνται μόνο αν
η υποβολή δεν είναι έγκυρη. Καλούνται ακόμη και αν στο onSuccess
ή
στο onSubmit
ακυρώσουμε την εγκυρότητα της φόρμας χρησιμοποιώντας
την addError()
.
Μετά την επεξεργασία της φόρμας, ανακατευθύνουμε σε άλλη σελίδα. Αυτό αποτρέπει την ακούσια επανυποβολή της φόρμας με το κουμπί ανανέωση, πίσω ή με την κίνηση στο ιστορικό του περιηγητή.
Δοκιμάστε να προσθέσετε και άλλα στοιχεία φόρμας.
Πρόσβαση στα στοιχεία
Η φόρμα είναι ένα component του presenter, στην περίπτωσή μας ονομάζεται
registrationForm
(από το όνομα της factory method createComponentRegistrationForm
),
οπότε οπουδήποτε στον presenter μπορείτε να αποκτήσετε πρόσβαση στη φόρμα
χρησιμοποιώντας:
$form = $this->getComponent('registrationForm');
// εναλλακτική σύνταξη: $form = $this['registrationForm'];
Τα μεμονωμένα στοιχεία της φόρμας είναι επίσης components, επομένως μπορείτε να αποκτήσετε πρόσβαση σε αυτά με τον ίδιο τρόπο:
$input = $form->getComponent('name'); // ή $input = $form['name'];
$button = $form->getComponent('send'); // ή $button = $form['send'];
Τα στοιχεία αφαιρούνται χρησιμοποιώντας το unset:
unset($form['name']);
Κανόνες επικύρωσης
Αναφέρθηκε η λέξη έγκυρη, αλλά η φόρμα δεν έχει ακόμη κανόνες επικύρωσης. Ας το διορθώσουμε αυτό.
Το όνομα θα είναι υποχρεωτικό, γι' αυτό το επισημαίνουμε με τη μέθοδο
setRequired()
, το όρισμα της οποίας είναι το κείμενο του μηνύματος
σφάλματος που θα εμφανιστεί εάν ο χρήστης δεν συμπληρώσει το όνομα. Εάν
δεν παρέχουμε όρισμα, θα χρησιμοποιηθεί το προεπιλεγμένο μήνυμα
σφάλματος.
$form->addText('name', 'Όνομα:')
->setRequired('Παρακαλώ εισάγετε ένα όνομα');
Δοκιμάστε να υποβάλετε τη φόρμα χωρίς να συμπληρώσετε το όνομα και θα δείτε ότι θα εμφανιστεί ένα μήνυμα σφάλματος και ο περιηγητής ή ο διακομιστής θα την απορρίπτει μέχρι να συμπληρώσετε το πεδίο.
Ταυτόχρονα, δεν μπορείτε να ξεγελάσετε το σύστημα γράφοντας, για παράδειγμα, μόνο κενά στο πεδίο. Όχι. Η Nette αφαιρεί αυτόματα τα κενά στην αρχή και στο τέλος. Δοκιμάστε το. Είναι κάτι που πρέπει πάντα να κάνετε με κάθε input μίας γραμμής, αλλά συχνά ξεχνιέται. Η Nette το κάνει αυτόματα. (Μπορείτε να δοκιμάσετε να ξεγελάσετε τη φόρμα και να στείλετε μια συμβολοσειρά πολλών γραμμών ως όνομα. Ούτε εδώ η Nette δεν θα μπερδευτεί και θα αλλάξει τις αλλαγές γραμμής σε κενά.)
Η φόρμα επικυρώνεται πάντα από την πλευρά του διακομιστή, αλλά
δημιουργείται επίσης επικύρωση JavaScript, η οποία εκτελείται αστραπιαία
και ο χρήστης ενημερώνεται για το σφάλμα αμέσως, χωρίς να χρειάζεται να
υποβάλει τη φόρμα στον διακομιστή. Αυτό το χειρίζεται το script
netteForms.js
. Εισαγάγετέ το στο template του layout:
<script src="https://unpkg.com/nette-forms@3"></script>
Αν κοιτάξετε τον πηγαίο κώδικα της σελίδας με τη φόρμα, μπορείτε να
παρατηρήσετε ότι η Nette εισάγει τα υποχρεωτικά στοιχεία σε στοιχεία με
την κλάση CSS required
. Δοκιμάστε να προσθέσετε το ακόλουθο φύλλο
στυλ στο template και η ετικέτα “Όνομα” θα γίνει κόκκινη. Με αυτόν τον
κομψό τρόπο, επισημαίνουμε τα υποχρεωτικά στοιχεία στους χρήστες:
<style>
.required label { color: maroon }
</style>
Προσθέτουμε περαιτέρω κανόνες επικύρωσης με τη μέθοδο addRule()
. Η
πρώτη παράμετρος είναι ο κανόνας, η δεύτερη είναι πάλι το κείμενο του
μηνύματος σφάλματος και μπορεί να ακολουθήσει ένα όρισμα του κανόνα
επικύρωσης. Τι σημαίνει αυτό;
Θα επεκτείνουμε τη φόρμα με ένα νέο προαιρετικό πεδίο “ηλικία”, το
οποίο πρέπει να είναι ακέραιος αριθμός (addInteger()
) και επιπλέον
εντός επιτρεπτού εύρους ($form::Range
). Και εδώ ακριβώς θα
χρησιμοποιήσουμε την τρίτη παράμετρο της μεθόδου addRule()
, με την
οποία περνάμε στον επικυρωτή το απαιτούμενο εύρος ως ζεύγος
[από, έως]
:
$form->addInteger('age', 'Ηλικία:')
->addRule($form::Range, 'Η ηλικία πρέπει να είναι από 18 έως 120', [18, 120]);
Εάν ο χρήστης δεν συμπληρώσει το πεδίο, οι κανόνες επικύρωσης δεν θα ελεγχθούν, καθώς το στοιχείο είναι προαιρετικό.
Εδώ υπάρχει χώρος για μια μικρή αναδιάρθρωση (refactoring). Στο μήνυμα
σφάλματος και στην τρίτη παράμετρο, οι αριθμοί αναφέρονται διπλά,
πράγμα που δεν είναι ιδανικό. Αν δημιουργούσαμε πολύγλωσσες φόρμες και το μήνυμα
που περιέχει αριθμούς μεταφραζόταν σε πολλές γλώσσες, θα δυσκόλευε μια
πιθανή αλλαγή των τιμών. Για το λόγο αυτό, είναι δυνατόν να
χρησιμοποιηθούν οι χαρακτήρες υποκατάστασης %d
και η Nette θα
συμπληρώσει τις τιμές:
->addRule($form::Range, 'Η ηλικία πρέπει να είναι από %d έως %d ετών', [18, 120]);
Ας επιστρέψουμε στο στοιχείο password
, το οποίο θα καταστήσουμε
επίσης υποχρεωτικό και θα ελέγξουμε επιπλέον το ελάχιστο μήκος του
κωδικού πρόσβασης ($form::MinLength
), χρησιμοποιώντας πάλι τον
χαρακτήρα υποκατάστασης:
$form->addPassword('password', 'Κωδικός πρόσβασης:')
->setRequired('Επιλέξτε έναν κωδικό πρόσβασης')
->addRule($form::MinLength, 'Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον %d χαρακτήρες', 8);
Θα προσθέσουμε στη φόρμα ένα ακόμη πεδίο passwordVerify
, όπου ο
χρήστης θα εισάγει τον κωδικό πρόσβασης ξανά, για έλεγχο.
Χρησιμοποιώντας κανόνες επικύρωσης, θα ελέγξουμε αν οι δύο κωδικοί
πρόσβασης είναι ίδιοι ($form::Equal
). Και ως παράμετρο θα δώσουμε μια
αναφορά στον πρώτο κωδικό πρόσβασης χρησιμοποιώντας αγκύλες:
$form->addPassword('passwordVerify', 'Κωδικός πρόσβασης για έλεγχο:')
->setRequired('Παρακαλώ εισάγετε τον κωδικό πρόσβασης ξανά για έλεγχο')
->addRule($form::Equal, 'Οι κωδικοί πρόσβασης δεν ταιριάζουν', $form['password'])
->setOmitted();
Με τη χρήση της setOmitted()
, επισημάναμε το στοιχείο του οποίου η
τιμή στην πραγματικότητα δεν μας ενδιαφέρει και το οποίο υπάρχει μόνο
για λόγους επικύρωσης. Η τιμή δεν θα περάσει στο $data
.
Με αυτό, έχουμε μια πλήρως λειτουργική φόρμα με επικύρωση σε PHP και JavaScript. Οι δυνατότητες επικύρωσης της Nette είναι πολύ ευρύτερες, μπορούν να δημιουργηθούν συνθήκες, να εμφανίζονται και να αποκρύπτονται τμήματα της σελίδας βάσει αυτών, κ.λπ. Όλα θα τα μάθετε στο κεφάλαιο για την επικύρωση φορμών.
Προεπιλεγμένες τιμές
Συνήθως ορίζουμε προεπιλεγμένες τιμές για τα στοιχεία της φόρμας:
$form->addEmail('email', 'E-mail')
->setDefaultValue($lastUsedEmail);
Συχνά είναι χρήσιμο να ορίσουμε προεπιλεγμένες τιμές για όλα τα στοιχεία ταυτόχρονα. Για παράδειγμα, όταν η φόρμα χρησιμοποιείται για την επεξεργασία εγγραφών. Διαβάζουμε την εγγραφή από τη βάση δεδομένων και ορίζουμε τις προεπιλεγμένες τιμές:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Καλέστε την setDefaults()
μετά τον ορισμό των στοιχείων.
Απόδοση φόρμας
Από προεπιλογή, η φόρμα αποδίδεται ως πίνακας. Τα μεμονωμένα στοιχεία
πληρούν τον βασικό κανόνα προσβασιμότητας – όλες οι ετικέτες
γράφονται ως <label>
και συνδέονται με το αντίστοιχο στοιχείο
της φόρμας. Όταν κάνετε κλικ στην ετικέτα, ο δρομέας εμφανίζεται
αυτόματα στο πεδίο της φόρμας.
Μπορούμε να ορίσουμε οποιαδήποτε HTML attributes για κάθε στοιχείο. Για παράδειγμα, να προσθέσουμε ένα placeholder:
$form->addInteger('age', 'Ηλικία:')
->setHtmlAttribute('placeholder', 'Παρακαλώ συμπληρώστε την ηλικία');
Υπάρχουν πραγματικά πολλοί τρόποι για να αποδοθεί μια φόρμα, γι' αυτό υπάρχει ένα ξεχωριστό κεφάλαιο για την απόδοση.
Αντιστοίχιση σε κλάσεις
Ας επιστρέψουμε στη μέθοδο formSucceeded()
, η οποία στη δεύτερη
παράμετρο $data
λαμβάνει τα υποβληθέντα δεδομένα ως αντικείμενο
ArrayHash
. Επειδή πρόκειται για μια γενική κλάση, κάτι σαν
stdClass
, θα μας λείψει κάποια άνεση κατά την εργασία μαζί της, όπως
η πρόταση properties στους επεξεργαστές ή η στατική ανάλυση κώδικα. Αυτό θα
μπορούσε να λυθεί έχοντας μια συγκεκριμένη κλάση για κάθε φόρμα, της
οποίας οι properties αντιπροσωπεύουν τα μεμονωμένα στοιχεία. Π.χ.:
class RegistrationFormData
{
public string $name;
public ?int $age;
public string $password;
}
Εναλλακτικά, μπορείτε να χρησιμοποιήσετε τον κατασκευαστή:
class RegistrationFormData
{
public function __construct(
public string $name,
public int $age,
public string $password,
) {
}
}
Οι ιδιότητες της κλάσης δεδομένων μπορούν επίσης να είναι enum και θα αντιστοιχιστούν αυτόματα.
Πώς να πούμε στη Nette να μας επιστρέφει τα δεδομένα ως αντικείμενα
αυτής της κλάσης; Πιο εύκολα από ό,τι νομίζετε. Αρκεί απλώς να δηλώσετε
την κλάση ως τύπο της παραμέτρου $data
στη μέθοδο χειρισμού:
public function formSucceeded(Form $form, RegistrationFormData $data): void
{
// το $data είναι μια παρουσία του RegistrationFormData
$name = $data->name;
// ...
}
Ως τύπος μπορεί επίσης να δηλωθεί το array
και τότε τα δεδομένα
θα περάσουν ως πίνακας.
Με παρόμοιο τρόπο μπορεί να χρησιμοποιηθεί και η συνάρτηση
getValues()
, στην οποία περνάμε το όνομα της κλάσης ή το αντικείμενο
προς ενυδάτωση ως παράμετρο:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
Εάν οι φόρμες σχηματίζουν μια πολυεπίπεδη δομή αποτελούμενη από containers, δημιουργήστε μια ξεχωριστή κλάση για καθένα:
$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;
}
Η αντιστοίχιση τότε από τον τύπο της property $person
καταλαβαίνει
ότι πρέπει να αντιστοιχίσει το container στην κλάση PersonFormData
. Εάν η
property περιείχε έναν πίνακα από containers, δηλώστε τον τύπο array
και
περάστε την κλάση για αντιστοίχιση απευθείας στο container:
$person->setMappedType(PersonFormData::class);
Μπορείτε να ζητήσετε τη δημιουργία του σχεδίου της
κλάσης δεδομένων της φόρμας χρησιμοποιώντας τη μέθοδο
Nette\Forms\Blueprint::dataClass($form)
, η οποία θα το εκτυπώσει στη σελίδα του
περιηγητή. Στη συνέχεια, αρκεί να επιλέξετε τον κώδικα με κλικ και να
τον αντιγράψετε στο έργο σας.
Πολλαπλά κουμπιά
Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως χρειαζόμαστε να
διακρίνουμε ποιο από αυτά πατήθηκε. Μπορούμε να δημιουργήσουμε τη δική
μας συνάρτηση χειρισμού για κάθε κουμπί. Θα την ορίσουμε ως handler για το
γεγονός onClick
:
$form->addSubmit('save', 'Αποθήκευση')
->onClick[] = [$this, 'saveButtonPressed'];
$form->addSubmit('delete', 'Διαγραφή')
->onClick[] = [$this, 'deleteButtonPressed'];
Αυτοί οι handlers καλούνται μόνο στην περίπτωση έγκυρα συμπληρωμένης
φόρμας, όπως και στην περίπτωση του συμβάντος onSuccess
. Η διαφορά
είναι ότι ως πρώτη παράμετρος, αντί για τη φόρμα, μπορεί να περάσει το
κουμπί υποβολής, ανάλογα με τον τύπο που θα δηλώσετε:
public function saveButtonPressed(Nette\Forms\Controls\Button $button, $data)
{
$form = $button->getForm();
// ...
}
Όταν η φόρμα υποβάλλεται με το πλήκτρο Enter, θεωρείται σαν να υποβλήθηκε με το πρώτο κουμπί.
Το συμβάν onAnchor
Όταν στη factory method (όπως π.χ. η createComponentRegistrationForm
) κατασκευάζουμε
τη φόρμα, αυτή δεν γνωρίζει ακόμη αν υποβλήθηκε, ούτε με ποια δεδομένα.
Υπάρχουν όμως περιπτώσεις όπου χρειαζόμαστε να γνωρίζουμε τις
υποβληθείσες τιμές, για παράδειγμα, η περαιτέρω μορφή της φόρμας
εξαρτάται από αυτές, ή τις χρειαζόμαστε για εξαρτώμενα selectboxes κ.λπ.
Μπορείτε λοιπόν να αφήσετε ένα μέρος του κώδικα που κατασκευάζει τη
φόρμα να κληθεί μόνο τη στιγμή που είναι, όπως λέγεται, αγκυρωμένη,
δηλαδή είναι ήδη συνδεδεμένη με τον presenter και γνωρίζει τα υποβληθέντα
δεδομένα της. Τέτοιο κώδικα τον περνάμε στον πίνακα $onAnchor
:
$country = $form->addSelect('country', 'Χώρα:', $this->model->getCountries());
$city = $form->addSelect('city', 'Πόλη:');
$form->onAnchor[] = function () use ($country, $city) {
// αυτή η συνάρτηση καλείται όταν η φόρμα γνωρίζει αν έχει υποβληθεί και με ποια δεδομένα
// επομένως, η μέθοδος getValue() μπορεί να χρησιμοποιηθεί
$val = $country->getValue();
$city->setItems($val ? $this->model->getCities($val) : []);
};
Προστασία από ευπάθειες
Το Nette Framework δίνει μεγάλη έμφαση στην ασφάλεια και γι' αυτό φροντίζει σχολαστικά για την καλή ασφάλεια των φορμών. Το κάνει εντελώς διαφανώς και δεν απαιτεί χειροκίνητη ρύθμιση.
Εκτός από την προστασία των φορμών από επιθέσεις Cross Site Scripting (XSS) και Cross-Site Request Forgery (CSRF), κάνει πολλές μικρές ασφαλιστικές δικλείδες για τις οποίες εσείς δεν χρειάζεται πλέον να σκέφτεστε.
Για παράδειγμα, φιλτράρει από τις εισόδους όλους τους χαρακτήρες ελέγχου και ελέγχει την εγκυρότητα της κωδικοποίησης UTF-8, έτσι ώστε τα δεδομένα από τη φόρμα να είναι πάντα καθαρά. Στα select boxes και radio lists, ελέγχει ότι τα επιλεγμένα στοιχεία ήταν πραγματικά από τα προσφερόμενα και ότι δεν υπήρξε παραποίηση. Έχουμε ήδη αναφέρει ότι στα text inputs μίας γραμμής αφαιρεί τους χαρακτήρες τέλους γραμμής που θα μπορούσε να στείλει ένας εισβολέας. Στα inputs πολλών γραμμών, κανονικοποιεί τους χαρακτήρες τέλους γραμμής. Και ούτω καθεξής.
Η Nette λύνει για εσάς κινδύνους ασφαλείας που πολλοί προγραμματιστές ούτε καν υποψιάζονται ότι υπάρχουν.
Η αναφερόμενη επίθεση CSRF συνίσταται στο ότι ο εισβολέας προσελκύει το θύμα σε μια σελίδα που εκτελεί διακριτικά στον περιηγητή του θύματος ένα αίτημα προς τον διακομιστή στον οποίο το θύμα είναι συνδεδεμένο, και ο διακομιστής πιστεύει ότι το αίτημα εκτελέστηκε από το θύμα με τη θέλησή του. Γι' αυτό η Nette αποτρέπει την υποβολή φορμών POST από άλλο domain. Εάν για κάποιο λόγο θέλετε να απενεργοποιήσετε την προστασία και να επιτρέψετε την υποβολή της φόρμας από άλλο domain, χρησιμοποιήστε:
$form->allowCrossOrigin(); // ΠΡΟΣΟΧΗ! Απενεργοποιεί την προστασία!
Αυτή η προστασία χρησιμοποιεί ένα SameSite cookie με το όνομα _nss
. Η
προστασία μέσω SameSite cookie μπορεί να μην είναι 100% αξιόπιστη, γι' αυτό είναι
σκόπιμο να ενεργοποιήσετε επιπλέον την προστασία μέσω token:
$form->addProtection();
Συνιστούμε να προστατεύετε με αυτόν τον τρόπο τις φόρμες στο
διαχειριστικό τμήμα του ιστότοπου που αλλάζουν ευαίσθητα δεδομένα
στην εφαρμογή. Το framework αμύνεται έναντι της επίθεσης CSRF δημιουργώντας
και επαληθεύοντας ένα token εξουσιοδότησης, το οποίο αποθηκεύεται στο
session. Επομένως, είναι απαραίτητο να έχετε ανοιχτό το session πριν από την
εμφάνιση της φόρμας. Στο διαχειριστικό τμήμα του ιστότοπου, το session
είναι συνήθως ήδη ενεργοποιημένο λόγω της σύνδεσης του χρήστη.
Διαφορετικά, ξεκινήστε το session με τη μέθοδο Nette\Http\Session::start()
.
Η ίδια φόρμα σε πολλούς presenters
Εάν χρειάζεστε να χρησιμοποιήσετε την ίδια φόρμα σε πολλούς presenters,
συνιστούμε να δημιουργήσετε ένα factory για αυτήν, το οποίο στη συνέχεια
θα περάσετε στον presenter. Μια κατάλληλη τοποθεσία για μια τέτοια κλάση
είναι, για παράδειγμα, ο κατάλογος app/Forms
.
Η κλάση factory μπορεί να μοιάζει κάπως έτσι:
use Nette\Application\UI\Form;
class SignInFormFactory
{
public function create(): Form
{
$form = new Form;
$form->addText('name', 'Όνομα:');
$form->addSubmit('send', 'Σύνδεση');
return $form;
}
}
Ζητάμε από την κλάση να κατασκευάσει τη φόρμα στη factory method για components στον presenter:
public function __construct(
private SignInFormFactory $formFactory,
) {
}
protected function createComponentSignInForm(): Form
{
$form = $this->formFactory->create();
// μπορούμε να τροποποιήσουμε τη φόρμα, εδώ για παράδειγμα αλλάζουμε την ετικέτα στο κουμπί
$form['send']->setCaption('Συνέχεια');
$form->onSuccess[] = [$this, 'signInFormSuceeded']; // και προσθέτουμε τον handler
return $form;
}
Ο handler για την επεξεργασία της φόρμας μπορεί επίσης να παρασχεθεί ήδη από το factory:
use Nette\Application\UI\Form;
class SignInFormFactory
{
public function create(): Form
{
$form = new Form;
$form->addText('name', 'Όνομα:');
$form->addSubmit('send', 'Σύνδεση');
$form->onSuccess[] = function (Form $form, $data): void {
// εδώ εκτελούμε την επεξεργασία της φόρμας
};
return $form;
}
}
Λοιπόν, έχουμε πίσω μας μια γρήγορη εισαγωγή στις φόρμες στη Nette. Δοκιμάστε να ρίξετε μια ματιά στον κατάλογο examples στη διανομή, όπου θα βρείτε περαιτέρω έμπνευση.