Før eller senere i din programmeringskarriere vil du bli konfrontert med dilemmaet for validering og unntakshåndtering. Dette var tilfellet med meg og mitt lag også. For noen eller flere år siden nådde vi et punkt da vi måtte ta arkitektoniske tiltak for å imøtekomme alle de unike tilfellene som vårt ganske store programvareprosjekt måtte håndtere. Nedenfor er en liste over praksis vi kom til å verdsette og anvende når det gjelder validering og unntakshåndtering.
Da vi begynte å diskutere vårt problem, oppdaget en ting veldig raskt. Hva er validering og hva er unntakshåndtering? For eksempel i et brukerregistreringsskjema har vi noen regler for passordet (det må inneholde både tall og bokstaver). Hvis brukeren bare skriver inn bokstaver, er det et valideringsproblem eller et unntak. Skal brukergrensesnittet validere det, eller bare sende det til backend og ta noen unntak at jeg blir kastet?
Vi nådde en felles konklusjon at validering refererer til regler definert av systemet og verifisert mot data levert av brukeren. En validering bør ikke bryr seg om hvordan forretningslogikken fungerer, eller hvordan systemet for øvrig fungerer. For eksempel kan vårt operativsystem forvente, uten noen protester, et passord bestående av vanlige bokstaver. Men vi ønsker å håndheve en kombinasjon av bokstaver og tall. Dette er et tilfelle for validering, en regel vi vil pålegge.
På den annen side er unntak tilfeller der systemet vårt kan fungere på en uforutsigbar måte, feilaktig, eller slet ikke hvis noen spesifikke data er gitt i feil format. For eksempel, i eksempelet ovenfor, hvis brukernavnet allerede eksisterer på systemet, er det et tilfelle av et unntak. Vår forretningslogikk skal kunne kaste det riktige unntaket, og brukergrensesnittet tar tak i og håndterer det slik at brukeren får en fin melding.
Nå som vi har klart hva våre mål er, la oss se noen eksempler basert på den samme brukerregistreringsformidelsen.
For de fleste av dagens browsere er JavaScript den andre naturen. Det er nesten ingen nettside uten noen grad av JavaScript i den. En god praksis er å validere noen grunnleggende ting i JavaScript.
La oss si at vi har et enkelt brukerregistreringsskjema i index.php
, som beskrevet nedenfor.
bruker registrering Registrer ny konto
Dette vil utgjøre noe som ligner på bildet nedenfor:
Hvert slikt skjema skal validere at teksten som er oppgitt i de to passordfeltene, er lik. Åpenbart er dette for å sikre at brukeren ikke gjør feil når han skriver inn sitt passord. Med JavaScript, gjør validering ganske enkelt.
Først må vi oppdatere litt av vår HTML-kode.
Vi har lagt til navn i feltene for passordet, slik at vi kan identifisere dem. Da spesifiserte vi at ved innsending skal skjemaet returnere resultatet av en funksjon som kalles validatePasswords ()
. Denne funksjonen er JavaScript vi skal skrive. Enkle skript som dette kan holdes i HTML-filen, andre, mer sofistikerte bør gå i egne JavaScript-filer.
Det eneste vi gjør her er å sammenligne verdiene til de to inngangsfeltene "passord
"og"bekrefte
". Vi kan referere til skjemaet ved parameteren vi sender inn når vi ringer funksjonen. Vi brukte"dette
"i skjemaets onsubmit
attributt, så selve skjemaet sendes til funksjonen.
Når verdiene er de samme, ekte
vil bli returnert og skjemaet vil bli sendt, ellers vil en varselmelding bli vist for å fortelle brukeren at passordene ikke stemmer overens.
Selv om vi kan bruke JavaScript til å validere de fleste av våre innganger, er det tilfeller når vi vil gå på en enklere vei. En viss grad av inngangsvalidering er tilgjengelig i HTML5, og de fleste nettlesere er glade for å bruke dem. Ved hjelp av HTML5-validering er det enklere i noen tilfeller, selv om det gir mindre fleksibilitet.
bruker registrering Registrer ny konto
For å demonstrere flere valideringssaker utvidet vi skjemaet litt. Vi har lagt til en e-postadresse og et nettsted også. HTML-valideringer ble angitt på tre felt.
brukernavn
er bare rett og slett nødvendig. Den vil validere med en streng som er lengre enn null tegn. e-post
"og når vi spesifiserer"nødvendig
"attributt, vil nettlesere bruke en validering til feltet. url
". Vi spesifiserte også en"mønster
"attributt der du kan skrive dine vanlige uttrykk som validerer de obligatoriske feltene. For å gjøre brukeren oppmerksom på tilstanden til feltene, brukte vi også en liten bit av CSS for å farge grensene til inngangene i rød eller grønn, avhengig av tilstanden til den nødvendige validering.
Problemet med HTML-valideringer er at forskjellige nettlesere oppfører seg annerledes når du prøver å sende inn skjemaet. Noen nettlesere vil bare søke CSS for å informere brukerne, andre vil forhindre at skjemaet sendes helt. Jeg anbefaler deg å teste HTML-valideringene dine grundig i forskjellige nettlesere og, om nødvendig, gi også en JavaScript-tilbakebetaling for de nettleserne som ikke er klare nok.
Ved nå vet mange om Robert C. Martins rene arkitekturforslag, der MVC-rammene bare er for presentasjon og ikke for forretningslogikk.
I hovedsak bør forretningslogikken din være plassert på et separat, godt isolert sted, organisert for å gjenspeile arkitekturen i søknaden din, mens rammeverkets synspunkter og kontrollører skal kontrollere levering av innholdet til brukeren, og modellene kan slettes helt eller, om nødvendig , brukes kun til å utføre leveringsrelaterte operasjoner. En slik operasjon er validering. De fleste rammer har gode valideringsfunksjoner. Det ville være synd å ikke sette modellene på jobb og gjøre en liten validering der.
Vi vil ikke installere flere MVC web rammer for å demonstrere hvordan vi kan validere våre tidligere skjemaer, men her er to omtrentlige løsninger i Laravel og CakePHP.
Laravel er utformet slik at du har mer tilgang til validering i kontrolleren der du også har direkte tilgang til innspill fra brukeren. Den innebygde validator typen foretrekker å bli brukt der. Imidlertid er det forslag på Internett om at validering i modeller fortsatt er en god ting å gjøre i Laravel. Et komplett eksempel og løsning av Jeffrey Way kan bli funnet på hans Github-depot.
Hvis du foretrekker å skrive din egen løsning, kan du gjøre noe som ligner på modellen nedenfor.
klassen UserACL utvider Eloquent private $ rules = array ('userName' => 'kreves | alfa | min: 5', 'passord' => 'kreves | min: 6', 'bekreft' => 'kreves | min: 6 ',' email '=>' nødvendig 'email', 'website' => 'url'); private $ feil; Validering av offentlig funksjon ($ data) $ validator = Validator :: make ($ data, $ this-> rules); hvis ($ validator-> mislykkes ()) $ this-> errors = $ validator-> feil; returner falsk; returnere sann; offentlige funksjonsfeil () return $ this-> errors;
Du kan bruke dette fra din kontroller ved å bare opprette UserACL
objekt og samtale validere på den. Du vil sannsynligvis ha "registrere
"metode også på denne modellen, og registrere
Vil bare delegere de allerede validerte dataene til din forretningslogikk.
CakePHP fremmer validering i modeller også. Den har omfattende valideringsfunksjonalitet på modellnivå. Her handler det om hvordan en validering for vårt skjema vil se ut i CakePHP.
klassen UserACL utvider AppModel public $ validate = ['userName' => ['rule' => ['minLength', 5], 'required' => true, 'allowEmpty' => false, 'on' => ' ',' message '=>' Brukernavn må være minst 5 tegn langt. ' ], 'password' => ['rule' => ['equalsTo', 'confirm'], 'message' => 'De to passordene stemmer ikke overens. Vennligst skriv inn dem igjen. ' ]]; offentlig funksjon equalsTo ($ checkedField, $ otherField = null) $ value = $ this-> getFieldValue ($ checkedField); return $ value === $ this-> data [$ this-> name] [$ otherField]; privat funksjon getFieldValue ($ fieldName) return array_values ($ otherField) [0];
Vi bare eksemplifisert reglene delvis. Det er nok å markere valideringskraften i modellen. CakePHP er spesielt bra på dette. Den har et stort antall innebygde valideringsfunksjoner som "MINLENGTH
"i eksemplet og ulike måter å gi tilbakemelding til brukeren. Enda flere begreper som"nødvendig
"eller"allowEmpty
"er ikke faktisk valideringsregler. Kake vil se på disse når du genererer visningen din og legger HTML-valideringer også på felt merket med disse parametrene. Reglene er imidlertid store og kan enkelt utvides ved å bare lage metoder på modellklassen som vi gjorde til sammenlign de to passordfeltene. Endelig kan du alltid spesifisere meldingen du vil sende til visningene i tilfelle av valideringsfeil. Mer om CakePHP-validering i kokboken.
Validering generelt på modellnivå har sine fordeler. Hvert rammeverk gir enkel tilgang til inngangsfeltene og skaper mekanismen for å varsle brukeren i tilfelle valideringsfeil. Du trenger ikke try-catch-setninger eller andre sofistikerte trinn. Validering på server siden sikrer også at dataene blir validerte, uansett hva. Brukeren kan ikke lure vår programvare mer som med HTML eller JavaScript. Selvfølgelig kommer hver server side validering med kostnaden for et nettverk rundtur og datakraft på leverandørens side i stedet for kundens side.
Det endelige trinnet i å sjekke data før du forplikter det til systemet, er i nivå med vår forretningslogikk. Informasjon som kommer til denne delen av systemet, bør være sanitert nok til å være brukbar. Forretningslogikken bør bare sjekke om saker som er kritiske for det. For eksempel legger du til en bruker som allerede eksisterer, når vi kaster et unntak. Det må ikke skje lengden på brukeren å være minst fem tegn på dette nivået. Vi kan trygt anta at slike begrensninger ble håndhevet på høyere nivåer.
På den annen side er det å diskutere å sammenligne de to passordene. Hvis vi for eksempel bare krypterer og lagrer passordet i nærheten av brukeren i en database, kan vi slippe sjekken og anta at tidligere lag sørget for at passordene er like. Men hvis vi oppretter en ekte bruker på operativsystemet ved hjelp av et API eller et CLI-verktøy som faktisk krever et brukernavn, passord og passordbekreftelse, kan vi også ta den andre oppføringen også og sende den til et CLI-verktøy. La det validere om passordene stemmer overens og vær klar til å kaste unntak hvis de ikke gjør det. På denne måten modellerte vi vår forretningslogikk for å matche hvordan det virkelige operativsystemet oppfører oss.
Kastende unntak fra PHP er veldig enkelt. La oss lage vår brukerkontrollklass, og demonstrere hvordan du implementerer en tilleggsfunksjonalitet for brukere.
klasse UserControlTest utvider PHPUnit_Framework_TestCase funksjonstestBehavior () $ this-> assertTrue (true);
Jeg liker alltid å starte med noe enkelt som får meg til å gå. Å lage en dum test er en fin måte å gjøre det på. Det tvinger meg også til å tenke på hva jeg vil implementere. En test som heter UserControlTest
betyr at jeg trodde jeg vil trenge en UserControl
klasse for å implementere metoden min.
require_once __DIR__. '/ ... /UserControl.php'; klassen UserControlTest utvider PHPUnit_Framework_TestCase / ** * @expectedException Exception * @expectedExceptionMessage Brukeren kan ikke være tom * / funksjonstestEmptyUsernameWillThrowException () $ userControl = ny UserControl (); $ userControl-> add (");
Den neste testen som skrives er et degenerativt tilfelle. Vi vil ikke teste for en bestemt brukerlengde, men vi vil sørge for at vi ikke vil legge til en tom bruker. Det er noen ganger lett å miste innholdet i en variabel fra visning til bedrift, over alle de lagene i applikasjonen vår. Denne koden vil tydeligvis mislykkes, fordi vi ikke har en klasse ennå.
PHP Advarsel: require_once ([long-path-here] / Test / ... /UserControl.php): klarte ikke å åpne strøm: Ingen slik fil eller katalog i [long-path-here] /Test/UserControlTest.php på linje 2
La oss lage klassen og kjøre testene våre. Nå har vi et annet problem.
PHP Fatal feil: Ring til udefinert metode UserControl :: add ()
Men vi kan også fikse det på bare et par sekunder.
klasse UserControl public function add ($ brukernavn)
Nå kan vi få en fin testfeil som forteller oss hele historien om koden vår.
1) UserControlTest :: testEmptyUsernameWillThrowException Feilet hevdet at unntak av typen "Unntak" er kastet.
Til slutt kan vi gjøre noen egentlig koding.
offentlig funksjon legge til ($ brukernavn) if (! $ brukernavn) kaste ny unntak ();
Det gjør forventningen til unntakspass, men uten å spesifisere en melding, vil testen fortsatt mislykkes.
1) UserControlTest :: testEmptyUsernameWillThrowException Feilet hevdet at unntaksmeldingen "inneholder" Brukeren kan ikke være tom ".
Tid til å skrive unntakets melding
offentlig funksjon legg til ($ brukernavn) if (! $ brukernavn) kaste ny unntak ('Brukeren kan ikke være tom!');
Nå gjør det vårt testpass. Som du kan observere, verifiserer PHPUnit at den forventede unntaksmeldingen finnes i det faktisk kastede unntaket. Dette er nyttig fordi det gir oss mulighet til å konstruere meldinger dynamisk og bare sjekke for den stabile delen. Et vanlig eksempel er når du kaster en feil med en grunntekst, og på slutten angir du årsaken til det unntaket. Årsaker er vanligvis gitt av tredjepartsbiblioteker eller applikasjoner.
/ ** * @expectedException Exception * @expectedExceptionMessage Kan ikke legge til bruker George * / funksjon testWillNotAddAnAlreadyExistingUser () $ command = \ Mockery :: mock ('SystemCommand'); $ command-> shouldReceive ('execute') -> en gang () -> med ('adduser George') -> andReturn (false); $ command-> shouldReceive ('getFailureMessage') -> en gang () -> ogReturn ('Brukeren eksisterer allerede på systemet.'); $ userControl = ny UserControl ($ command); $ UserControl-> legge til ( 'George');
Kastefeil på dupliserte brukere vil tillate oss å utforske denne meldingen, bygg et skritt videre. Testen ovenfor skaper en mock som vil simulere en systemkommando, den vil mislykkes, og på forespørsel vil den returnere en fin feilmelding. Vi vil injisere denne kommandoen til UserControl
klasse for intern bruk.
klasse UserControl private $ systemCommand; offentlig funksjon __construct (SystemCommand $ systemCommand = null) $ this-> systemCommand = $ systemCommand? : Ny SystemCommand (); offentlig funksjon legg til ($ brukernavn) hvis (! $ brukernavn) kaste ny unntak ('Brukeren kan ikke være tom!'); klasse SystemCommand
Injiserer a SystemCommand
forekomsten var ganske enkelt. Vi har også opprettet en SystemCommand
klasse i testen vår for å unngå syntaxproblemer. Vi vil ikke implementere det. Dens omfang overskrider dette opplæringsemnet. Vi har imidlertid en annen feilmeldingsmelding.
1) UserControlTest :: testWillNotAddAnAlreadyExistingUser Mislyktes hevdet at unntaket av typen "Unntak" er kastet.
Jepp. Vi kaster ingen unntak. Logikken til å ringe systemkommandoen og prøve å legge til brukeren mangler.
offentlig funksjon legg til ($ brukernavn) if (! $ brukernavn) kaste ny unntak ('Brukeren kan ikke være tom!'); hvis ! $ this-> systemCommand-> kjør (sprintf ('adduser% s', $ brukernavn))) kaster ny unntak (sprintf ('Kan ikke legge til bruker% s. Årsak:% s', $ brukernavn, $ this-> systemCommand-> getFailureMessage ()));
Nå, de endringene til Legg til()
Metode kan gjøre trikset. Vi prøver å utføre vår kommando på systemet, uansett hva, og hvis systemet sier at det ikke kan legge til brukeren uansett grunn, kaster vi et unntak. Dette unntakets melding vil være del hardkodet, med brukerens navn vedlagt og da årsaken fra systemkommandoen sammenføyet på slutten. Som du kan se, gjør denne koden vår testpass.
Kaster unntak med forskjellige meldinger er nok i de fleste tilfeller. Men når du har et mer komplekst system, må du også ta disse unntakene og ta ulike handlinger basert på dem. Analysere et unntaks melding og ta handlingen utelukkende på det som kan føre til noen irriterende problemer. For det første er strenger en del av brukergrensesnittet, presentasjon, og de har en flyktig natur. Basing logikk på stadig skiftende strenge vil føre til avhengighetsledelse mareritt. For det andre, ringer en GetMessage ()
metode på fanget unntak hver gang er også en merkelig måte å bestemme hva du skal gjøre neste gang.
Med alle disse i tankene, er å lage våre egne unntak det neste logiske skrittet å ta.
/ ** * @expectedException ExceptionCannotAddUser * @expectedExceptionMessage Kan ikke legge til bruker George * / funksjon testWillNotAddAnAlreadyExistingUser () $ command = \ Mockery :: mock ('SystemCommand'); $ command-> shouldReceive ('execute') -> en gang () -> med ('adduser George') -> andReturn (false); $ command-> shouldReceive ('getFailureMessage') -> en gang () -> ogReturn ('Brukeren eksisterer allerede på systemet.'); $ userControl = ny UserControl ($ command); $ UserControl-> legge til ( 'George');
Vi endret vår test for å forvente vårt eget tilpassede unntak, ExceptionCannotAddUser
. Resten av testen er uendret.
klassen ExceptionCannotAddUser utvider Unntak offentlig funksjon __construct ($ userName, $ reason) $ message = sprintf ('Kan ikke legge til bruker% s. Årsak:% s', $ userName, $ reason); foreldre :: __ konstruere ($ melding, 13, null);
Klassen som utfører vårt tilpassede unntak, er som alle andre klasser, men det må utvides Unntak
. Bruke tilpassede unntak gir oss også et flott sted å gjøre all presentasjonsrelatert strengmanipulering. Ved å flytte sammenhengen her fjernet vi også presentasjonen fra forretningslogikken og respekterte prinsippet om enkeltansvar.
offentlig funksjon legg til ($ brukernavn) if (! $ brukernavn) kaste ny unntak ('Brukeren kan ikke være tom!'); hvis (! $ this-> systemCommand-> utfør (sprintf ('adduser% s', $ brukernavn))) kaste ny ExceptionCannotAddUser ($ brukernavn, $ this-> systemCommand-> getFailureMessage ());
Å kaste vårt eget unntak er bare et spørsmål om å endre den gamle "kaste
"kommandoen til den nye og sende inn to parametere i stedet for å skrive meldingen her. Selvfølgelig går alle tester forbi.
PHPUnit 3.7.28 av Sebastian Bergmann ... Tid: 18 ms, Minne: 3,00Mb OK (2 tester, 4 påstander) Utført.
Unntak må fanges på et tidspunkt, med mindre du vil at brukeren skal se dem som de er. Hvis du bruker et MVC-rammeverk, vil du sannsynligvis få tak i unntak i kontrolleren eller modellen. Etter at unntaket er fanget, blir det omgjort til en melding til brukeren og gjengitt i ditt syn. En vanlig måte å oppnå dette på er å skape en "tryAction ($ handling)
"-metoden i programmets basestyring eller -modell, og kaller det alltid med gjeldende handling. I den metoden kan du gjøre fangstlogikken og finmeldingsgenerering som passer til rammen din.
Hvis du ikke bruker et webramme eller et webgrensesnitt for den saks skyld, bør presentasjonslaget ditt ta vare på og omdanne disse unntakene.
Hvis du utvikler et bibliotek, vil det være ditt kunders ansvar å ta tak i dine unntak.
Det er det. Vi traverserte alle lagene i vår søknad. Vi validerte i JavaScript, HTML og i våre modeller. Vi har kastet og tatt unntak fra vår forretningslogikk og til og med laget våre egne unntak. Denne tilnærmingen til validering og unntakshåndtering kan brukes fra små til store prosjekter uten alvorlige problemer. Men hvis valideringslogikken din blir svært kompleks, og ulike deler av prosjektet bruker overlappende deler av logikken, kan du vurdere å trekke ut alle valideringer som kan gjøres på et bestemt nivå til en valideringsservice eller en valideringsleverandør. Disse nivåene kan inkludere, men ikke å være begrenset til JavaScript-validator, backend PHP-validator, tredjeparts kommunikasjonsvalider og så videre.
Takk for at du leser. Ha en fin dag.