Mai devreme sau mai târziu, în cariera de programare, vă veți confrunta cu dilema validării și tratării excepțiilor. Acesta a fost și cazul meu și al echipei mele. Cu câțiva ani în urmă, am ajuns la un moment când trebuia să luăm măsuri arhitecturale pentru a se potrivi cu toate cazurile excepționale pe care proiectul nostru de software destul de mare trebuia să le gestioneze. Mai jos este o listă de practici pe care am ajuns să le apreciem și să le aplicăm atunci când vine vorba de validare și tratare a excepțiilor.
Când am început să discutăm problema noastră, un lucru a apărut foarte repede. Ce este validarea și care este tratarea excepțiilor? De exemplu, într-un formular de înregistrare a utilizatorilor, avem câteva reguli pentru parolă (trebuie să conțină atât cifre, cât și litere). Dacă utilizatorul introduce numai litere, este o problemă de validare sau o excepție. În cazul în care interfața UI va valida acest lucru sau doar să o transmită în backend și să captureze excepții care ar fi aruncate?
Am ajuns la o concluzie comună că validarea se referă la regulile definite de sistem și verificate în raport cu datele furnizate de utilizator. O validare nu ar trebui să aibă în vedere modul în care funcționează logica de afaceri sau cum funcționează sistemul pentru acea problemă. De exemplu, sistemul nostru de operare se poate aștepta, fără proteste, o parolă compusă din litere simple. Cu toate acestea, vrem să impunem o combinație de litere și numere. Acesta este un caz de validare, o regulă pe care vrem să o impunem.
Pe de altă parte, excepțiile sunt cazuri în care sistemul nostru poate funcționa într-un mod imprevizibil, greșit sau deloc dacă anumite date specifice sunt furnizate într-un format greșit. De exemplu, în exemplul de mai sus, dacă numele de utilizator există deja în sistem, este cazul unei excepții. Logica noastră de afaceri ar trebui să fie capabilă să arunce excepția corespunzătoare și UI captura și manipula, astfel încât utilizatorul va vedea un mesaj frumos.
Acum, când am clarificat care sunt obiectivele noastre, să vedem câteva exemple bazate pe aceeași idee a formularului de înregistrare a utilizatorilor.
Pentru majoritatea browserelor de astăzi, JavaScript este a doua natură. Nu există aproape nici o pagină web fără un anumit grad de JavaScript în ea. O bună practică este de a valida câteva lucruri de bază în JavaScript.
Să presupunem că avem un simplu formular de înregistrare a utilizatorului în index.php
, așa cum este descris mai jos.
Înregistrare utilizator Înregistrați un cont nou
Aceasta va scoate ceva similar cu imaginea de mai jos:
Fiecare astfel de formular trebuie să valideze că textul introdus în cele două câmpuri de parolă este egal. Evident, acest lucru este acela de a vă asigura că utilizatorul nu face o greșeală atunci când introduceți parola. Cu JavaScript, efectuarea validării este destul de simplă.
Mai întâi trebuie să actualizăm un pic de cod HTML.
Am adăugat nume în câmpurile de introducere a parolei, astfel încât să le putem identifica. Apoi am specificat că la trimiterea formularului ar trebui să returneze rezultatul unei funcții numite validatePasswords ()
. Această funcție este JavaScript pe care îl vom scrie. Scripturile simple cum ar fi acestea pot fi păstrate în fișierul HTML, altele, cele mai sofisticate ar trebui să meargă în propriile lor fișiere JavaScript.
Singurul lucru pe care îl facem aici este de a compara valorile celor două câmpuri de intrare numite "parola
" și "a confirma
"Putem referi formularul la parametrul pe care îl trimitem atunci când apelam funcția.Am folosit"acest
"în formularul lui onsubmit
atribut, astfel încât forma în sine este trimisă la această funcție.
Când valorile sunt aceleași, Adevărat
va fi returnat și formularul va fi trimis, în caz contrar se va afișa un mesaj de avertizare prin care utilizatorul va indica că parolele nu se potrivesc.
În timp ce putem folosi JavaScript pentru a valida majoritatea intrărilor noastre, există cazuri în care vrem să mergem pe o cale mai ușoară. Un grad de validare a intrărilor este disponibil în HTML5, iar majoritatea browserelor sunt bucuroși să le aplice. Utilizarea validării HTML5 este mai simplă în unele cazuri, deși oferă o mai mică flexibilitate.
Înregistrare utilizator Înregistrați un cont nou
Pentru a demonstra mai multe cazuri de validare, am extins formularul nostru un pic. Am adăugat și o adresă de e-mail și un site web. Validările HTML au fost stabilite în trei domenii.
nume de utilizator
este pur și simplu necesar. Acesta va valida cu orice șir mai mult de zero caractere. e-mail
"și când specificăm"necesar
", browserele vor aplica o validare câmpului. URL-ul
"De asemenea, am specificat un"model
"la care puteți scrie expresiile dvs. regulate care validă câmpurile obligatorii. Pentru a face utilizatorul conștient de starea câmpurilor, am folosit și un pic de CSS pentru a colora marginile intrărilor în roșu sau verde, în funcție de starea validării necesare.
Problema cu validările HTML este că diferite browsere se comportă diferit atunci când încercați să trimiteți formularul. Unele browsere vor aplica doar CSS pentru a informa utilizatorii, alții vor împiedica prezentarea completă a formularului. Vă recomandăm să testați în întregime validările dvs. HTML în diferite browsere și, dacă este necesar, să furnizați și o rezervă JavaScript pentru acele browsere care nu sunt suficient de inteligente.
Până acum mulți oameni știu despre propunerea de arhitectură curată a lui Robert C. Martin, în care cadrul MVC este doar pentru prezentare și nu pentru logica de afaceri.
În esență, logica dvs. de afaceri ar trebui să locuiască într-un loc separat, bine izolat, organizat pentru a reflecta arhitectura aplicației dvs., în timp ce vizualizările și controlorii cadrului ar trebui să controleze livrarea conținutului către utilizator, iar modelele ar putea fi eliminate complet sau, dacă este necesar , utilizat numai pentru a efectua operațiuni legate de livrare. O astfel de operațiune este validarea. Cele mai multe cadre au caracteristici de validare excelente. Ar fi o rușine să nu vă puneți la lucru modelele și să faceți puțină validare acolo.
Nu vom instala mai multe cadre web MVC pentru a demonstra cum să validăm formularele anterioare, dar aici sunt două soluții aproximative în Laravel și CakePHP.
Laravel este proiectat astfel încât să aveți mai mult acces la validare în Controler, unde aveți, de asemenea, acces direct la intrare de la utilizator. Tipul validator încorporat preferă să fie utilizat acolo. Cu toate acestea, există sugestii pe Internet că validarea în modele este încă un lucru bun de făcut în Laravel. Un exemplu și o soluție completă de Jeffrey Way pot fi găsite în depozitul său Github.
Dacă preferați să vă scrieți propria soluție, puteți face ceva similar modelului de mai jos.
Clasa UserACL se extinde Eloquent private $ rules = array ('userName' => 'necesar | alfa | min: 5', 'password' => ' ',' email '=>' obligatoriu ',' website '=>' url '); private $ errors; funcția publică validată ($ date) $ validator = Validator :: make ($ data, $ this-> rules); dacă ($ validator-> nu reușește ()) $ this-> errors = $ validator-> erori; return false; return true; erori de funcționare publică () return $ this-> errors;
Puteți folosi acest lucru de la controlerul dvs. prin crearea pur și simplu UserACL
obiect și apel valida pe el. Veți avea probabil "Inregistreaza-te
"metoda, de asemenea, pe acest model, și Inregistreaza-te
va delega doar datele deja validate logicii dvs. de afaceri.
CakePHP promovează și validarea în modele. Are funcționalitate extinsă de validare la nivel de model. Iată cum ar arăta o validare pentru forma noastră în CakePHP.
Clasa UserACL extinde aplicația AppModel public $ validate = ['userName' => ['rule' => ['minLength', 5], 'required' => true, 'enableEmpty' => false, 'on' => ',' message '=>' Numele de utilizator trebuie să aibă cel puțin 5 caractere. ' ], 'password' => ['rule' => ['equalsTo', 'confirm'], 'message' => 'Cele două parole nu se potrivesc. Reintroduceți-le. " ]]; funcția publică este egală cu ($ checkedField, $ otherField = null) $ value = $ this-> getFieldValue ($ checkedField); returnează valoarea $ === $ this-> data [$ this-> name] [$ otherField]; funcția privată getFieldValue ($ fieldName) return array_values ($ otherField) [0];
Am exemplificat parțial regulile. Este suficient să evidențiați puterea validării în model. CakePHP este deosebit de bun în acest sens. Are un număr mare de funcții de validare încorporate, cum ar fi "MINLENGTH
"în exemplul și diferite modalități de a oferi feedback utilizatorului. Mai mult, concepte precum"necesar
"sau"allowEmpty
"nu sunt de fapt reguli de validare.Take-ul se va uita la acestea atunci când generează vizualizarea dvs. și pune validări HTML, de asemenea, pe câmpurile marcate cu acești parametri.Cu toate acestea regulile sunt mari și pot fi ușor de extins prin simpla creare a metodelor pe clasa de model ca noi comparați cele două câmpuri de parolă.În cele din urmă, puteți specifica întotdeauna mesajul pe care doriți să-l trimiteți la vizualizări în cazul eșecului de validare.Mai multe despre validarea CakePHP în cartea de bucate.
Validarea în general la nivel de model are avantajele sale. Fiecare cadru oferă acces facil la câmpurile de intrare și creează mecanismul de notificare a utilizatorului în cazul eșecului de validare. Nu este nevoie de declarații de încercare sau de alți pași sofisticați. Validarea pe partea serverului asigură, de asemenea, validarea datelor, indiferent de ce. Utilizatorul nu mai poate șterge software-ul nostru, ca și în cazul HTML sau JavaScript. Desigur, fiecare validare pe partea de server vine cu costul unei călătorii de rețea și de calcul pe partea furnizorului în locul clientului.
Ultimul pas în verificarea datelor înainte de a le angaja în sistem este la nivelul logicii noastre de afaceri. Informațiile care ajung la această parte a sistemului ar trebui să fie suficient de sanitizate pentru a fi utilizabile. Logica de afaceri ar trebui să verifice doar cazurile care sunt critice pentru aceasta. De exemplu, adăugarea unui utilizator care există deja este un caz atunci când aruncăm o excepție. Verificarea lungimii utilizatorului să fie de cel puțin cinci caractere nu ar trebui să se întâmple la acest nivel. Putem presupune în mod sigur că astfel de limitări au fost aplicate la niveluri mai înalte.
Pe de altă parte, compararea celor două parole este o chestiune de discuție. De exemplu, dacă doar criptuzăm și salvăm parola lângă utilizator într-o bază de date, am putea să renunțăm la verificare și să presupunem că straturile anterioare s-au asigurat că parolele sunt egale. Cu toate acestea, dacă creăm un utilizator real în sistemul de operare utilizând un instrument API sau CLI care necesită de fapt confirmarea numelui de utilizator, a parolei și a parolei, este posibil să dorim să luăm și al doilea element și să îl trimitem unui instrument CLI. Permiteți-le să revalideze dacă parolele se potrivesc și să fie gata să arunce o excepție dacă nu. În acest fel, am modelat logica noastră de afaceri pentru a se potrivi cu modul în care se comportă sistemul de operare real.
Aruncarea excepțiilor de la PHP este foarte ușoară. Să creăm clasa de control al accesului utilizatorilor și să demonstrăm modul de implementare a unei funcționalități de adăugare a utilizatorilor.
clasa UserControlTest extinde PHPUnit_Framework_TestCase function testBehavior () $ this-> assertTrue (true);
Îmi place întotdeauna să încep cu ceva simplu care mă face să merg. Crearea unui test stupid este o modalitate foarte bună de a face acest lucru. De asemenea, mă obligă să mă gândesc la ceea ce vreau să pun în aplicare. Un test numit UserControlTest
înseamnă că am crezut că o să am nevoie UserControl
clasa pentru a pune în aplicare metoda mea.
requ_once __DIR__. '/ ... /UserControl.php'; class UserControlTest extinde PHPUnit_Framework_TestCase / ** * @expectedException Exception * @expectedExceptionMessage Utilizatorul nu poate fi gol * / function testEmptyUsernameWillThrowException () $ userControl = new UserControl (); $ userControl-> add (");
Următorul test pentru a scrie este un caz degenerativ. Nu vom testa o anumită durată de utilizator, dar dorim să ne asigurăm că nu dorim să adăugăm un utilizator gol. Este uneori ușor să pierdeți conținutul unei variabile de la vizualizare la afacere, peste toate straturile aplicației noastre. Acest cod va eșua în mod evident, deoarece încă nu avem o clasă.
Avertisment PHP: require_once ([long-path-here] / Test / ... /UserControl.php): nu a reușit să deschidă fluxul: Nici un astfel de fișier sau director în [long-path-here] /Test/UserControlTest.php pe linia 2
Să creăm clasa și să ne conducem testele. Acum avem o altă problemă.
PHP Eroare fatală: Apel la metoda nedefinită UserControl :: add ()
Dar putem rezolva și asta în doar câteva secunde.
clasa UserControl funcția publică adăugă ($ username)
Acum putem avea o eșec de test frumos spunându-ne întreaga poveste a codului nostru.
1) UserControlTest :: testEmptyUsernameWillThrowException Nu a reușit să se afirme că excepția de tip "Excepție" este aruncată.
În cele din urmă, putem face o codificare reală.
funcția publică adăugă ($ username) if (! $ username) arunca noua Excepție ();
Asta face așteptările pentru trecerea excepției, dar fără a specifica un mesaj, testul va eșua.
1) UserControlTest :: testEmptyUsernameWillThrowException Nu a reușit să se afirme că mesajul de excepție "conține" Utilizatorul nu poate fi gol ".
Este timpul să scrieți mesajul Excepției
funcția publică adăugă ($ username) if (! $ username) arunca o nouă excepție ("Utilizatorul nu poate fi gol!");
Acum, asta face trecerea testului. După cum puteți observa, PHPUnit verifică faptul că mesajul de excepție așteptat este conținut în excepția efectiv aruncată. Acest lucru este util pentru că ne permite să construim dinamic mesaje și să verificăm numai partea stabilă. Un exemplu obișnuit este atunci când arunci o eroare cu un text de bază și, la sfârșit, specificați motivul acestei excepții. Motivele sunt de obicei furnizate de biblioteci terțe părți sau de aplicații.
/ ** * @expectedExceptionException * @expectedExceptionMessage Nu se poate adăuga utilizator George * / function testWillNotAddAnAlreadyExistingUser () $ command = \ Mockery :: mock ('SystemCommand'); $ command-> shouldReceive ('execute') -> once () -> cu ('adduser George') -> șiReturn (false); $ command-> shouldReceive ('getFailureMessage') -> o dată () -> șiReturn ("Utilizatorul există deja în sistem"); $ userControl = noul UserControl ($ command); $ UserControl-> add ( 'George');
Aruncarea erorilor pe utilizatorii dubliți ne va permite să explorăm această construcție a mesajului cu un pas mai departe. Testul de mai sus creează o machetă care va simula o comandă de sistem, va eșua și, la cerere, va returna un mesaj de eșec frumos. Vom injecta această comandă către UserControl
clasa pentru uz intern.
clasa UserControl private $ systemCommand; funcția publică __construct (SystemCommand $ systemCommand = null) $ this-> systemCommand = $ systemCommand? : noul SystemCommand (); funcția publică adăugă ($ username) if (! $ username) arunca o nouă excepție ("Utilizatorul nu poate fi gol!"); clasa SystemCommand
Injectarea a SystemCommand
exemplu a fost destul de ușor. Am creat și o SystemCommand
clasa din testul nostru doar pentru a evita problemele de sintaxă. Nu o vom implementa. Domeniul său de aplicare depășește tema acestui tutorial. Cu toate acestea, avem un alt mesaj de eșec al testării.
1) UserControlTest :: testWillNotAddAnAlreadyExistingUser Nu a reușit să se afirme că excepția tipului "Excepție" este aruncată.
Da. Nu facem excepții. Logica pentru a apela comanda sistemului și a încerca să adăugați utilizatorul lipsește.
funcția publică adăugă ($ username) if (! $ username) arunca o nouă excepție ("Utilizatorul nu poate fi gol!"); dacă executați (sprintf ('adduser% s', $ username))) aruncați o nouă excepție (sprintf ('Nu se poate adăuga utilizator% s. Motiv:% s', $ username, $ acest sistem-> sistemCommand-> getFailureMessage ()));
Acum, aceste modificări la adăuga()
metoda poate face truc. Încercăm să executăm comanda noastră pe sistem, indiferent de ce, și dacă sistemul spune că nu poate adăuga utilizatorul din orice motiv, aruncăm o excepție. Mesajul acestei excepții va fi parțial codificat, cu numele utilizatorului atașat și apoi motivul de la comanda sistemului concatenat la sfârșit. După cum puteți vedea, acest cod face testul nostru.
Aruncarea excepțiilor cu mesaje diferite este suficientă în majoritatea cazurilor. Cu toate acestea, atunci când aveți un sistem mai complex, trebuie, de asemenea, să prindeți aceste excepții și să luați diferite acțiuni bazate pe acestea. Analiza mesajului unei excepții și luarea de măsuri numai pe aceasta pot duce la unele probleme enervante. În primul rând, șirurile fac parte din UI, prezentare, și au un caracter volatil. Logica bazată pe șiruri în continuă schimbare va duce la coșmarul de gestionare a dependenței. În al doilea rând, sunând a getMessage ()
metoda de excepție prins de fiecare dată este, de asemenea, o modalitate ciudată de a decide ce să facă în continuare.
Având în vedere toate acestea, crearea propriilor excepții este următorul pas logic de luat.
/ ** * @expectedException ExceptionCannotAddUser * @expectedExceptionMessage Nu se poate adăuga utilizator George * / function testWillNotAddAnAlreadyExistingUser () $ command = \ Mockery :: mock ('SystemCommand'); $ command-> shouldReceive ('execute') -> once () -> cu ('adduser George') -> șiReturn (false); $ command-> shouldReceive ('getFailureMessage') -> o dată () -> șiReturn ("Utilizatorul există deja în sistem"); $ userControl = noul UserControl ($ command); $ UserControl-> add ( 'George');
Am modificat testul pentru a ne aștepta excepția personalizată, ExceptionCannotAddUser
. Restul testului este neschimbat.
clasa ExceptionCannotAddUser extinde excepția funcția publică __construct ($ userName, $ reason) $ message = sprintf ('Nu se poate adăuga utilizator% s. Motiv:% s', $ userName, $ reason); părinte :: __ construct ($ message, 13, null);
Clasa care implementează excepția personalizată este ca orice altă clasă, dar trebuie extinsă Excepție
. Utilizarea excepțiilor personalizate ne oferă, de asemenea, un loc excelent pentru a face toate manipularea șirului de prezentare. Deplasând concatenarea aici, am eliminat prezentarea din logica de afaceri și am respectat principiul responsabilității unice.
funcția publică adăugă ($ username) if (! $ username) arunca o nouă excepție ("Utilizatorul nu poate fi gol!"); dacă (! $ this-> systemCommand-> execute (sprintf ('adduser% s', $ username))) aruncați ExceptionCannotAddUser nou ($ username, $ this-> systemCommand-> getFailureMessage ());
Aruncarea propriei noastre excepții este doar o chestiune de schimbare a "arunca
"comandă celui nou și trimiteți doi parametri în loc să compuneți mesajul aici. Bineînțeles că toate testele trec.
PHPUnit 3.7.28 de Sebastian Bergmann ... Timp: 18 ms, Memorie: 3.00Mb OK (2 teste, 4 afirmații) Terminat.
Excepțiile trebuie să fie prinse la un moment dat, cu excepția cazului în care doriți ca utilizatorul să le vadă așa cum sunt. Dacă utilizați un cadru MVC, probabil că veți dori să prindeți excepții în controler sau model. După ce excepția este capturată, ea este transformată într-un mesaj către utilizator și redat în interiorul vizualizării. O modalitate comună de a realiza acest lucru este crearea unui "tryAction ($ acțiune)
"în modul de bază al aplicației dvs. sau model și întotdeauna apelați-l cu acțiunea curentă.În această metodă puteți face logica de capturare și generație de mesaj frumos pentru a se potrivi dvs. cadru.
Dacă nu utilizați un cadru web sau o interfață web pentru acest lucru, stratul de prezentare ar trebui să aibă grijă de capturarea și transformarea acestor excepții.
Dacă dezvoltați o bibliotecă, capturarea excepțiilor dvs. va fi responsabilitatea clienților dvs..
Asta e. Am traversat toate straturile aplicației noastre. Am validat în JavaScript, HTML și în modelele noastre. Am aruncat și prins excepții de la logica noastră de afaceri și chiar am creat excepții personalizate. Această abordare a validării și tratării excepțiilor poate fi aplicată de la proiecte mici la mari, fără probleme grave. Cu toate acestea, dacă logica dvs. de validare devine foarte complexă și diferite părți ale proiectului dvs. utilizează părți suprapuse ale logicii, puteți lua în considerare extragerea tuturor validărilor care pot fi efectuate la un anumit nivel unui serviciu de validare sau unui furnizor de validare. Aceste niveluri pot include, dar nu trebuie să fie limitate la validator JavaScript, backend validator PHP, validator de comunicare terță parte și așa mai departe.
Mulțumesc că ați citit. O zi bună.