Αυτόνομες Φόρμες

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

Ωστόσο, εάν χρησιμοποιείτε το Nette Application και presenters, ο οδηγός για χρήση σε presenters είναι για εσάς.

Πρώτη Φόρμα

Ας προσπαθήσουμε να γράψουμε μια απλή φόρμα εγγραφής. Ο κώδικάς της θα είναι ο ακόλουθος (πλήρης κώδικας):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Όνομα:');
$form->addPassword('password', 'Κωδικός πρόσβασης:');
$form->addSubmit('send', 'Εγγραφή');

Μπορούμε να την αποδώσουμε πολύ εύκολα:

$form->render();

και θα εμφανιστεί στον περιηγητή ως εξής:

Η φόρμα είναι ένα αντικείμενο της κλάσης Nette\Forms\Form (η κλάση Nette\Application\UI\Form χρησιμοποιείται σε presenters). Προσθέσαμε σε αυτή τα λεγόμενα στοιχεία: όνομα, κωδικό πρόσβασης και ένα κουμπί υποβολής.

Τώρα, ας ζωντανέψουμε τη φόρμα. Ρωτώντας $form->isSuccess(), θα μάθουμε αν η φόρμα υποβλήθηκε και αν συμπληρώθηκε έγκυρα. Αν ναι, θα εμφανίσουμε τα δεδομένα. Έτσι, μετά τον ορισμό της φόρμας, προσθέτουμε:

if ($form->isSuccess()) {
	echo 'Η φόρμα υποβλήθηκε και επικυρώθηκε με επιτυχία';
	$data = $form->getValues();
	// το $data->name περιέχει το όνομα
	// το $data->password περιέχει τον κωδικό πρόσβασης
	var_dump($data);
}

Η μέθοδος getValues() επιστρέφει τα υποβληθέντα δεδομένα με τη μορφή ενός αντικειμένου ArrayHash. Θα δείξουμε πώς να το αλλάξουμε αργότερα. Το αντικείμενο $data περιέχει τα κλειδιά name και password με τα δεδομένα που συμπλήρωσε ο χρήστης.

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

$form->addError('Συγγνώμη, αυτό το όνομα χρήστη χρησιμοποιείται ήδη.');

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

Η φόρμα υποβάλλεται από προεπιλογή χρησιμοποιώντας τη μέθοδο POST στην ίδια σελίδα. Και τα δύο μπορούν να αλλάξουν:

$form->setAction('/submit.php');
$form->setMethod('GET');

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

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

Πρόσβαση στα Στοιχεία

Ονομάζουμε τη φόρμα και τα μεμονωμένα στοιχεία της components. Σχηματίζουν ένα δέντρο 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, η οποία εκτελείται αμέσως και ο χρήστης ενημερώνεται αμέσως για το σφάλμα, χωρίς να χρειάζεται να υποβάλει τη φόρμα στον διακομιστή. Αυτό γίνεται από το σενάριο netteForms.js. Εισάγετέ το στη σελίδα:

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

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

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

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

Θα επεκτείνουμε τη φόρμα με ένα νέο προαιρετικό πεδίο «ηλικία», το οποίο πρέπει να είναι ακέραιος αριθμός (addInteger()) και επιπλέον εντός επιτρεπόμενου εύρους ($form::Range). Και εδώ θα χρησιμοποιήσουμε την τρίτη παράμετρο της μεθόδου addRule(), με την οποία περνάμε το απαιτούμενο εύρος στον επικυρωτή ως ζεύγος [από, έως]:

$form->addInteger('age', 'Ηλικία:')
	->addRule($form::Range, 'Η ηλικία πρέπει να είναι μεταξύ 18 και 120.', [18, 120]);

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

Εδώ υπάρχει περιθώριο για μια μικρή αναδιάρθρωση. Στο μήνυμα σφάλματος και στην τρίτη παράμετρο, οι αριθμοί αναφέρονται διπλά, κάτι που δεν είναι ιδανικό. Εάν δημιουργούσαμε πολύγλωσσες φόρμες και το μήνυμα που περιέχει αριθμούς μεταφραζόταν σε πολλές γλώσσες, θα ήταν δύσκολο να αλλάξουμε τις τιμές αργότερα. Για το λόγο αυτό, είναι δυνατό να χρησιμοποιηθούν σύμβολα κράτησης θέσης %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 για κάθε στοιχείο. Για παράδειγμα, προσθέστε ένα placeholder:

$form->addInteger('age', 'Ηλικία:')
	->setHtmlAttribute('placeholder', 'Παρακαλώ συμπληρώστε την ηλικία σας');

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

Αντιστοίχιση σε Κλάσεις

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

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 = $form->getValues(RegistrationFormData::class);
$name = $data->name;

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

Εάν οι φόρμες σχηματίζουν μια πολυεπίπεδη δομή που αποτελείται από 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;
}

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

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

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

Πολλαπλά Κουμπιά

Εάν η φόρμα έχει περισσότερα από ένα κουμπιά, συνήθως πρέπει να διακρίνουμε ποιο από αυτά πατήθηκε. Η μέθοδος isSubmittedBy() του κουμπιού θα μας επιστρέψει αυτή την πληροφορία:

$form->addSubmit('save', 'Αποθήκευση');
$form->addSubmit('delete', 'Διαγραφή');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// αποθήκευση δεδομένων
	}

	if ($form['delete']->isSubmittedBy()) {
		// διαγραφή δεδομένων
	}
}

Μην παραλείψετε την ερώτηση $form->isSuccess(), καθώς επαληθεύει την εγκυρότητα των δεδομένων.

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

Προστασία από Ευπάθειες

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

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

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

Το Nette αντιμετωπίζει για εσάς κινδύνους ασφαλείας που πολλοί προγραμματιστές δεν γνωρίζουν καν ότι υπάρχουν.

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

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

Αυτή η προστασία χρησιμοποιεί ένα SameSite cookie με όνομα _nss. Επομένως, δημιουργήστε το αντικείμενο της φόρμας πριν στείλετε την πρώτη έξοδο, ώστε το cookie να μπορεί να σταλεί.

Η προστασία με SameSite cookie μπορεί να μην είναι 100% αξιόπιστη, οπότε συνιστάται να ενεργοποιήσετε επίσης την προστασία με token:

$form->addProtection();

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

Λοιπόν, αυτή ήταν μια γρήγορη εισαγωγή στις φόρμες στο Nette. Προσπαθήστε να ρίξετε μια ματιά στον κατάλογο examples στη διανομή, όπου θα βρείτε περισσότερη έμπνευση.

έκδοση: 4.0