Φόρμες που χρησιμοποιούνται αυτόνομα

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

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

Πρώτη φόρμα

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

use Nette\Forms\Form;

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

Και ας την απεικονίσουμε:

$form->render();

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

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

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

if ($form->isSuccess()) {
	echo 'The form has been filled in and submitted correctly';
	$data = $form->getValues();
	// $data->name περιέχει όνομα
	// $data->password περιέχει κωδικό πρόσβασης
	var_dump($data);
}

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

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

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

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

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

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

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

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

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

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

$input = $form->getComponent('name');
// εναλλακτική σύνταξη: όνομα'],

$button = $form->getComponent('send');
// εναλλακτική σύνταξη: $button = $form['send'],

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

unset($form['name']);

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

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

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

$form->addText('name', 'Name:')
	->setRequired('Please enter a 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');

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

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

Ας επιστρέψουμε στην επεξεργασία δεδομένων φόρμας. Η μέθοδος getValues() επέστρεψε τα υποβληθέντα δεδομένα ως αντικείμενο 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 = $form->getValues(RegistrationFormData::class);
$name = $data->name;

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

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

$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), η οποία θα την εκτυπώσει στη σελίδα του προγράμματος περιήγησης. Στη συνέχεια, μπορείτε απλά να κάνετε κλικ για να επιλέξετε και να αντιγράψετε τον κώδικα στο έργο σας.

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

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

$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}

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

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

Προστασία από τρωτά σημεία

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

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

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

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

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

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

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

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

$form->addProtection();

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

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

έκδοση: 4.0