You are on page 1of 563

Web Development with Zend Framework 2 Concepts, Techniques and Practical Solutions MichaelRomer Thisbookisforsaleathttp://leanpub.com/zendframework2-en Thisversionwaspublishedon2013-03-10 ThisisaLeanpubbook.LeanpubempowersauthorsandpublisherswiththeLeanPublishing process.

LeanPublishingistheactofpublishinganin-progressebookusinglightweighttool sand manyiterationstogetreaderfeedback,pivotuntilyouhavetherightbookandbuildtractiono nce youdo.

2012-2013MichaelRomer

Contents Aboutthebook 1 EarlyAccessEdition...................................... 1 Thebook sonlinecommunity................................. 1 ImportantnoticeforAmazoncustomers ........................... 1 Introduction 2 Forwhomisthisbook?..................................... 2 You&I ............................................. 3 Structureofthisbook ..................................... 3 Repetitions ........................................... 4 Howyoucanbestworkwiththisbook............................ 4 Foundabug?.......................................... 4 Conventionsusedinthisbook................................. 5 ZendFramework2-Anoverview 6 HowFrameworkisbeingdeveloped.............................. 6 Modulesystem......................................... 7 Eventsystem.......................................... 9 MVCimplementation ..................................... 10 Additionalcomponents..................................... 11 DesignPatterns:Interface,Factory,Manager,etc. ...................... 13 Hello,ZendFramework2! 16 Installation ........................................... 16 ZendSkeletonApplication ................................... 17 Composer............................................ 17 Afirstsignoflife........................................ 19

DirectorystructureofaZendFramework2application ................... 20 Theindex.phpfile ....................................... 23

CONTENTS Preparingone sownmodule 28 Preparingthe HelloWorld module ............................. 28 Autoloading........................................... 34 OnetimeRequestandbackagain 40 ServiceManager......................................... 40 Writingaserviceofone sown................................. 44 ModuleManager ........................................ 50 Application........................................... 59 ViewManager.......................................... 62 Summary............................................ 64 EventManager 66 Registeringalistener...................................... 66 Registeringseverallistenersatthesametime......................... 67 Removingaregisteredlistener................................. 68 Triggeranevent ........................................ 69 SharedEventManager...................................... 70 Usingeventsinone sownclasses............................... 72 Modules 82 The Application module................................... 82 Module-dependentbehaviour................................. 84 Installingathird-partymodule ................................ 86 Configuringathird-partymodule............................... 88 Controller 92 Concept&modeofoperation................................. 92 Controllerplugins ....................................... 92 Writingone sowncontrollerplugin.............................. 98

CONTENTS Views 100 Concept&modeofoperation.................................100 Layouts.............................................102 Writingaviewhelperofone sown..............................103 Model 105 Entities,repositories&valueobjects .............................106 Businessservices&factories..................................108 Businessevents.........................................108 Routing 109 Introduction...........................................109 Definitionofroutes.......................................109 Matchingtest..........................................110 GenerationofaURL......................................117 Standardrouting........................................118 Creativerouting:A/Btests...................................120 Dependencyinjection 121 Introduction...........................................121 Zend\Diforobjectgraphs ...................................126 Zend\Diforconfigurationmanagement............................134 PersistencewithZend\Db 137 Connectingtodatabases....................................138 GeneratingandrunningSQL-Statements...........................140 Workingwithtablesandentries................................143 Organisationofdatabasequeries ...............................147 Zend\DBalternative:Doctrine2ORM ............................157 Validators 158 Standardvalidators.......................................158 Writingyourownvalidators..................................160

CONTENTS Webforms 162 Preparingaform........................................162 Displayingaform .......................................165 Editingformentries ......................................166 Validatingformentries.....................................167 Standardformelements ....................................174 Fleldsets.............................................176 LinkingentitieswithForms ..................................181 Developers Dairy 194 Introduction...........................................194 Envisioning...........................................194 Sprint1-CodeRepository,DevelopmentEnvironmentandtheinitialCodebase......194 Sprint2-Acustommodulewithaddproductfunctionality.................198 Sprint3-Addadeal,showavailabledeals..........................229 Sprint4-Orderform......................................253 Sprint5-MakeZfDealsavailableasaZF2module......................257 What snext? ..........................................267

About the book Early Access Edition Ifyouarereadingthissection,youareholdingthe earlyaccessedition ofthisbookinyourhands . Earlyaccess meansthatyoucanalreadybeginreadingthebookwhilethesubsequentchapters arestillbeingwritten.Theywillbemadeavailabletoyouassoonastheyarefinished.Thanks totheleanpublishingconcept,youarealsonowmuchmoreuptodatethanyouhaveeverbeen before.ThecontentsofthisbookrefertoVersion2.0ofFramework,theywillveryprobablyals o unconditionallyvalidtolaterversions.Itisverypossiblethatyoumightfindanumberofspe lling errorsoralsoabugortwointhecodeexamplesintheearlyaccessedition.Atthepresenttimeno onehassupportedmewithproofreadingoreditinginordertoreducethenumberoferrorsinthis booktoaminimum.Naturally,Iattempttobeworkascarefullyaspossible,butIcannotguarant ee absolutefreedomfromerrorsatthistime.Pleasebearwithme.Ifyouwouldliketohelpmeimpro ve thisbook,feelfreetoparticipateinthebook sonlinecommunityandletmeknowaboutyourideas oranyissuesyoudiscoverwithininthebook.Iwouldgreatlyappreciateit. The book s online community Foundabugwithinthebook?WanttotalkaboutthebookcontentsorZendFramework2ingeneral? Pleasefeelfreetojointhebook sonlinecommunity,theGoogle-Group WebDevelopmentwith ZendFramework2Book .YouwillneedaGoogleAccountwhichmaybesetupfreeofcharge. Important notice for Amazon customers IfyouboughtthisbookatAmazon,itiscurrentlysomewhatmoredifficulttoensurethatyou automaticallyreceiveallofitsupdatesastheyarepublished.Toavoidproblems,pleasesend ashort emailwith UpdatesZF2book inthesubjectlinetozf2buch@michael-romer.de.Inthismanner, youcanbecertainthatyouwillreallyalwayshavethelatestversionofthebook.Asthanksfor youreffortsyouwillalsoadditionallyreceivethePDFandEPUBversionsofthebook. http://leanpub.com/

https://groups.google.com/forum/#!forum/web-development-with-zend-framework-2-boo k 1

Introduction ZendFramework2isanopensourceFrameworkforthedevelopmentofprofessionalwebapplicati onsforPHPVersion5.3. andhigher.Itisoperatedontheserverandisprimarilyusedtoproduce dynamicwebcontentsorconducttransactionssuchaspurchasingaproductinanonlineshop. Inthiscontext,ZendFramework2cansupportthedevelopmentofanytypeof(web)application becauseitprovidesuniversallyapplicablesolutionsthatcanbeusedinecommerce,content, communityorSaaSapplicationsequallywell.ZendFrameworkisessentiallybeingdevelopedb ythe ZendCompany,whichalsogaveFrameworkitsnameandprovidesitwithasold(financial)basis, notleastalsobecauseZenditselfisalsoinvolvedinthedevelopmentofthePHPprogramming language.However,inadditiontoZend,anumberofprestigiouscompaniesalsosupportZend Frameworkandareinterestedinitslong-termsuccess.AmongthemarealsoMicrosoftandIBM. AlloftheaboveprovidessecurityfortheselectionofZendFrameworkandalsomakesitsselect ion agooddecisionasthebasisforone sownapplicationfromaneconomicpointofview. ZendFrameworkinthestableVersion1.0firstappearedonthesceneon30/06/2007andhassince madealastingimpressiononthefaceofwebdevelopmentwithPHPOnecantrulysaythat programmingwithPHPalsofirstbecamereallyacceptableforapplicationscriticaltocompan ywith ZendFramework.Ifonepreviouslyputone strustincomplexJ2E(Java)applicationsincorporat e contexts,inthemeantimePHPandZendFrameworkaregladlychosenwithaclearconscience becausethiscombinationindeedprovidesabalancedratiooflightnessandprofessionalismt hat practicallynootherplatformcanachieve. IfonetakesthefirstpreviewreleasesofZendFramework1intoconsideration,todayFramewor kis alreadymorethan6yearsold.Evenifmuchhasbeenachievedintheinnumerablereleasesfrom thefirstpreviewuptothecurrentversion1.11,anumberofurgentlyneededimprovementsand extensionscouldnolongerbeimplementedontheoldcodebase;thisjustifiesanewmajorrelea se, whichisforthefirsttimenolongercompatibletoearlierversions.Itistimeforanewbeginni ng. ZendFramework2marksthenextmilestoneintheevolutionofthePHPwebframeworks,but alsoofPHPitself,forasonehasrecentlyseenontheexamplesofJavaandRuby,agood programminglanguagealoneisnotenoughtoalsobereallysuccessfulonabroaderfront.Only withframeworks suchasStruts,Spring,RailsorindeedalsoZendFramework thatarebasedon theprogramminglanguageandalsosignificantlyreducetheinitialdevelopmenteffortbutal so, long-termmaintenanceeffortofanapplication,doesawebdevelopmentplatformreallybecom e established. For whom is

this book? Itisachallengetowriteatechnicalbookwhichfindsabalancebetweentheoryandpracticeand allowsbothnovicesandprofessionalstogetthebestoutofthebook.Igladlyacceptedthistas k,but Ileftmyselfanescapehatch.IfIhavethefeelingthatwearegettinglostinaforestofdetails that 2

Introduction cannotbeexplainedingreaterdetailattherespectivelocationorevenentirelyinthisbook, Irefer thereadertopassageslateroninthebookortosecondaryliteraturesources. AfurtherchallengeiswhetherornotmyreadershaveanypreviousknowledgeofZendFramework. AdeveloperwhohasonlyjustbeguntoworkwithVersion2ofZendFrameworkrequiresdifferent informationinsomeplacesthanan oldhand ,quicklyfindshisorherwayaroundthemany cornersinVersion2becauseheorsheisalreadyfamiliarwithideasandconceptsfromVersion1 . ForallthosewhoarefamiliarwithVersion1,IwillfrequentlyrefertothepredecessorofVers ion2 atappropriateplaces wheneverIconsideritnecessary withoutgoingintoexcessivedetail.Tha t mightalsoperhapshelpnovices,becauseinthismannertheywouldgetabetterfeelingforwhy Version2isnecessary.ThisbookshouldbehelpfulforbothnovicesandadvancedlearnersofZe nd Framework. IpresumethatyouhavebasicknowledgeofPHP.YoudonothavetobeaPHPexpert,particularly becausemany native PHPfunctionsevenbecameobsoletewhenoneusedFramework,for example,SessionManagement,whichmapsinanobject-orientedmannerandinthismanner thankfullyabstractssomeofthelow-levelfunctionality. Hence,ifyouareaccustomedtoPHP syntax,haveabasicunderstandingoftheoperatingprinciplesofPHPapplicationsandarefam iliar withthecommonfunctionsofthelanguagecore,youarewellprepared.Ifnecessary,youwillal so havetouseaPHPhandbook. You & I Hi,IamMichael.Ihopethatyouwon tobjectifIoccasionallyusecontractionsandthelessforma l you insteadof one inmyexplanations.Thatmakeswritingiteasierformmeandensuresaless formalatmosphere. Structure of this book Thisbookisnotmeanttobeacompendium,butratherapragmaticandpractice-oriented introductiontothebasicconceptsandpracticalworkwithZendFramework2.Fromacertain pointinone sprogressasadeveloperonward,theofficialdocumentationservesasacompendium forexperienceddevelopers,butitisnotreallyappropriateforusewhileyouarebecomingfam iliar

withthesubject;instead,itservesasan(indispensible)referenceworkforfurtherdetaile dquestions afteronehasachievedtherequiredbasicunderstandingoftheprogram.Andexactlythatisthi s book sobjective. Itisstructuredsuchthatyoucanreaditfromthebeginningtotheend,andthatiswhatyoushoul d do.WewillbeginwithanoverviewofFrameworkandwillfirstlookattheessentialconceptsand ideaswhichmakeuptheessenceofFrameworkandalsodifferentiateitfromitspredecessors.O n theway,wewillrepeatedlyalsolooktotheleftandtotherightandthusbecomefamiliarwith someframeworkconditions,forexamplehowFrameworkwasreallydeveloped.Thenwewillgo

Introduction intomoredetailandelucidateFramework smostimportantcomponentsandrelationships;then takealookathowaHTTPrequestisprocessedandwriteourfirstbitofcode.Thisisfollowed byanexcursionintotheFrameworkenvironment.Wewillexaminethemostimportantmodules, whichhavebeenmadeavailablebythirdpartiesand,forexample,maketherealisationofauser administrationofone sownobsolete.AlargepartofthemagicofthenewFrameworkversionisthe resultofthemoduleconcept,andmodulescan aswewillseelater greatlyaccelerateandsimplify thedevelopmentofyourownapplications.Itistobeexpectedthatinashorttimealargenumber ofhigh-qualitymoduleswillbeavailableforZendFramework2. Lastbutnotleast,thereisanadditional,intensivepracticesectionintheformofa developer s diary . Wewilldevelopawebapplicationtogether,whichisalsointendedtoreallybeused subsequentlybyabusinessenterprise;forthisreasonitisbetterifwereallytryhard.Atthe latest, wewillbegindoingreal hands-on workatthispoint. Repetitions YouwillsoonrealisethatIfrequentlyexplaincontextsseveraltimesinthisbookThiscanbed ue totwothings:1)thatIlosttrackofwhatIhadalreadysaidandwhatIhadnot(:-D)or2)that Iconsciouslyintendedtodoit.ForeventheoldRomansknewthat:Repetitiomaterstudiorum est Repetitionisthemotheroflearning. Butalsothefactthattherespectivecontextsarein adifferentcontextineachcaseandthusdiscussedfromanotherperspectiveisalsoconducive to comprehension.Thus,ifyouhappentofindaplaceinthebookwhereyouthink, Ialreadyknow allthat! ,justbegladthatyouhavelearnedsomuchandcontinuereadingorjustskiptherespect ive passage. How you can best work with this book Programming(orhandlingaprogrammingframework)isbestlearnedwhenyoubecomeactive yourself.Indeed,particularlythefirstchapteruptothepracticesectionisalreadyhelpfu leven withoutanopenedIDE,butyouwillhavethegreatestpossiblelearningsuccessifyoureproduc e someofthelinesofcodeorwritesomeyourself.Intheidealcase,youhaveasystemwithadebugg

er athand,withwhichyoucanfollowFramework smodeofoperationstepbystep.Someknowledge ofGitandaGitHubaccountwouldnothurtanythingeither,butarenotessential. Found a bug? Haveyoufoundabuginthetextorcode?Pleasefeelfreetofileabugticketinthebugtracker.I amthankfulforyourfeedbackandsupport. https://github.com/michael-romer/zf2book/issues

Introduction Conventions used in this book Code examples ThelistingsinthisbookhaveSystaxHighlighting,whereverpossible,butdonotalwaysconfo rmto acodingstandard;thisservestomakethemmorelegible.PHPcodeisintroducedbya<?php,and a// [..] intherespectivelistingindicatesexcludedcodefragments. ManylistingshavealinktoGitHub,wheretheycanbedownloadedintheformofaso-called gist orsimplybeadoptedwith Copy&Paste .Inthismanner,codeexamplescaneasilybere-enacted onyourownsystem. Command line Whenacommandhastobeexecutedonthecommandline,thisissymbolisedbyaprecedingdollar sign($).Thevisualfeedbackofthecommandisindicatedbyapreceding greaterthansign (>). Example: 1 $ phpunit --version 2 > PHPUnit 3.6.12 by Sebastian Bergmann. So,thatisenoughoftheforeword,let sgetdowntowork!

Zend Framework 2 -An overview BeforewebegintoimmerseourselvesinthedetailsofFrameworkinthefurthercourseofthe book,weinitiallywanttogetanoverviewandtoelucidatesomeofthecoreaspectsandthoughts underlyingFramework.WhatareFramework smaincharacteristics? How Framework is being developed ZendFramework2isopensource.Initially,thismeansthatanyonecanexaminethesourcecode anduseitforhisorherownpurposes. Frameworkisbeingdevelopedunderthe NewBSD License .Whereasundertheoriginal BSDLicense itwasstillnecessarytorefertotheuseof alibraryoraframeworkunder BSDLicence ,thisisnotnecessaryforZendFramework2;the so-called MarketingClause doesnotexistinthe NewBSDLicense .Wedonotwanttodriftoff intosoftwarelicensinglaw,butdowanttosayatthispointthat,withreferencetothelaw,tha t Frameworkismakingthingseasyforusbecausethe NewBSDLicense indeedbelongstothesocalledf reesoftwarelicenses, whichhavepracticallynolimitationsordirectivesfortheuseofthe code. WhereasVersion1stillusedSVNforthecodeadministration,theprojectteamforthenewrelea se decidedtoadministertheFrameworkcodeonGitHub4.GitHubsupportsthejoint,distributedw ork onthecodebaseverywell,especiallysincemanyprogrammersarenowinvolvedinthedevelopme nt andarespreadacrosstheglobe. Thus,RobAllenresidesinEngland,whereasMatthewWeier O Phinney,ProjectHeadforZendFramework2livesintheUSA,andBenScholzen,inGermany. Theabove-mentionedprogrammersareallso-called codecontributors ,i.e.theyhavealreadyma de asignificantcontributiontoFrameworkandthushaveaspecialstatusintheteam.Theydecisi vely shapeFrameworksstructureanddesign.SomeoftheprogrammersaredirectlyemployedatZend, forexampleMatthewWeierO Phinney;othersarefreelancersorhaveotherworkingconditions whichallowthemtocollaborateonZendFramework duringoraftertheirworkinghours.Thus, itisacolourfulgroupofgoodpeople.ZendFramework scomponent-orientationallowstheuseof so-called ComponentMaintainers5 ,whoareresponsibleforaspecificFrameworkcomponent,and theythemselvesorjointlywithotherprogrammerscontrolthefateofacertaincomponent. TheteamorganisesitselfprimarilyviaWiki6,mailinglistsandIRCchats.The ZendFramework

ProposalsProcess provideseveryonewiththepossibilityofsubmittingsuggestionsforthefu rther developmentofFrameworkandregular BugHuntingDays helpresolveknownFramework problemsinafocusedmannerbyjoiningforces.NewversionsofFramework,withwhichbug fixesandnewfeaturesaremadeavailable,arepublishedregularly. 4https://github.com/zendframework/zf2 5http://framework.zend.com/wiki/display/ZFDEV2/Component+Maintainers 6http://framework.zend.com/wiki/dashboard.action 6

ZendFramework2-Anoverview 7 The PSR-2 Coding Standard ThecodeofFrameworkitselfisbeingdevelopedacrossallcomponentsunderconsiderationof thePSR-2CodingStandards7. Theideaofthe PHPSpecificationRequest , PSR forshort, wasinspiredbytheJavaSpecificationRequest8.Usingthisprocedure,newJavastandardsare definedandextensionsoftheJavaprogramminglanguageortheJavaruntimeenvironmentare jointlydevelopedandareagreeduponbyallmanufacturers.Thisprocedurehasmanyadvantage s fortheapplicationdeveloperbecauseitmakestheapplicationpropermuchmoreportableand manufacturer-independent.Thus,itispossibleina(moreorless)simplemanner,forexample , tochangetheproviderofone sownapplicationserver. The PHPSpecificationRequests arebasedonasimilaridea theyshould,inparticular,ensure thatsoftwarecomponentsmadebydifferentmanufacturersandframeworksarecompatibletoon e anotherandcanbeusedincombination.Incontrastto JSR ,the PSR procedureisrelativenew. Todateonlythreespecificationshavebeenagreedupon. PSR-0:DefinesthecoherencybetweenPHPnamespacesandtheorganisationofPHPfilesin afilesysteminordertomaketheautoloadingofclasses,whichshouldalsobecomponentandman ufacturerindependent,assimpleaspossible. PSR-1/PSR-1basic:Anewcommoncodingstandard. PSR-2:AnextensionofthePSR-1CodingStandard. AprimaryfocusofZendFrameworkVersion2,aswewillrepeatedlyseeinthecourseofthisbook, isonthefunctionalextensionofanapplicationbymeansofreusableandsimplyintegratedmod ules. CompliancewiththePSRstandardisagreathelpinthiscontext. Known problems Asisthecaseforeverylargepieceofsoftware,Frameworkisnotcompletelyfreeoferrors.Ina consequentmanner,githubissues.isusedfortracking .Thus,ifyoudiscoveraproblem,itis worthwhiletoinitiallylooktheretoseewhethertheerrorisalreadyknown.Ifnot,youcanope na threadthere. Module system

DasmodulesystemisthecentralhubofVersion2.MatthewWeierO Phinneystatedthisextremely clearlyinthemailinglist: 7https://github.com/pmjones/fig-standards/blob/psr-1-style-guide/proposed/PSR-2advanced.md 8http://de.wikipedia.org/wiki/Java_Specification_Request .https://github.com/zendframework/zf2/issues?state=open

ZendFramework2-Anoverview 8 ModulesareperhapsthemostimportantnewdevelopmentinZF2. TheFrameworkModuleSystemwascompletelyreworkedforVersion2. Eventhoughitwas indeedalreadypossibletoorganiseone sowncodeinmodules,thosemoduleswereneverreally independentlyusablenorcouldtheybetransferredtootherapplicationswithouthavingtofi tthe moduleintotherespectivecodewithagreatdealofeffortthere. Theeffortofdoingthiswas normallysogreatthatonecouldjustaseasilyprogramthefunctionhim-orherselfThemodule systemofVersion1wasthusrestrictedtotheadvantagesofabettercodeorganisationwithina self-containedapplication.Theresultisthatwenowprobablyhavethousandsofimplementat ions fortheauthenticationofauser,orsimilarfunctions,whicharegenerallyapplicablebecaus ethey arenotrestrictedtoaspecificapplication. Oneisaccustomedtoaddingfunctionalextensionsintheformofpluginsorextensionsfrom applicationssuchasWordPress,DrupalorMagento.EvenSymfony2 apopular,alternativeweb framework withitsbundleshasalreadyhadamoduleconcept,whichmakesit,forexample, possibletointegratethefunctionalityofacontentmanagementsystem(CMS)intoone sown applicationwithouthavingtoprogramitoneself,forsometime. AndnowZendFramework hasalsoincludedthisextensionoptioninitsnewversion.Let stakeaconcreteexampleagain: Anapplication,whichwasdevelopedonthebasisofZendFramework,subsequentlyadditionall y requiresthefunctionalityofablog.Thisisacustomaryrequirementbecauseablogisextreme ly practical,forexample,asanSEOmeasure.AnyonewhohasworkedwithWordpress&Co.knows howcomprehensivetherequirementsforamodernblogsystemhavemeanwhilebecome.Itquickly becomesclearthatitisnotagoodideatonowdevelopone sownbloggingsoftware. Instead,itappearsappropriatetouseoneoftheavailablefreeorcommercialbloggingsystem s, whichhoweverduetoitsconceptcanonlybesetupinparalleltoone sownapplication.This hasanumberofdisadvantages:Forexample,itisnoteasilypossibletousetheloggingsystemo f one sownapplicationwithoutfurtherado;noristhispossibleforthecachinglayer.Theblog s dataarelocatedinanotherdatabase,andifwewanttodisplaythelast3blogpostteasersonour applicationshomepage,wehaveimplementthistie-inviaoneoftheblog sAPIs,possiblyover anRSSfeedorsomethingsimilar(ormessaroundinthedatabaseofathirdpartyapplication ). Naturally,theadministratoraccountswhichallowouremployeestoadministercustomermast er datadonotexistintheblogsystem,andweshouldnotevenmentionSingle-Sign-On.Wehaveto simulateourcorporatedesignintheblog stemplatesystembecauselayout,markupandstylesar e notreadilyavailablethere.Anyfuturedesignadaptationswillalsoalwayshavetobereconst ructed there.Ourapplication sbuildscriptscannotbeusedfortheblog;incontrast,thereleaseproc

esses mustbeadaptedsothatwecansomehowalsoincludethethirdpartyblogsystem.Thus,with thisapproachweskiddirectlyintothecomplexityofEnterpriseApplicationIntegration(EAI) andServiceOrientedArchitecture(SOA),andinthismannerwecreateacolourfulbouquetof newproblemsandchallengesforourselves.IfinsteadablogmoduleforZendFramework2were http://symfony.com/ http://de.wikipedia.org/wiki/Single_Sign-on http://de.wikipedia.org/wiki/Enterprise_Application_Integration http://de.wikipedia.org/wiki/Serviceorientierte_Architektur

ZendFramework2-Anoverview 9 availabletouse onewhichwouldseamlesslyintegrateitselfintheexistingauthentication,b uild &release,caching,logginganddesignimplementations everythingwouldbemuchmoresimple, indeednearlytrivial.Andexactlythistrainofthoughtisthecoreideaofthemodulesystem. AZendFrameworkmodulebringseverythingwithitthatisrequiredforitsoperation.Thisincl udes notonlytheappropriatePHPcode,butalsotheHTMLtemplates,CSS,JavaScriptcode,images,e tc., sothataModuleisatrulyself-containedpackage.Agoodmodulecanbereadilyintegratedina ZendFramework2applicationand simplyruns.ThemodulesystemthusmakesZendFramework 2tomuchmorethanjustawebframework;indeeditgoesfarbeyondthisandisreallyaplatform forintegratedapplicationsandfunctions. Inthenextchapterwewillelaborateonthetechnicaldetailsofthemodulesystemandtakealoo kat howitfunctionsinternallyandhowmodulesaredevelopedbecauseevenone sownapplicationis representedincodebyamodule.ModulesareeverywhereinthenewZendFramework!Whenyou understandthemodules,that shalfthebattlebothforthedevelopmentofone sownapplication andforembeddingalreadyimplementedfunctionsandsystemsintoit. Furtheroninthebook,wewilltakealookattheavailablemodulesbecauseinadditiontothealr eady mentionedfunctionsforusermanagement,thereismuchmore;forexample,modulesrelatedto Doctrine24,thewell-knownPHP-ORM5system.Thesemoduleuseso-called gluecode ,which allowsonetoeasilyusethislibraryinaZendFramework2application Event system ZendFramework2isdecisivelybasedontheconceptofEvent-drivenArchitecture6.Thisapproa ch buildsontheideathatcertainactivitiesoccurinasystemafteraspecificeventhavepreviou sly takenplace.Toachievethis,activitiesregisterthemselvesforanalertwhentheeventoccur s.Ifthe eventoccurs,theregisteredactivitiestakeplace.Hereisananalogyfromtherealworld:Whe nwe waitatabusstop(wehave registered ourselvesfortheeventofthebus sarrival),andwhenthe busfinallydrivesup(eventoccurs),itsdooropens(activity1),webuyaticket(activity2), search foravacantseat(activity3),andthebusstartsup(activity4). Hereisanotherexample:AnarticleofferedbyeBayissold.Thiseventtriggersaseriesofacti vities inthesystem: Aconfirmationofpurchaseissenttothepurchaser.

Aconfirmationofsaleissenttotheseller. Salesfeesarecalculatedandchargedtotheseller saccount. Thearticleinquestionisremovedfromthesearchindex,sothatitcannolongerbefound(it hasalreadybeensold). 4http://www.doctrine-project.org/ 5http://de.wikipedia.org/wiki/Objektrelationale_Abbildung 6http://en.wikipedia.org/wiki/Event-driven_architecture

ZendFramework2-Anoverview 10 IfeBaydecidesatalaterpointintimetoalsoinformtheunsuccessfulbiddersthattheauction has ended(inrealitythishasalreadybeendone),thisactivitycanbeaddedtootheractivitiesfo rthis event. However,EDAisatwo-edgedsword.Ontheonehand,oneachievesanenormousflexibilityin structuringworkflowsbyemployingthisstyleofarchitecture.Newactivitiescanbeeasilya dded. Inthismanner,entireprocedurescansubsequentlybeeasilymodifiedinthismanner.Flexibi lity isthedecisiveargument.Incontrast,thereisacertainlackoftransparencyregardingtheth ings thatallreallytakeplaceintheapplicationwhenaneventoccurs.Fundamentally,anactivity for aneventcanberegisteredmoreorlessanywhereintheapplicationwithoutthisconnectionbei ng visibleatthelocationincodewheretheeventsubsequentlyreallyoccurs.Afurtherchalleng eis thesequenceoftheactivities.IfthesellerintheeBayexamplegivenabove,isalsonotifiedo fthe feesdue(whicharecalculatedonthebasisofthefinalpriceintheauction),thisactivitymus toccur afterthefeeshavebeencalculated(i.e.anotheractivityhadtotakeplace).Now,thingsbegi ntoget complicated.Inanutshell,EDAcanresultinprocessesthataredifficulttounderstand.Thec auses oferrorsaremoredifficulttoidentify,anddebuggingapplicationsismorecomplicated. Despitethis,theadvantagesoutweighthedisadvantagesbysomuch,particularlyalsoinconn ection withFramework smodulesystem,thattheprogrammersdecidedtouseEDAforZendFramework 2.Ifoneisfamiliarwiththepitfalls,onecaneasilyavoidthem. MVC implementation AlsoinZendFramework2,theimplementationoftheMVCpatternsisatthefocusofFramework. AlthoughZendFramework2alsoagainprovidestheoptionoffreelyusingitscomponentsand, forexample,ignoringZend\Mvc,inpracticalworkonewouldonlydothisinexceptionalcases. Indeed,inmostcases,itispreciselytheMVCimplementationandtheresultingadvantageousc ode structureinone sownapplicationthatisoftenthebasisfordecisionstouseZendFramework. Zend\Mvc structuresanapplicationviathelogicalseparationofcodecomponentsinto models , views and controllers ,andinthismannerensuresacertainorderthatisnotonlybeneficialfor theapplication sserviceabilityandextensibility,butalsopromotesthereuseoffunctions.

AndthisiswhatZend\Mvc inactionlookslike:AftertheZend\ModuleManager,thecentralunit intheModuleSystem,haspreparedalltheavailableModulesforuse,readintheconfiguration , andinitialisedadditionalcomponents,theZend\Mvc\Router ensuresthatasuitableController(a class)isinstantiatedinaModuleandthecorrectaction(amethod)isinvokedinit.Inthiscon text, theroutingisbasedonpreviouslyconfiguredroutes,whichrepresentthemappingbetweenURL s undControllersorActions,respectively.TheselectedcontrollerrefersbacktotheRequest Object tofurtherprocessit;thisallowsanobject-orientedaccesstotheRequestParameters,which is madeaccessiblebytheMvcEvent object.Thelatter,inturn,wasgeneratedatthestartandwill bemadeavailableattheappropriatelocations.Inthefurthercourse,thecontrollermakesus eofthe application smodelandaccessespersistentdata,servicesandbusinesslogic.Itultimatelyg enerates the viewmodel ,onthebasisofwhichandappropriateHTMLtemplatesaswellastheuseofthe

ZendFramework2-Anoverview 11 so-called viewhelper theresultoftheinvocationisgenerated.Optionally,theoutputisnowal so insertedintoalayoutandthefinalresultisreturnedtothecallingprogram.Butwewillgoint othat ingreaterdetaillater. SinceZend\Mvc extensivelyusestheFrameworkEventSystem,agreatmanyoptionsforinfluencing thestandardcoursesketchedaboveareprovidedhere.Inthismanner,forexample,accesscont rol oraninputfiltercanbeimplementedbeforethecontrollerisexecuted.Beforethereturnofth e results,onecouldensurethatthegeneratedHTMLmarkupiserror-free,ifnecessary,withthe aid ofHTMLPurifier7. ThecodefortheMVCimplementationisacompletelynewdevelopment.TheMVCimplementation inFrameworkVersion1wasstillratherinflexible,whereasthenewversionisdefinitelymore flexibleandultimatelyallowstheconfigurationofanyarbitrarilyadaptedworkflows.Basi cally,the proceduresketchedoutabovecanalsobecompletelydifferentlystructuredusingZend\Mvc without havingtodispensewiththeuseofZendFrameworkandtheadvantagesresultingfromitsuse. Additional components InadditiontoZend\Mvc,Zend\View,Zend\ServiceManager,Zend\EventManager andZend\ModuleManager, whichjointlycomprisethe frameworkcore ,ZendFramework2hasanumberoffurthercomponents, whichwewillbrieflyconsiderinthefollowing.Themajorityofthesecomponentswillalso beconsideredinadetailedmanneragaininthecourseofthisbook.Atthattimewewilldealwith eachoftherespectivecomponentsmoreintensively.Incontrasttomanyotherframeworks,Zen d Frameworkhasalwaysbeensoconceivedthatitisalsopossibletouseonlyselectedcomponents , whileothercomponentsareignored. Whenlookingatthelistofcomponents,thosewhohaveusedZendFramework1willnoticethat somecomponentsnolongerexist.Inparticular,themanycomponentsforlinkinguptodiversew eb servicesarenolongerpartofFrameworkinVersion2.0,butaremaintainedasindependentproj ects orlibraries.Thus,forexample,Zend_Service_Twitter isnolongerapartoftheprogram,butis nowadministeredintheGitaccountzendframework8initsownrepository.Theideabehindthis istosharpenFramework sprofileandtofocusitsapplicationarea.NordoesZend_Registry exist

inthenewversion.ItstaskisnowperformedbytheServiceManager,which(aswewillseeinthe furthercourseofthebook)canalsodomuchmore.Zend_Test hasalsobeenremoved.Thegood newsisthatessentially asaresultoftheloosecouplingoftheindividualobjectsandservices that predominatesinZendFramework2 noadditionalfunctions,otherthanthosealreadyavailable inPHPUnit.,arenowrequiredfor unittesting .Thisisverygoodnewsandaswewillseeina subsequentchapterandinthepracticesectionofthebook,unittestinghasbecomemuchsimple r inthenewversion. Asstandard,thefollowingcomponentsareadditionallycontainedinimZendFramework2. 7http://htmlpurifier.org/ 8https://github.com/zendframework .https://github.com/sebastianbergmann/phpunit/

ZendFramework2-Anoverview 12 Authentication:Servestoimplementa Login function,inwhichthecomputerchecks whetherornotauserreallyisthepersonwhomheclaimstobe(forexample,becausehe knowsthesecretpassword). Barcode:Libraryforgeneratingbarcodes. Cache &Memory:Genericimplementationofacachingsystemsunderconsiderationof different Backends ,forexample Memcached or APC . Captcha:GenerationofCAPTCHAs,forexampleforuseinwebforms. Code:Toolsfortheautomaticgenerationofcode. Config:Aidwhichhandlesreadingandwritingofapplicationconfigurationsinextremely differentformats,forexampleYAMLorXML. Console:Libraryforusingapplicationfunctionality,forexamplecontroller,viaashell(in stead ofonthebasisofaHTTPrequestsbyabrowser). Crypt:Functionsthathandleencryption. Db:Libraryforworkwithdatabases(butnoORMsystem). Di:ImplementationofaDependencyInjection(DI)Container. Dom:Libraryforserver-sideworkwiththeDOM. Escaper:Aidforoutputescaping. Feed:GenerationofRSSandatomfeeds. File:Aidforworkingwithfiles. Filter:Functionsforfilteringdata,forexampleintheframeworkofawebform. Form:LibraryforthePHP-assisted,object-orientedgenerationofwebformsunderconsiderationof Validators and Filters . Http:AidfordealingwiththeHTTP. I18n: Extensivelibraryfortheinternationalisationofapplications,e.g. theoutputof translatedcontents. InputFilter:Allowstheuseoffiltersandvalidatorsonreceiveddata. Json:ToolsfortheserialisationanddeserialisationofJSONdatastructures. Ldap:LibraryforthelinkageofLDAPsystems,forexampleinconjunctionwithAuthentication. Loader:AutoloadingfunctionsforPHPclassesaswellasforloadingMVCmodules. Log:Implementationofagenericloggingfunctionalitywithsupportofdifferenttypesof Log Memories . Mail &Mime:Libraryforsending(multipart-)emails. Math:Diversemathematicalaids. Navigation:Generationandoutputtingofwebsitenavigations. Paginator:Generationandoutputtingof SheetNavigations ,forexampleinresultslists. Permissions:Libraryforrightsandrolesystems. ProgressBar: Generationandpresentationofprogressbars(amongothersalsoonthe commandline) http://de.wikipedia.org/wiki/CAPTCHA

ZendFramework2-Anoverview 13 Serializer:Toolsfortheserialisationanddeserialisationofobjects,forexampleforlongt ermstorage. Server,Soap &XmlRpc:Libraryforthegenerationofwebservices,e.g.onthebasisofSOAP orXML-RPC.PartofSoap isalsoahelpfulSOAPClientimplementation. Session:Administrationofusersessions. Stdlib: Diversestandardfunctionsandobjects,forexampletheimplementationofa UserlandPriorityQueue ,which,e.g.,isusedbytheEventManager. Tag:Functionsfortheadministrationof Tags andthegeneration,forexample,of Tag Clouds onawebsite. Text:Aidthathandlesthemanagementofstringsandscripts.Isusedinternally,e.g.,by Captcha. Uri:FunctionsforthegenerationandvalidationofURIs. Validator:Extensivelibraryforthesyntacticvalidationofdata,forexampleofentriesin forms,formanyapplicationcases,amongthemISBN,IBAN,emailaddressesandmuchmore. Version:HoldsinformationontheusedFrameworkVersionandtheavailable,newestversion atGitHubinreadiness. Design Patterns: Interface, Factory, Manager, etc. IfonelooksthroughZendFramework2codeforatime,onenoticesthatitiscrammedfullof implementationsofso-calledDesignPatterns.Ifoneismorefamiliarwith typicalPHPcode andthatisnotintendedtobejudgementalinanyway itwilltakeawhilebeforeonefindsone s wayaroundZendFramework2.IfonehashadmuchtodowithJava,onewillfeelmuchmore rapidlyathome,simplybecause designpatterns foundtheirwayintotheJavaworldseveralyears earlierorevenevolvedthere,respectively.Tosimplyyouraccesstothematerial,let stakeal ook atafewordinaryZendFrameworkconstructsinthefollowing.NotallofthemarereallyDesign Patternsinastrictsense,butweshouldignorethisfactforthemoment.Andweshouldalsodo thesamewiththefactthatIuseserviceablesimplificationsinmyexplanationsatsomeplaces in thebook.Thisisnotacomprehensivebookondesignpatternsandtheknowledgetobeimpartedis primarilyintendedtohelpthereaderdevelopanunderstandingforZendFramework.Atthistim eI shouldperhapsrepeatthefollowingadvice:Ofcourse,asanapplicationdeveloperitisnotne cessary tounderstandallthemechanicsofZendFramework2indetail.Quitethecontrary:Frameworkis meanttoreducetheworkeffortandtomakeitpossibleforonetoconcentrateentirelyonthe programmingofthe businesslogic itself.However,itisagreathelpifonehasafundamental understandingoftheconnectionsbetweentheindividualFrameworkcomponents andevenmore important:ofthebasicconcepts.Thus,itisworthwhilenottoskipthischapter. http://de.wikipedia.org/wiki/Entwurfsmuster

ZendFramework2-Anoverview 14 Interface Interfacesareaninherentelementofobject-orientedprogramming,andPHPhassupportedthe m comprehensivelysinceVersion5.Interfacesallowonetodecoupleinvokingcodefromaconcre te implementation.Ifonealwaysdevelopsone sapplicationsforadefinedinterface,onecanrest assuredthatatruntimeaconcreteimplementationwithstipulatedmethodsandpropertieswil lbe available.Indeed,onecanalsouseanalternativeimplementationwithouthavingtomodifyth e invokingcode. Listener A listener isashortstringofcodethatisexecutedassoonasadefinedeventoccursinan application. Technicallyspeaking,toachievethis,alistenerisregisteredbeforehandbyasocalled EventManager (Attention:riskofconfusionwithapopularprofession)foranevent.In thismanner,theconnectionconvenesatthislocation. ListenerAggregate A ListenerAggregate herdsaseriesoflistenerstogether,forexample,inordertoregisterthem with an EventManager .Notmuchmore,butalsonoless. Factory A Factory isalwaysusedwhentheinstantiationofaspecificobjectiscomplicated,i.e.when anentireseriesofmanipulationsarerequiredtomakeanobjectreadyforuse.Forexample,Zen d FrameworkusesafactorytoinstantiateitsModuleManager andadditionallytoregisteranumberof module-relevantlisteners. Service A Service providesaccesstospecificfilesorfunctions.Thetermisextremelygeneralandcan haveacompletelydifferentmeaningineachcasedependingonthecontext. Inthescopeof FrameworkaserviceisunderstoodtomeananobjectthatismadeavailablebyaServiceManager andprovidesadefinedservice.Thus,forexample,listenersregisteredintheServiceManage r are

termedservices justas,e.g.,theModuleManager,butalsoontrolleror viewhelper ,are. Manager A manager isanobjectthatmanagestheadministrationofaspecifictypeofotherobjectinthe system.Forexample,Doctrine2hasaso-called EntityManager ,whichadministers entities ,i.e. http://www.doctrine-project.org/

ZendFramework2-Anoverview 15 certainpersistentobjects(e.g.,inashopofferings,categories,customers,orders,etc.) throughout theirentirelifecycleandensuresthattheentitiesarereadfromadatabaseandchangesare transparentlyreturned. Strategy BehindtheStrategyDesignPatternistheideaofswappingoutalgorithms,whichonewould otherwise hardwire attherespectivelocationinthecode,toaclassofitsownandthusto makeitexchangeable.Thus,sortingalgorithms,forexample,aregoodcandidatesforthisstr ategy pattern. Thedifferentalgorithms,accordingtowhich,e.g.,aproductlistscanbesorted(price increasing/decreasing,ratingincreasing/decreasing,etc.)arenotpermanentlyencoded, butinstead realisedintheformofaclassoftheirown,whichallimplementacommoninterface,which specifiesasort() method.Ifonehasonceimplementedthismechanism,anyotherarbitrarysorting procedurecanberealisedatalatertimeandthenbeadded. Model View Controller (MVC) TheMVCpatterndecisivelyaffectsthestructureoftheapplicationcodebecauseitlogically separates thosecomponentsfromoneanother,whichmanagethedisplay(View),theprocessingofuser interaction(Controller)and businesslogic withitsobjectsandservices. Actions Actions areanapproachforfurtherstructuringthecodeusedforprocessinguserinteractionsi n controllers.TheyarethereforecloselyconnectedwiththeMVCpatternandalsoareusedinZen d Framework2. View Helper Withtheaidof viewhelpers ,codeforpresentationlogiccanbeencapsulatedandreusedina standardisedway. Controller Plugins

Bymeansof ControllerPlugins frequentlyusedcodecanbeorganisedforinteractionprocessing andbeusedinseveralcontrollers.

Hello, Zend Framework 2! Putawayallthegreytheory let stakealookatFrameworkinaction Asdiscussedinthelastchapter,Zend\Mvc isanindependent,optional,butessentialcomponent ofFramework.ThefollowingcontextalwaysincludesZend\Mvc.Zend\Mvc alsodictatesitsown applicationandinacertainmanneralsothedirectoryandcodestructure.Butthatisactually quite practical.IfoneisalreadyfamiliarwithaZendFramework2application,onecanalsooriento neself veryquicklyinotherapplicationsthatarealsobasedonFramework.Althoughonedoesindeedh ave thefreedomtoestablishacompletelydifferentdirectoryandcodestructure,thiswouldmake life unnecessarilydifficult,aswewillseelater.Ifanapplicationtobecreated,itiswisetouse Zend\Mvc fromtheverybeginning.However,ifonewantstoextendanexistingapplicationwithfunction s fromZendFramework2,itisperhapsappropriatetoinitiallydispensewithZend\Mvc completelyor tofirstuseitatalatertime. ZendFramework1and2inparallelOnecanalsooperateZendFramework2inparalleltoVersion1a ndinitiallyonlyuseZendFramework2intermittently. Installation Inprinciple,ZendFramework2doesnothavetobetediouslyinstalled.Onesimplydownloads theCode,makesitavailableoverawebserverwithPHPinstallationandcanbeginimmediately. However,thefactthattheZendFramework2Codealoneisnotenoughtobeabletoactually seeaZend\Mvc-basedapplicationinactionisachallengebecause,aswehavealreadymentione d, Zend\Mvc,i.e.thecomponentswhichtakeovertheprocessingofHTTPrequests,isoptionaland accordinglyisalsonotinherently wired foruse. Onemustthusinitiallypersonallyensure thatZend\Mvc issoequippedwithconfigurationandinitialisationlogic theso-called boilerplate code thatitcanalsoactuallybeused.Otherwise,oneinitiallysees nothing. ToavoidthiseffortandtomakegettingstartedwithVersion2assimpleaspossible,theso-cal led ZendSkeletonApplication wasdevelopedinthecourseofFramework sdevelopment;thisserves asatemplateforone sownprojectandincludesthenecessary boilerplatecode ,whichonewould otherwisehavetoprepareoneselfwithgreateffort. http://packages.zendframework.com/

16

Hello,ZendFramework2! ZendSkeletonApplication Theinstallationofthe ZendSkeletonApplication isthesimplestwithhelpfromGit. Totake advantageofthis,itisfirstnecessarytoinstallGitonone sowncomputer.OnMacsystemsandin manyLinuxdistributions,Gitisevenalreadypreinstalled.ForinstallationonaWindows syst em, GitforWindows4isavailablefordownloading.TheinstallationunderLinuxnearlyalwaysruns undertherespectivepackagemanager.Afterinstallationandafterinvocationof 1 $ git --version onthecommandline,oneshouldseethisorasimilar signoflife : 1 > git version 1.7.0.4 Fromhereonwards,everythingisveryeasy changetothedirectoryinwhichthesubdirectoryfor theapplicationistobesetupandwhichcanlaterbemadeavailabletothewebserveras document root ,anddownloadtheZendSkeletonApplication. 1 $ git clone git://github.com/zendframework/ZendSkeletonApplication.git Admittedly,inGitjargonithastobetermed cloning andnot downloading .Butforthetime being,wewillignorethat.Andbytheway,donotbeafraidofGit!Onedoesnotneedanyadvanced GitknowledgeinordertosuccessfullyworkwiththisbookandFramework.Ofcourse,thereader canalsoadministerhisorherownapplicationcodeinthefutureevenpermanently withGit,but itisnotnecessary.Therefore,asubversion,CVSorevennosystematallcanalsobesubsequent ly usedforcodeadministrationwithoutproblems. Downloadingthe ZendSkeletonApplication isveryfast,evenforlessrapidInternetconnections , butonemustalwayshavesuchaconnectioninanycase.Thereasonforthefastdownloadisthefac

t thatFrameworkcodeitselfisnotdownloadedatall;insteadonlythecorrespondingboilerpla tecode forthedevelopmentofone sownapplication,whichisbasedonZendFramework2,isprovided. Composer The ZendSkeletonApplication useswithComposer5anotherPHTtool,whichestablisheditself forthemanagementofdependenciesforothercodelibrariessometimeago.Theideabehindthe composerisassimpleasitisingenious.Aconfigurationfilecontainsadefinitionoftheothe rcode librariesthatanapplicationisdependentonandfromwheretherespectivelibrarycanbeobta ined. Inthiscase,theapplicationisdependentonZendFramework2,ascanbeseenbylookinginthefi le composer.jsonintheapplicationroot. 4http://code.google.com/p/msysgit/ 5getcomposer.org/

Hello,ZendFramework2! 1 { 2 "name": "zendframework/skeleton-application", 3 "description": "Skeleton Application for ZF2", 4 "license": "BSD-3-Clause", 5 "keywords": [ 6 "framework", 7 "zf2" 8 ], 9 "homepage": "http://framework.zend.com/", 10 "require": { 11 "php": ">=5.3.3", 12 "zendframework/zendframework": "2.*" 13 } 14 } Listing4.16 Inlines11and12,theapplication stwodependenciesaredeclared.BothPHP5.3.3orhigherand thecurrentversionofZendFramework2arerequired.Thefollowingtwoinvocationsensuretha t ZendFramework2isdownloadedandadditionallyalsointegratedintheapplicationsuchthati tis immediatelyutilisableandthecorrespondingFrameworkClassesaremadeavailablebyautolo ading. 1 $ cd ZendSkeletonApplication 2 $ php composer.phar install

3 > Installing zendframework/zendframework (dev-master) ComposerhasnowdownloadedZendFramework2andmadeitavailablefortheapplicationinthe vendor directory. Phar-ArchiveAPharArchiveprovidestheoptionofmakingaPHPapplicationavailableinthefo rmofasinglefile.IfonelooksattheComposer-RepositoryatGitHub.,itbecomesclearthatco mposerdoesnotconsistofasinglefile,asonemightthink, butthatitscomponentsaremerelybundledinaPharArchivefordistributionoftheapplicatio n. .https://github.com/composer/composer 6https://gist.github.com/3820657

Hello,ZendFramework2! PharArchiveandSuhosinIf Suhosin .isusedonasystem,theuseofPharmustinitiallybeexplicitl ypermittedsuchthatthesuhosin.iniisextendedbytheentrysuhosin.executor.include.whi telist=phar.Otherwise,problemscanoccurintheexecutionoftheComposercommand. .http://www.hardened-php.net/suhosin/ InstallationwithoutGitorComposerIfnecessary,itisalsopossibletoobtainFrameworkand the ZendSkeletonApplication viaa normaldownload orPyrus(thesuccessortoPEAR).Additionalinstallationinform ationistobefoundontheofficialdownloadsite.. .http://framework.zend.com/downloads A first sign of life Wehavenowcompletednearlyalloftherequiredpreparations.Finally,weonlyhavetoensure thattheapplication spublic directoryisconfiguredasDocumentRootofthewebserverandcan becalledup/invokedviatheURL http://localhostbythebrowser. Forexample,toachievethis,adirectiveinfollowingexemplaryformmustbespecifiedinthe httpd.confofApache: 1 // [..] 2 DocumentRoot /var/www/ZendSkeletonApplication/public 3 // [..] whereitisrequiredthatthe ZendSkeletonApplication wasdownloadedwiththefollowing commandbeforehand: 1 $ cd /var/www 2 $ git clone git://github.com/zendframework/ZendSkeletonApplication.git

Hello,ZendFramework2! SettingupaPHPruntimeenvironment. Sincethescopeofmyreaders previousknowledgeisprobablyextremelydifferent,IwillnotexplainexactlyhowawebserverisinstalledonasystemtogetherwithPHPatthis time,butinsteadpresumethatmyreadersalreadyknowthis. Foranyonewhoneedsassistance,additionalinformationandsupportaretobefoundintheAppe ndixofthisbook Iftheseconfigurationshavebeenmade,ZendFramework2shouldshowitselfforthefirsttime whenhttp://localhost iscalledupinthebrowser:

StartpageZendSkeletonApplicationofZendFramework2 Directory structure of a Zend Framework 2 application Now,wecanfinallylookatit,aZend\Mvc-basedZendFramework2applicationwithitscharacte risticdirectorylayoutandthetypicalconfigurationandinitialisationcode: 1 ZendSkeletonApplication/ 2 config/ 3 application.config.php 4 autoload/ 5 global.php 6 local.php 7 ... 8 module/ 9 vendor/

Hello,ZendFramework2! 10 public/ 11 .htaccess 12 index.php 13 data/ Inourcasethe ApplicationRoot istheZendSkeletonApplication directory,whichisautomaticallygeneratedbycloningtheappropriateGitHubrepository. Intheconfig directory,thereis,on theonehand,application.config.php,whichcontainsthebasicconfigurationforZend\Mvc and itscollaboratorsasaPHParray. Inparticular,theModuleManager isconfiguredthere;wewill frequentlytalkaboutitsdetailsinthecourseofthebook. Ifrequired,theautoload directory containsadditionalconfigurationdataintheformofadditionalPHPfiles;initially,thiss eems abitstrange,butonebecomesaccustomedtoit.Tobeginwith,thedirectory sdesignationas autoload isabitirritating.Inthislocation, Autoload hasnothingtodowiththe Autoloading ofPHPClasses,butinsteadindicatesthattheconfigurationsthatarefiledinthisdirectory willbe automaticallytakenintoconsideration.Andthatoccurschronologicallyaftertheconfigur ationof theapplication.config.php andalsoaftertheconfigurationsperformedbytheindividualmodules, whichwewilltalkaboutlater.Thissequenceofconfigurationevaluationisextremelyimport ant becauseitallowsthesituation-dependentoverwritingofconfigurationvalues.Thesamepri nciple appliestoglobal.php undlocal.php:configurationsintheglobal.php arealwaysvalid,butthey canbeoverwrittenbyconfigurationsinthelocal.php.Technicallyspeaking,Frameworkinit ially readsintheglobal.php andsubsequentlythelocal.php,wherebypreviouslydefinedvaluescan bereplaced,ifnecessary.Whatisthatgoodfor?Inthismanner,configurationscanbedefined independentlyoftheruntimeenvironment.Letusassumethattheprogrammersofanapplicatio n havesetuparuntimeenvironmentlocallyontheircomputers.SinceaMySQLdatabaseisrequire d fortheapplication,allofthedevelopershaveinstalledthisontheircomputersandinthepro cesshave configuredtheaccessrightssuchthatpasswords,whichtherespectivedevelopersalsoother wise frequentlyuse,areutilised.Itisindeedmoreconvenient.However,sinceeachdeveloperpot entially hasanindividualpasswordforthedatabase,thisconfigurationcannotbehard-wired,butmus

t beindividuallyspecified.Toachievethis,thedeveloperentershisorherconnectiondatain the local.php file,whichheorshemaintainslocallyinthecomputeranddoesnotcheckintothe codeadministrationsystemeither.Whereastheconnectiondataforthe livesystem aredeposite d inglobal.php file,everydevelopercanworkwithhisorherownconnectiondata,whichare definedwiththeaidofthelocal.php.Inthismannerevenspecialconfigurationsfortestorst aging systemscanbedeposited.Incidentally,configurationfilesoftheform xyz.local.php (alsoap plies to global ),forexampledb.local.php,arealsoprocessedbyFrameworkasdescribedabove. TheindividualmodulesoftheapplicationarelocatedintheModule directory.Eachmodulecomes withitsowntypicaldirectorytree,whichwewilltakeacloserlookatlater.However,atthist imethe importantthingisthateverymodulecanalsohaveitsownconfiguration.Wenowhavethreeplac es inwhichsomethingcanbeconfigured:application.config.php,module-specificconfigurat ion andtheglobal.php andlocal.php files(ortheir specialisations asdescribedabove),whichthe systemreadsinexactlythisorderandultimatelyprovidealarge,commonconfigurationobjec t, becauseinthecourseofexecutionexactlytheseconfigurationsaremerged.Iftheconfigurat ions

Hello,ZendFramework2! ofapplication.config.php areonlyofinterestinthefirstfewmetersofbootstrapping,the configurationsofthemodulesandthosefromglobal.php andlocal.php arealsoimportantin thelatercourseoftheprocessingchainandaregenerouslymadeavailablebytheServiceManag er. Wewillalsolearnmoreaboutthislater.Theattentivereaderrealisesatthistimethatasares ultof this configurationcascade ,forexamplemoduleconfigurationsthatflowintotheapplicationf rom thirdpartymanufacturers modulescanbeextendedorevenreplaced.Thisisverypractical. Thevendor directorycontainsconceptionallythecodewhichonedidnotwriteoneself(ignoring the ZendSkeletonApplication codeatthistime,butwhichonecouldhavehadtowriteoneselfin caseofdoubt)orwhichonedidnotwriteespeciallyforthisapplication.ZendFramework2isth us locatedapproximatelythere,but,ifnecessary,alsoinotherlibraries.Whendealingwithad ditional libraries,onemustalwaysensurethatthecorrespondingclassescanbeaddressedbytheappli cation. However,ifonecaninstalltherespectivelibraryusingcomposer,thisworkdoesnothavetobe donebythedevelopereither.Theinstallationofadditionallibrariesshouldthereforeinth eideal casealwaysbeperformedusingcomposer.ThefactthatalsotheZF2modules,whichactually shouldbelocatedinthemodule directory,canalsobemadeavailableviathevendor directory isalsointeresting.(Tobeperfectlycorrect,onewouldhavetosaythatiscanbeconfiguredvi a application.config.php andthemodulescanthereforebasicallybedepositedanywhere.)This meansthatthirdpartymanufacturers librariesthatadheretotheZendFramework2module standardcanalsobeaddedinthismanner.Thus,onecanensurethatonlythosemodulesthat oneactuallydevelopedinthescopeoftherespectiveapplicationarelocatedinthemodule directory. Allothermodulescanalsobemadeavailableviavendor Allfilesthataretobemadeexternallyaccessibleviathewebserver(withtheexceptionofspe cific restrictionsinwebserverconfiguration)arelocatedinpublic.Thisisalsotheplaceforima gesCSS orJSfilesaswellasforthe centralentrypoint ,theindex.php.Theideabehindthisisthatevery HTTPrequestthatreachesthewebserverandaspecificapplicationinitiallyresultsincalli ngupthe index.php.Always.RegardlessofhowtheURLcallitselfisformulated.Theonlyexceptionsar e URLsthatrefertoanactuallyexistingfilewithinorbelowthepublic directory.Onlyinthiscase, doestheindex.php notperformtheexecution,insteadtheappropriatefileisreadandreturned.This

mechanismisachievedbyatypicalZendFramework.htaccess fileinthepublic directory: 1 RewriteEngine On 2 RewriteCond %{REQUEST_FILENAME} -s [OR] 3 RewriteCond %{REQUEST_FILENAME} -l [OR] 4 RewriteCond %{REQUEST_FILENAME} -d 5 RewriteRule ^.*$ -[NC,L] 6 RewriteRule ^.*$ index.php [NC,L] Inorderforthistofunction,severalconditionsmustbefulfilled.Ontheonehand,thewebser ver mustbeequippedwithaso-calledRewriteEngine7,whichmustalsobeactivated.Ontheother hand,thewebserverhastoallowanapplicationtosetdirectivesviaitsown.htaccess.Toachi eve this,the[followingdirective]mustbeexemplarilyintheApachehttpd.conf . 7http://httpd.apache.org/docs/current/mod/mod_rewrite.html

Hello,ZendFramework2! 1 AllowOverride All Thedata directoryisrelativelyunspecific.Basically,dataofallkinds,whichhaveanythingto dowiththeapplication(documentation,testdata,etc.)orthataregeneratedintherunningt ime (cachingdata,generatedfiles,etc.),canbedepositedthere. The index.php file Everyrequestthatdoesnotmapontoafilethatactuallyexistsinthepublic directoryisthus redirectedviathe.htaccess filetotheindex.php.Itthereforehasaspecialimportanceforwork withZendFramework2.Atthistime,itisagainimportanttorealizethattheindex.php itselfis notpartofFramework,butthatitisindispensibleforusingtheFramework sMVCcomponents. Pleaseremember:Zend\Mvc isthecomponentthatrepresentsthe processingframework foran application.Theindex.php comeswiththeZendSkeletonApplication;thus,wedonothaveto developitourselves. Becauseoftheimportanceoftheindex.php bothforFrameworkandforourunderstandingof Framework smechanics wewillnowriskadetailedlookatthisveryeasilyunderstoodfile: 1 <?php 2 chdir(dirname(__DIR__)); 3 require 'init_autoloader.php'; 4 Zend\Mvc\Application::init(include 'config/application.config.php')->run(); Listing4.1 Tobeginwith,wewillchangetotheapplicationrootdirectoryoftheapplication,inordertob e abletoeasilyrefertootherresources.Theninit_autoloader.php iscalledup;thisinitiallytriggers autoloadingbycomposer.Thisnondescriptcall-upensuresthatallthelibrariesthathavebe

en installedbycomposerautomaticallymaketheirclassesavailableviaautoloadingmechanism s: 1 <?php 2 // [..] 3 if (file_exists('vendor/autoload.php')) { 4 $loader = include 'vendor/autoload.php'; 5} 6 // [..] Listing4.2 Consequently,wecandispensewithallrequire() call-upsintheapplication.ThefewlinesthatI havewrittendownhereinsuchanemotionlessmanneractuallyrepresentanenormousattainmen t

Hello,ZendFramework2! forusasPHPdevelopers: Itsimplycouldnotbeeasiertointegratelibrariesintoone sown application. Intheinit_autoloader.php,theautoloadingoftheZF2classesviatheenvironmentvariableZ F2_PATH orviaGitsubmoduleisthenalsoalternativelyensured,justincasethatonedidnotobtain ZendFrameworkviacomposerbecauseinthatcasetheabove-mentionedautoloadingmechanism ofcomposerissufficient.WiththeaidoftheenvironmentvariableZF2_PATH,forexample,anu mber ofapplicationsinthesystemcanuseacentralinstallationoftheframeworkcode.Whetherorn ot thisistrulyexpedient,Icannotreallysay.Now,abriefchecktoseewhetherZendFramework2 cannowbeloaded otherwisenothingwillhappen andthenwecangetstarted: 1 <?php 2 // [..] 3 Zend\Mvc\Application::init( 4 include 'config/application.config.php') 5 ->run(); Listing4.3 Thecall-upoftheclassmethodinit() oftheApplication initiallyensuresthattheServiceManager issuperimposed.TheServiceManager isthecentralobjectinZendFramework2.Itallowsother objectstobeaccessedinmanyways,isnormallythe principalpointofcontact intheprocessing chainandisalsothefirstentrypointingeneral.WewillconsidertheServiceManager laterin greaterdetail.Forsimplicity ssake,onecaninitiallyimaginetheServiceManager asasortof globaldirectory,inwhichanobjectcanbedepositedunderadefinedkey. Forallthosewho havealreadyworkedwithFrameworkVersion1,theServiceManager thusinitiallypresentsitself asasortofZend_Registry.Atthispoint,weshouldperhapsmakeasmallleapforward.Not onlypreviouslygeneratedobjectinstancescomeintoconsiderationasvaluesthatcanbedepo sited intheServiceManager underastipulatedkey,butalso Factories ,whichgeneratetherespective objects inthecontextoftheServiceManager analogouslydesignatedas services .Theunderlying

ideaisthattheseservicescanonlythenbegeneratedwhentheyarereallyneeded.Thisprocedu reis termed lazyloading ,adesignpatternintendedtodelaymemoryandtime-consuminginstancing ofobjectsforaslongaspossible.Indeed,someanumberofservicesforsometypesofrequestsa re neverneeded;whyshouldthealwaysbeinstancedbeforehand? Butbacktothecode:Theinit() methodistransferredtotheapplicationconfigurationasparameter, andthishasalreadybeentakenintoconsiderationbythegenerationoftheServiceManager:

Hello,ZendFramework2! 25 1 2 3 4 5 6 7 8 <?php // [..] $serviceManager = new ServiceManager( new ServiceManagerConfig( $configuration['service_manager'] ) ); // [..] Listing4.4 Atthispoint,theServiceManager isnowinitialisedandequippedwiththoseserviceswhichare requiredinthescopeofprocessingofrequestsbyZend\Mvc.However,theServiceManager canalso beeffectivelyusedforcompletelydifferentpurposes,beyondZend\Mvc. Subsequently,theapplicationconfigurationitselfisdepositedintheServiceManager forlateruse. 1 2 3 4 <?php // [..] $serviceManager->setService('ApplicationConfig', // [..] $configuration); Listing4.5 ThentheServiceManager isaskedtoperformitsservicesforthefirsttime. 1 2 3 4 <?php // [..] $serviceManager->get('ModuleManager')->loadModules(); // [..] Listing4.6 Theget() methodrequestsaservice.Incidentally,inthissituationwealreadyhaveacaseinwhich

theServiceManager doesnotreturnaninstantiatedobject,butinsteadusesa factory togenerate therequestedservice,byacclamationasitwere.Inthiscase,theZend\Mvc\Service\ModuleM anagerFactory isused,andgeneratestherequestedModuleManager. ButhowdoestheServiceManager actuallyknownowthatwhenevertheModuleManager serviceis requestedthattheabove-mentionedfactoryistobecalleduponforitsgeneration?Letusagai nlook atthecodeaheadofit:

Hello,ZendFramework2! 1 <?php 2 // [..] 3 $serviceManager = new ServiceManager( 4 new ServiceManagerConfig($configuration['service_manager']) 5 ); 6 // [..] Listing4.7 AsaresultofthetransferofServiceManagerConfig,theServiceManager ispreparedfortheuseof Zend\Mvc andhasregisteredexactlythatfactoryfortheModuleManager,amongotherthings.Inthe followingchapters,wewilltakeanotherlookatallofthisingreaterdetailandalsolookatth eother serviceswhichareprovidedasstandard. Butletusnowreturntothecodesequence:AftertheModuleManager hasnowbeenmadeavailable viatheServiceManager,theloadModules() methodinitialisesallthemodulesactivatedbythe application.config.php,Ifthemodulesareready,theServiceManager isagaincontactedandthe application serviceisrequestedfromit. 1 <?php 2 // [..] 3 return $serviceManager->get('Application')->bootstrap(); 4 // [..] Listing4.8

Thisfactmayappearabitstrange,especiallysincetheentireprocessingsequenceindeedori ginally beganviaaZend\Mvc\Application.Butitnowbecomesclearthatitsinit() methodinitiallyonly initialisedtheServiceManager,whereastheApplication itselfisthenitselfgeneratedasaservice. Nowaverycomplexprocedure,whichisresponsiblefortheprocessingoftherequestitself,be gins. The application isprepared( bootstrapping occurs).Backintheindex.php,theapplicationis thenexecutedandtheresultisreturnedtothecallingprogram. 1 <?php 2 // [..] 3 Zend\Mvc\Application::init(include 4 ->run(); 5 // [..] Listing4.9 'config/application.config.php') Ihavedevotedachapterinthisbooktotheexactconsiderationoftherequestprocessingbecau seof itsimportance,butalsoofitscomplexity.Untilwegettoit,wewillkeepthisinmind:Theinde x.php

Hello,ZendFramework2! isthecentralentrypointforallrequeststhatareprocessedbytheapplication.Theseveryre quests aretechnicallyreroutedtotheindex.phpby.htaccess.TheactualURLthatwascalledupbythe userisnaturallymaintainedandissubsequentlyreadbyFrameworkinordertolocateanapprop riate controllerwithitsaction.TheServiceManager isatthefocusoftheprocessingandgivesaccessto theservicesoftheapplication.Therefore,wemustinitiallygeneratetheServiceManager,b eforeit can,inturn,giveusaccesstotheModuleManager,withwhosehelpwecanbringboththeregister ed modulesandtheApplication,whichisresponsibleforprocessingtherequests,tolife.Sofar ,so good. ZendFramework2withalternativewebserversNaturally,canalternativewebserverinsteado fApache suchasnginx. beused. Inthiscase,onlytheApache-specificconfigurationaswellasthatof.htaccessaretobeanal ogouslytransformed,forexamplewiththehelpofthe nginxrules . .http://nginx.org/

Preparing one s own module Theactualapplicationlogic,i.e.theindividualpages,templates,forms,etc.,areencapsu latedin modules.NowthatwehavetheexecutableZendSkeletonApplicationatourdisposal,itistimet o prepareourownfirstmodule.Becauseweinitiallyhavetoconcentrateontheindividualsteps that arerequiredtoprepareandencapsulateourownmodule,wewillstartwiththeclassicmodule. Hello,World! Preparing the Hello World module AZendFramework2moduleisfirstandforemostcharacterisedbyadefineddirectorystructure

andafewfilesthathavetobepartofeverymodule1 Module.php 2 config/ 3 module.config.php 4 public/ 5 images/ 6 css/ 7 js/ 8 src/ 9 Helloworld/ 10 Controller/ 11 IndexController.php 12 view/ 13 helloworld/ 14 index/ 15 index.phtml

orthosethatcanpresentifneeded. ThisstructuremustbecreatedinaHelloworld directoryinthemodule directorywithinthe application.Byconvention,amoduleisitsownnamespace,whichthankstoPHP5.3wecanalso designateassuchnatively.InFrameworkVersion1,thepseudo-namespacesstillhadtobeused ; thisresultedinverylongclassdesignations,forexampleinZend_Form_Decorator_Captcha_ Word Fortunately,withPHP5.3andZendFramework2,thisproblemisathingofthepast. Tobeginwith,wewillfilltheModule.php filewithlife. 28

Preparingone sownmodule 29 1 <?php 2 namespace Helloworld; 3 4 class Module 5 { 6 public function getConfig() 7 { 8 return include __DIR__ . '/config/module.config.php'; 9 } 10 } Listing5.1 TheModule classisassignedtothenamespacethatisstipulatedbyourmodule,inthiscase Helloworld.TheclassitselfisanormalPHPclass,whichcanhaveaseriesofmethods,which canbecalledupbydifferentFrameworkmanagersandcomponents,forexampleinthescopeofthe initialisation.ThegetConfig() methodisalsoamongthem.AsalreadyinZendFramework1,the conventionoverconfiguration approachisalsoextensivelyusedinthenewversion.Thismeans thatthereareconventions(agreements)that,ifusedasagreedupon,makefurtherconfigurat ion unnecessary. Inthiscasethefollowingconventionhasbeenstipulated: Ifyouimplementa getConfig() methodinyourmoduleclass,itwillbecalledupinthescopeoftheinitialisationofthe ModuleManager.Nosoonersaidthandone!However,ourmethoditselfdoesnotimmediatelyretu rn theconfiguration,buttoachievethisitinsteadreadsthemodule.config.php fileinthemodule s config directory,whichthenhasthefollowingcontents. 1 <?php 2 return array( 3

'view_manager' => array( 4 'template_path_stack' => array( 5 __DIR__ . '/../view' 6 ) 7 ) 8 ); Listing5.2 Tobeginwith,itbecomesapparentthattheconfigurationforamoduleismappedviaaPHParray. Therearefundamentallyalargenumberofoptionsastohowonecanmaintainconfigurations,fo r exampleasINIfile,viaYAMLorasXMLstructure.Allthesestructuresrequiremoreorlesscomp lex parsing.HoweverthemostefficientandinFrameworkthepreferredmethodistoimmediately deposittheconfigurationinPHPcode.Thismakesanyparsingunnecessaryandaslenderinclud e() alreadyensuresthedesiredeffectofreadingintheconfiguration.Buthereagain, convention over configuration alsoapplies.Ifwedefineaview_manager sectioninourconfigurations,thesevalues

Preparingone sownmodule 30 willalwaysbesubsequentlyconsideredwhensearchingforthecorrecttemplateasifbymagic. Thus,weconfigureherethedirectoryinwhichourmodule sViews(theHTMLtemplates)willbe deposited.Accordingly,atthistimethereisno conventionoverconfiguration ,butratherexpl icit information. Moreover,weshouldspecifyintheModule.php howtheautoloadingoftheindividualmoduleclasses ittofunction.Toachievethis,weimplementthegetAutoloaderConfig() method that will be processed during the initialisation of the ModuleManager onceagain,accordingto convention. 1 <?php 2 // [..] 3 public function getAutoloaderConfig() 4 { 5 return array( 6 'Zend\Loader\StandardAutoloader' => array( 7 'namespaces' => array( 8 __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__ 9 ) 10 )

11 ); 12 } 13 // [..] Listing5.2 Wewillreturntothe Autoloading topicagainlaterinmoredetailandnowwillhavetobe satisfiedwiththeknowledgethattheconstructdescribedaboveensuresthattheclassesofth is module especiallyalsothecontroller willbeautomaticallyloadedandcanthusalsobetakenint o considerationbyFramework.TheModule.php filenowlookslikethis: 1 <?php 2 namespace Helloworld; 3 4 class Module 5 { 6 public function getAutoloaderConfig() 7 { 8 return array( 9 'Zend\Loader\StandardAutoloader' => array( 10 'namespaces' => array( 11 __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__ 12 )

Preparingone sownmodule 31 13 ) 14 ); 15 } 16 17 public function getConfig() 18 { 19 return include __DIR__ . '/config/module.config.php'; 20 } 21 } Listing5.3 NowwewilldedicateourselvestotheIndexController inthe/src/Helloworld/Controller directory: 1 <?php 2 namespace Helloworld\Controller; 3 4 use Zend\Mvc\Controller\AbstractActionController; 5 use Zend\View\Model\ViewModel; 6 7 class IndexController extends AbstractActionController 8 { 9 public function indexAction() 10 { 11 return

new ViewModel(array('greeting' => 'hello, world!')); 12 } 13 } Listing5.4 Tobeginwith,wewillagainpayattentiontothenamespace.OurIndexController thusbelongs totheHelloworld moduleandisaController there.Sofar,sogood.Theclassinheritsfromthe FrameworkclassZend\Mvc\Controller\AbstractActionController everythingthatmakesitwhat isnowis:aclassthatcanprocessarequest( dispatching )andintheprocessusesitsactions.The term actions meanspublicmethodsthatagainconformtoacertainnameconvention:amethod oftheclassthenbecomesan action when action isappendedtothemethoddesignation.The controllernowhasanaction,theindexAction. ManythingscustomarilynowoccurinsidetheindexAction,suchastheprocessingofrequest parameters,writingorreadingofdatefromdatabasesoraccessingremotewebservices.Norma lly, theactiondoesnotdoeverythingitself(inthiscontextoneotherwisealsospeaksoftheso-ca lled fat controller ),butratherdelegatestheindividualtaskstootherfellowcampaigners.Thisappr oach normallyincreasesthereusabilityandserviceabilityofthecode.

Preparingone sownmodule 32 Asarule,anactionendsitsworkbymakingtheresultsoftheoperationsperformedavailable forpresentationinthecallingprogram sbrowser.InZendFramework2alltherequireddataare returned(that ssomethingthatisnormallyonlysaidintennis)intheformofaso-called view models .Toputitsimply,a viewmodel representsthedataunderlyinga userinterface(UI) and additionallyalsocontrolsthestatusofcertainUIcomponents.Wewillsointothisinmoredet ail againlater. Whenwenowdesiretodisplaythesalutation hello,world! asheadinginabrowser,the appropriateh1 tagisstillmissing. Sincetheso-called view isresponsiblefortheHTML presentationinaMVC-basedapplication,wenowhavetocreatethis(strictlyspeakingweare reallyonlygeneratea viewtemplate thatproducesthedesiredresultinthescopeof rendering a view andonthebasisofa viewmodel .Foreveryactionthereisnormallyexactlyoneviewora viewtemplate,respectively.Toachievethis,weenterthefollowingintheindex.phtml fileinthe view/helloworld/index directory: 1 <h1><?php echo $this->greeting; ?></h1> Listing5.5 Alsointhestructuringoftheview,themodule snamespace,butalsothedesignationofthecontr oller andthatoftheaction,playsarole,ascanbeclearlyseen.Ourviewislocatedinaview directory. Thatisfixedanddoesnotchange.Theninasubdirectorythatisnamedafterthemodule,andther e againinasubdirectorythatisnamedafterthecontroller.Theviewitselfbearsthenameofthe respectiveactionwiththesuffix .phtml .Afilewiththe .phtml endingcomprisesbyconvention PHPcodeandHTMLmarkup,wherebythePHPcodeisrestrictedtothepresentationandshould not,forexample,containbusinesslogic.Incidentally,the phtml suffixstandsforPHP+HTML.I n ourviewfile,wethushaveHTMLmarkupandthenaccesstheview sdatamodelvia$this,which wegeneratedinthecontrollerbeforehand.Weaccessthekeygreeting directlywith greeting ,for whichwefiledthevalue hello,world! intheviewmodel.Wethenoutputitbymeansofecho. Butwearestillnotfinished.Wewanttosee hello,world! onthescreenwhenwecall-uptheURL http://localhost/sayhello.Toachievethis,wehavetoextendtheconfigurationofthemodul e inthemodule.config.php

byacorrespondingrouteandthedetailsofourcontroller. 1 <?php 2 return array( 3 'view_manager' => array( 4 'template_path_stack' => array( 5 __DIR__ . '/../view', 6 ), 7 ), 8 'router' => array( 9 'routes' => array( 10 'sayhello' => array(

Preparingone sownmodule 33 11 'type' => 'Zend\Mvc\Router\Http\Literal', 12 'options' => array( 13 'route' => '/sayhello', 14 'defaults' => array( 15 'controller' => 'Helloworld\Controller\Index', 16 'action' => 'index', 17 ) 18 ) 19 ) 20 ) 21 ), 22 'controllers' => array( 23 'invokables' => array( 24 'Helloworld\Controller\Index' 25 => 'Helloworld\Controller\IndexController' 26 ) 27 ) 28 ); Listing5.6 Analogoustotheview_manager

key,therouter keyensures byconvention thattheconfiguration oftheroutingofthecorrespondingFrameworkcomponentsismadeaccessible. Sincewewill considerroutinglaterinmoredetail,onlythismuchwillbesaidatthispoint:Atthistime,we transferanarraywithindividualroutes,oneofwhichwehavenamed sayhello .Itshouldalways takeeffectwhenthe /sayhello stringfollowsthehostinformation(inourcaselocalhost)inthe URL.Ifthisisthecase,FrameworkshouldensurethattheIndexController,whichwehavejust prepared,andinittheactionindex isexecuted.Andthatisactuallyeverything.Duetothenested arraynotation,theconfigurationinitiallyappearstobeabitunclear.Butafterashorttime ,onehas quicklybecomeaccustomedtoit. ItisinterestingtonotethatwespecifiedHelloworld\Controller\Index asthevalueforthe controlleralthoughthecontrolisindeednamedIndexController.Theexplanationistobefou nd somewhatfurtheron.

Preparingone sownmodule 34 1 2 3 4 5 6 7 8 9 <?php // [..] 'controllers' => array( 'invokables' => array( 'Helloworld\Controller\Index' => 'Helloworld\Controller\IndexController' ) ) // [..] Listing5.7 Withthissmallpieceofcode,wedefineadistinctnameforourcontrollerthatisvalidacrossa ll oftheapplication smodules.Toensurethatitisunambiguous,weplacedthedesignationofthe controllerinfrontofthemodule sname.Helloworld\Controller\Index isthusnowthesymbolic nameforourcontroller,whichiscorrespondinglyusedintherouteconfiguration. Lastbutnotleast,wenowhavetoextendtheapplication.config.php filesuchthatournew modulewillbeconsideredatall.Toachievethis,weamendthenameofourmoduleinthemodules section. 1 2 3 4 5 6 7 <?php // [..] 'modules' => array( 'Application', 'Helloworld' ) // [..] Listing5.8

TheURLhttp://localhost/sayhello shouldnowprovidethedesiredresultandoutput hello, world! tothescreen. Autoloading Asageneralrule,classesmustinitiallybemadeavailablebymeansofarequire() call(orsomething similar)beforetheycanbeusedthefirsttime,foritisindeedsothatduringtheexecutionofa script onlythecodethatwaspreviouslymadeavailabletothePHPinterpreterisaccessiblethere.Th us, itisnotenoughtoprogramaPHPclass,todeposittheformersomewhereinthefilesystemand thelatteratanotherlocation,torefertothescriptthatisbeingexecutedwithouthavingmad ethe classknownbeforehand.Andnowabriefdigression:Onemustabsolutelydifferentiatebetwee n codethatispartofthePHPcoreandtheso-called userland code.Whereas,forexample,thecore

Preparingone sownmodule 35 classallows\DateTime tobeusedwithoutpreviousregistration,thisdoesnotapplyforclassesthat youhavewrittenyourself,i.e.userlandcode.SuchcodemustalwaysbemadeknowntothePHP interpreterinitially,andtherespectivePHPfilesinwhichtheclassdefinitionsarelocate dmusthave beenloaded. ZendFramework2makesintensiveuseofautoloading. Autoloadingsimplymeansthatthe registrationofclassesisperformedautomatically;therespectivePHPfileswiththeclasse sthat aredefinedtherearethusautomaticallyloadedwhenneeded.Inorderforthistofunction,ace rtain configurationmustbeperformed aswehavealreadyseeninthegetAutoloaderConfig() method intheModule.php file. Indeed,weavoidagreatdealoftypingoneverypagewithautoloadingbecausewewouldotherwis e havetoloadeachfilewithanexplicitrequire();but,ontheotherhand,weburdenthesystem additionallywiththeautoloadingfunction.Thus,autoloadinghascertaincoststhatcanmak e themselvesfeltintheexecutiontimeofaFramework2application.Thegoodthingisthatare differentways,amongthemhigh-performanceones,inwhichautoloadingcanbeimplemented, whichareallsupportedbyFramework. Frameworkhastwoessentialclassesthatareusedfor autoloading. Standard autoloader Thestandardautoloaderistheimplementationthathasmeanwhilebecomethecustomarymanner ofrealisingautoloading. Inthiscontext,theclassnameistranslatedone-to-oneintoafile name. Consequently,thecorrespondingloaderexpects,forexample,thattheZend_Translate class(fromZendFramework1)isdefinedinaTranslate.php fileintheZend directory. This alsoappliestoclassesthatmakeuseof realnamespaces :AclassTranslate thatisdefinedin theZend namespaceisexpectedtobeatthesamelocationinthefilesystem. Thisconvention correspondstoboththePEAR8-StandardandthePSR-0.ofthePHPFrameworkInteroperability Group](https://github.com/php-fig/fig-standards).Theimportantthingisthanonethink sofstating wherethecorrespondingdirectoryfortherespective(pseudo-)namespaceislocatedinthefi

le system,aswedidintheModule.php file. 1 <?php 2 namespace Helloworld; 3 4 class Module 5{ 6 public function getAutoloaderConfig() 7{ 8 return array( 9 'Zend\Loader\StandardAutoloader' => array( 8http://pear.php.net/ .https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md

Preparingone sownmodule 36 10 'namespaces' => array( 11 __NAMESPACE__ 12 => __DIR__ . '/src/' . __NAMESPACE__, 13 ), 14 ), 15 ); 16 } 17 18 // [..] 19 } Listing5.9 Theinclude_path isnamelynolongerconsulted,whichshouldacceleratetheloadingofclassesto thegreatestpossibleextent.Onedoesnoreallyhavetoknowanymoreatthistime.Ifoneconfor ms totheseconventions,theclasseswillbeautomaticallyloadedwithoutanyproblems. ClassMapAutoloader However,thehighest-performanceimplementationofautoloadingistheClassMapAutoloader ;it operatesonthebasisofasimple,associativePHParray,whichcontainsthefully-qualifiedc lass namesaskeyineachcaseandtheappropriatefilenamesasvalue.Itlooksapproximatelyliketh is: 1 <?php 2 return array( 3 'PhlyContact\Service\ContactControllerFactory' 4 => __DIR__ . '/src/PhlyContact/' . 5 'Service/ContactControllerFactory.php',

6 ); Listing5.10 Ifthecorrespondingclassisrequested,theloaderlooksinthearrayfortheappropriatevalu eand loadsthefile.That sit.Inthiscasethedisadvantageisobvious:Theclassmaphastobecontinu ously maintained.Ifacertainclassisnotlocatedthere,theautoloadingfails.Fortunately,ther eis,onthe onehand,thepossibilityoflettingaClassMapbegeneratedautomatically(e.g.inthescopeo fa buildprocess)toensurethatonedidnotforgetanyclass,and,ontheotherhand,severalmetho ds ofautoloadingcanbecombined.

Preparingone sownmodule 37 1 <?php 2 public function getAutoloaderConfig() 3 { 4 return array( 5 'Zend\Loader\ClassMapAutoloader' => array( 6 __DIR__ . '/autoload_classmap.php' 7 ), 8 'Zend\Loader\StandardAutoloader' => array( 9 'namespaces' => array( 10 __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, 11 ), 12 ), 13 ); 14 } Listing5.11 Inthismanner,theClassMapwillbeinitiallyconsultedandifnecessarythePSR-0mechanismw ill beresortedtoifnohitshaveoccurreduptothatpoint. TheClassMapitselfislocatedinafileofitsownoutsidetheModule.php andisonlyreferencedfrom there.Theunderlyingideaistogeneralizetheprocedurefortheimplementationofautoloadi ngto theextentthat evenoutsideofZendFramework librariesfromdifferentsourcescanbeintegrate d

withoutproblems.Inthelastseveralyears,somecodelibrarieshavealreadyrealizedthatit isareal addedvaluefortheuserifthelibraryprovidessomeformofsupportforautomaticallyloading thelibrary sclasses.Theproblemwiththisisthatineachcaseitisagainamatterofisolated solutions.Intheconcreterealisationofautoloading,eachlibrarygoesitsownwayincaseof doubt; andevenifthewaysaresimilar,theyindeeddifferindetail.Inthescopeofthemodulesofa ZF2application,Frameworkorientsitselfonanadditionalstandardfromwhich,forexample, the previouslymentionedcomposerprofitsandinthismannertoallow translibrary autoloadingint he simplestwaypossible.Forthispurpose,everymoduleisprovidedwiththreeadditionalfiles relevant toautoloading:autoload_classmap.php,autoload_function.php undautoload_register.php. Theautoload_classmap.php returnsaPHParrayasmappingofclassnamesandfilenamesas describedabove,whichanyarbitraryautoloader notonlythatofZendFramework,butalso,for example,thatofcomposer canprocessandifnecessaryalsoevencombinethemwithClassMaps ofotherlibraries.Incontrast,autoload_function.php returnsaPHPfunction:

Preparingone sownmodule 38 1 2 3 4 5 6 7 8 9 10 11 <?php return function ($class) { static $classmap = null; if ($classmap === null) { $classmap = include __DIR__ . '/autoload_classmap.php'; } if (!isset($classmap[$class])) { return false; } return include_once $classmap[$class]; }; Listing5.12 Thereturnedfunctioncanalsobeprocessedbyanautoloader,forexample,intheformthatitca n beconsideredasanadditionalsourcefortheautoloadingofclasses.Ultimately,thisfuncti onalso againaccessestheClassMap.Andautoload_register.php isevenmoreslender: 1 2 <?php spl_autoload_register(include __DIR__ .

'/autoload_function.php'); Listing5.13 Inthiscase,thepreviouslydefinedautoloadingfunction,whichagainaccessestheClassMap proper, isdirectlyregisteredforautoloadingandnotreturnedtothecallingprogramagain.Asimple 1 2 <?php require_once 'autoload_register.php'; Listing5.14 theninsuresthattheautoloadingforexactlythesecomponentsfunctions.Thisis,however,w ithout theoptionofperformingfurtheroptimisations,forexampleintheprocessingsequenceofall registeredautoloadingfunctions. Thethreeautoloadingfilesare,aswehavealreadyseeninthepreviouschapter,notrequiredf ora moduletofunctionbecauseallofthesefilesrefertotheautoloadingClassMap,whichitselfi snot absolutelynecessary,butcannoticeablyimprovetheperformanceofanapplication. Inconclusion,acompletemoduledirectorylayoutforthe Helloworld modulecompletewiththe autoloadingfilesisthendepictedasfollows:

Preparingone sownmodule 39 Module.php autoload_classmap.php autoload_function.php autoload_register.php config/ module.config.php public/ images/ css/ js/ src/ Helloworld/ Controller/ IndexController.php views/ Helloworld/ Index/ index.phtml

One time Request and back again LetuslookatexactlywhathappenstoarequestandhowtheFramework sdifferentcollaborators generatetheanswerwithour hello,world! onthebrowser. Everythingbeginswiththecall-upofthehttp://localhost/sayhello URL.TheHTTPrequest reachesthewebserver,whichafterconsulting.htaccess decidesthattheindex.php hastobe processedbythePHPinterpreter. Inthescopeoftheindex.php,theautoloadingisinitially configuredandsubsequentlytheapplication sinit() methodiscalledup. 1 <?php 2 Zend\Mvc\Application::init( 3 include 'config/application.config.php') 4 ->run(); Listing6.1 ServiceManager TheServiceManager instantiatedthere: 1 <?php 2 // [..] 3 $serviceManager = new ServiceManager( 4 new ServiceManagerConfig($configuration['service_manager']) 5 );

6 // [..] Listing6.2 AsaresultofthetransferofServiceManagerConfig,theServiceManager isequippedwith severalstandardserviceswhichZend\Mvc requiresforsmoothfunctioning. Inadditionthe correspondingconfigurationistransferredtotheServiceManagerConfig fromthepreviouslyloaded application.config.php. TheServiceManagerisasortofZend_Registry withextendedfunctions. WhereastheZend_Registry ofVersion1couldmerelyfileexistingobjectsunderacertainkeyandsubsequentlyload themagain(keyvaluestorage),theServiceManager goesseveralstepsfurther. 40

OnetimeRequestandbackagain Services and service generation Inadditiontotheadministrationofservicesintheformofobjects,a generator canalsobe registeredforakey,whichgeneratestherespectiveserviceinitiallywhenneeded.Toachiev ethis, eitherclasses(fullyqualified,i.e.,ifnecessary,withdeclarationofthenamespace,whic harethen onrequestinstantiated,forexampleintheform 1 <?php 2 $serviceManager->setInvokableClass( 3 'MyService', 4 'Helloworld\Service\MyService' 5 ); Listing6.3 orfactoriescanbedeposited. 1 <?php 2 $serviceManager->setFactory( 3 'MyServiceFactory', 4 'Helloworld\Service\MyServiceFactory' 5 ); Listing6.4 InorderfortheServiceManager tobeabletomanagetheMyServiceFactory,thelatterhasto implementtheFactoryInterface,whichrequiresacreateService method.Thismethodisthen calledupbytheServiceManager.Aslight-weightimplementationofafactor,aCallbackfunct ion canbedirectlytransferred. 1 <?php 2 $serviceManager->setFactory( 3 'MyServiceFactory', 4 function($serviceManager) { 5 // [..] 6 } 7 );

Listing6.5 Alternatively,cananabstractFactoryalsobeanalogouslyfiled,wherebythisisaddedwitho ut Identifier;thisisshownhereexemplarilyinaCallbackvariant.

OnetimeRequestandbackagain 1 <?php 2 $serviceManager->addAbstractFactory( 3 function($serviceManager) { 4 // [..] 5} 6 ); Listing6.6 Inaddition,so-called Initializers canbefiled;theyensurethataserviceisequippedwithval ues orreferencestootherobjectswhenitiscalledup. Initializers canbesoconceivedthattheinje ct objectsintoaservicewhentherespectiveserviceimplementsadefinedinterface.Wewillsee this inactionagainlater.Atthemoment,weshouldonlyrememberthattheyexist.Allservicesmade availablebytheServiceManager areso-called sharedservices ,thismeansthattheinstanceofa service generatedwhenneededoralreadypresent canalsobereturnedatasecondrequestofjust thisserviceinstance.Theinstanceoftheserviceisthusreused.Thisisvalidforallstandar dservices definedbyFramework;theonlyexceptioninthiscontextistheEventManager.Ifanewservicei s registered,onecanalsopreventthereuseofinstances. 1 <?php 2 $serviceManager->setInvokableClass( 3 'myService', 4 'Helloworld\Service\MyService', 5 false 6 ); Listing6.7 Standard services, Part

1 Attheverybeginningofrequestprocessing,theServiceManagerConfig ensuresthatanumber ofstandardservicesaremadeavailable.Itisimportanttorealizethatthereareservicesint he ServiceManager,whichcaninpartonlybeusedbyFramework(ormoreexactlybyitsMVC implementation),whereassomeotherservicesarealsousefulfortheapplicationdeveloper, for exampleinthecontextofaController.Thiswillbecomeclearersomewhatfurtheralong. Invocables Thefollowingserviceismadeavailableasan invocable ,i.e.byspecifyingaclass,whichisthen instantiatedwhennecessary.

OnetimeRequestandbackagain SharedEventManager (Zend\EventManager\SharedEventManager):Allowstheregistrationof listenersforcertainevents,alsowhentheeventmanagerrequiredforthisisnotyetavailabl e. TheSharedEventManagerisautomaticallymadeavailablebyanewEventManager whenthis isgeneratedbytheServiceManager.FurtherexplanationsoftheSharedEventManager willbe foundlaterinthebook. Factories InthecontextoftheServiceManager,Factoriesaretheretomakeservicesavailable,whichdo not existuntiltherealrequestoccurs,butratherarebuiltbyaFactory ondemand .Thefollowing servicesaremadeavailableindirectlybyafactoryasstandard. SharedEventManager (Zend\EventManager\SharedEventManager): TheEventManager can generateeventsandinformregisteredlistenersaboutthem.Itcanalsoberequestedviathe Zend\EventManager\EventManagerInterfacealias. ModuleManager (Zend\Mvc\Service\ModuleManagerFactory):Administersthemodulesofa ZF2application. Configuration TheServiceManageristhusdecisivelycontrolledforuseinthescopeofrequestprocessingby twoconfigurations:TheServiceManagerConfig,whichdefinesanumberofstandardservicesf or requestprocessing,andalsobytheapplication.config.php ormodule-specificconfigurations, respectively.Ineachcase,theservice_manager keyisessentialforthis: 1 <?php 2 return array( 3 // [..] 4 'service_manager' => array( 5 // [..] 6

), 7 ), 8 // [..] 9 ); Listing6.8 Belowtheservice_manager keys,thefollowingkeysarethenpossible: services:DefinitionofServiceswiththeaidofalreadyinstantiatedobjects. invocables:Definitionofservicesbydeclarationofaclass,whichisinstantiatedwhen needed.

OnetimeRequestandbackagain factories:Definitionoffactories,whichinstantiateserves. abstract_factories:Definitionofabstractfactories. aliases:Definitionofaliases. shared:Allowstheexplicitdeclarationofwhetheracertainservicecanbeusedanumberof timesorshouldbere-instantiatedifagainrequired. AssoonastheServiceManager isavailable,theapplication.config.php is,thenasawhole,i.e. alsowiththeothernon-ServiceManagerrelevantsections,itselfmadeavailableasservice.

1 <?php 2 // [..] 3 $serviceManager->setService('ApplicationConfig', $configuration); Listing6.9 Thisisimportantbecauseothercomponents,suchastheModuleManager orViewManager,alsoaccess theseservices. Atthepresenttime,theServiceManager (equippedwithdiversestandardservices)isthusin readinessand,inamannerofspeaking,isonlywaitingfortheshowtobegin.For,uptonow notmuchhashappenedexceptforafewbasicpreparations.Infact,atthispointnearlyallofth e above-mentionedservicesdonotyetexistbecausetheyhavenotyetbeenrequestedandareonly generatedwhennecessary. Writing a service of one s own Letusdrawupaserviceofourownforour Helloworld moduleinanexemplarymanner.To achievethis,weinitiallyaddanotherService subdirectoryinthesrc/Helloworld directoryofour module. 1

Module.php 2 config/ 3 module.config.php 4 public/ 5 images/ 6 css/ 7 js/ 8 src/ 9 Helloworld/ 10 Controller/ 11 IndexController.php

OnetimeRequestandbackagain 12 Service/ 13 GreetingService.php 14 view/ 15 Helloworld/ 16 Index/ 17 index.phtml TherewecreateaGreetingService class.Thisclassmustnotimplementanyspecialinterfacesor bederivedfromanybasicclasses;itisthusaso-called POPO ,a PlainOldPHPObject .Theonly importantthingisthatwedonotforgettomaketheclassavailableintherightnamespace. 1 <?php 2 namespace Helloworld\Service; 3 4 class GreetingService 5 { 6 public function getGreeting() 7 { 8 if(date("H") <= 11) 9 return "Good morning, world!"; 10 else if (date("H") > 11 && date("H") < 17) 11 return "Hello, world!";

12 else 13 return "Good evening, world!"; 14 } 15 } Listing6.10 Make the service available as an invocable Tousethisclassasaserviceinourcontrollerandtobeabletodisplayatime-orientedgreetin g,we mustaddtheclasstotheServiceManager asService.Wecandothisisthescopeofourmodulein twoways:Inthecourseofmoduleconfiguration(module.config.php)byaddingthesection

OnetimeRequestandbackagain 1 <?php 2 // [..] 3 'service_manager' => array( 4 'invokables' => array( 5 'greetingService' => 'Helloworld\Service\GreetingService' 6 ) 7 ) 8 // [..] Listing6.11 orprogrammaticallybyaddingthegetServiceConfig() functionintheModule.php: 1 <?php 2 public function getServiceConfig() 3 { 4 return array( 5 'invokables' => array( 6 'greetingService' 7 => 'Helloworld\Service\GreetingService' 8 ) 9 ); 10 } Listing6.12 Bothwaysleadtotheobjective.Ourserviceisnowavailableintheformofan invocable .Wecan requesttheserviceintheIndexController

ofour HelloWorld moduleanduseit: 1 <?php 2 // [..] 3 public function indexAction() 4 { 5 $greetingSrv = $this->getServiceLocator() 6 ->get('greetingService'); 7 8 return new ViewModel( 9 array('greeting' => $greetingSrv->getGreeting()) 10 ); 11 } Listing6.13

OnetimeRequestandbackagain Making the controller available via a factory class However,wedohaveoneproblemnow: Thecontrollerisdependentonaservice(andthe ServiceManager),whichitactivelyaccesses.Admittedly,itdoesnotinstantiatetheclassi tself, whichisgood;thus,itdoesprovideuswithapossibilityofmakinganalternativeimplementat ion availableintheServiceManager,ifnecessary,butitactivelyensuresthatalldependencies have beenresolved.Atthelatest,thatwillcreateproblemsforuswhenwedesiretoperformunittes ting. Apossiblealternativeinthiscasewouldbethepreviouslymentioned dependencyinjection or inversionofcontrol .Inthiscontext,therequiredcollaboratorsareautomaticallymadeavail able andmustnolongerbeactivelyrequested.IntheframeworkoftheServiceManager,wecanrealis e thisprocedure,forexample,viaaprecedingfactory. ToachievethuswepreparetheIndexControllerFactory factoryinthesamedirectoryinwhich theIndexController hasbeendeposited: 1 <?php 2 namespace Helloworld\Controller; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class IndexControllerFactory implements FactoryInterface 8 { 9 public

function createService(ServiceLocatorInterface $serviceLocator) 10 { 11 $ctr = new IndexController(); 12 13 $ctr->setGreetingService( 14 $serviceLocator->getServiceLocator() 15 ->get('greetingService') 16 ); 17 18 return $ctr; 19 } 20 } Listing6.14 Inaddition,wealterthemodule.config.php inthecontrollers sectionasfollows:

OnetimeRequestandbackagain 1 <?php 2 // [..] 3 'controllers' => array( 4 'factories' => array( 5 'Helloworld\Controller\Index' 6 => 'Helloworld\Controller\IndexControllerFactory' 7 ) 8 ) 9 // [..] Listing6.15 Fromnowon,ourIndexController isthusnolongergeneratedbyinstantiationofadefinedclass, butbythedepositedfactory,whichwaspreviouslyorganisedbythecontroller scollaborators and inthiscaseplacedatthedisposalofthecontrollerby SetterInjection .Westillhavetoaddthe corresponding Setter totheIndexController andinthecourseoftheactionitselftoaccessthe correspondingmembervariable,insteadofaccessingtheServiceLocator. 1 <?php 2 3 namespace Helloworld\Controller; 4 5 use Zend\Mvc\Controller\AbstractActionController; 6 use Zend\View\Model\ViewModel; 7 8 class IndexController extends AbstractActionController 9 { 10

private $greetingService; 11 12 public function indexAction() 13 { 14 return new ViewModel( 15 array( 16 'greeting' => $this->greetingService->getGreeting() 17 ) 18 ); 19 } 20 21 public function setGreetingService($service) 22 { 23 $this->greetingService = $service; 24 } 25 }

OnetimeRequestandbackagain Listing6.16 Thus,afactoryclasscanbeusedforboththegenerationofaserviceandforthegenerationof acontroller. Indeed,itisasfollows:TheZend\ServiceManager isemployedinZF2inseveral ways. Onceintheformwhichwehavealreadydiscussed:ascentralinstanceviawhicheven theApplication itselfisgenerated,i.e.the container fortheentireapplication,ifyouwishto callitthat.Andthenthereis,aswewillsoondiscussinmoredetail,anumberofspecialised ServiceManagers ,forexampleoneonlyfortheapplication scontroller. Inthiscontext,the followinglinesoftheIndexControllerFactory areofparticularinterest: 1 <?php 2 public function createService(ServiceLocatorInterface $serviceLocator) 3{ 4 // [..] 5 $ctr->setGreetingService( 6 $serviceLocator->getServiceLocator() 7 ->get('greetingService') 8 ); 9 // [..] 10 } 11 // [..] Listing6.17 ItisapparentthatinitiallygetServiceLocator() isinvokedinthe$serviceLocator.Thereason forthisisthefactthatthefactory screateService() methodisalwaystransferredtothe

ServiceManager ,whichhasbeenchargedwiththegenerationoftheservice,thus,inthiscase,th e ControllerLoader(isautomaticallycalledupbyFramework)whichwasreservedforthegenera tion ofcontrollers.However,thisinturndoesnothaveanyaccesstotheGreetingService,whichwe preparedbeforehandandwhichweonlymadeavailableinthe centralServiceManager (itisindeed ultimatelynotacontroller).Inorderthattheservicesofthe centralServiceManager cannow bemadeavailabledespitethis,theControllerLoader accessesthecentralServiceManagerviathe getServiceLocator(),andmakestheformeravailabletoitbyjustthesemethods.Somewhatlat er inthischapteryouwilllearnmoreaboutthedetailsofthismechanism. Making the controller available via a factory callback However,withtheIndexControllerFactory wenowhaveanadditionalclassinoursourcecode. Forthetimebeing,thisisbasicallynotaproblem,butitcouldbeabittoomuchofagoodthingin acaselikethis,inwhichitisnotmuchofachallengingtasktogeneratethefactory.Alight-we ight alternative,whichalsomakesitpossibleforustoavoiddirectdependence,istheuseofacall back functionasafactoryinthemodule.config.php:

OnetimeRequestandbackagain 50 1 <?php 2 // [..] 3 'controllers' => array( 4 'factories' => array( 5 'Helloworld\Controller\Index' => function($serviceLocator) { 6 $ctr = new Helloworld\Controller\IndexController(); 7 8 $ctr->setGreetingService( 9 $serviceLocator->getServiceLocator() 10 ->get('greetingService') 11 ); 12 13 return $ctr; 14 } 15 ) 16 ) 17 // [..] Listing6.18 ThecodeforthegenerationoftheIndexController,whichwaspreviouslylocatedintheIndexC ontrollerFactory, hasnowbeenmoveddirectlyintothemodule.config.php. Make the service available via Zend\Di RegardlessofwhichformofFactoryisused,inallofthemthegenerationoftherespectiveobje ct

occursprogrammatically,thismeansthatappropriatePHPcodemustbewritten.Zend\Di provides analternativeoptionwithwhosehelpwecangenerateentireobjectgraphsviaconfigurationf iles. Wewillgointomoredetaillater. ModuleManager Butnowletusreturntorequestprocessing:AftertheServiceManager hasbeenadequatelyprepared, ithasalsobeenusedforthefirsttime,togeneratetheModuleManager viatheregisteredfactorybefore loadingthemoduleisinitiated. 1 <?php 2 $serviceManager->get('ModuleManager')->loadModules(); Listing6.19

OnetimeRequestandbackagain 51 Generation of the ModuleManager TheModuleManagerFactory servesthepurposeofprovidingtheModuleManager service. Aswe remember,afactoryisalwaysusedwhenthegenerationofanobjectbecomesmorecomplex.In thescopeofthisgeneration,initiallyanewEventManager isrequestedfromtheServiceManager andisplacedatthedisposaloftheModuleManager.TheModuleManager isthereforeabletogenerate eventsandtoinformregistered Listeners beforehand.Thefollowingeventsaretriggeredbythe ModuleManager (atalaterpointintime!): loadModules:Isinitiatedwhenthemodulesareloaded. loadModule.resolve:Isinitiatedforeachmodulethatistobeloadedwhenthenecessary dataarereadin. loadModule:Isinitiatedduringloadingofamoduleforeverymodulewhenthedatathathave beenreadinareexported. loadModules.post:Isinitiatedafterallmoduleshavebeenloaded. Module-oriented listeners However,theModuleManagerFactory doesmuchmore.Tobeginwith,itgeneratesalargenumber oflistenersthatareregisteredfortheabove-mentionedevents. ModuleAutoloader:EnsuresthattheModule classoftheindividualmodulescanbeautomaticallyloaded. ModuleResolverListener:InstantiatestheModule.php oftherespectivemodule. AutoloaderListener:InvokesthegetAutoloaderConfig() methodinModules,inorderto obtaininformationonhowthemodules classescanbeautomaticallyloaded. OnBootstrapListener:CheckstodeterminewhetherModules haveanonBootstrap() method andregisterstheinvocationofthismethodforthebootstrap eventthatwillbetriggeredat alaterpointintimebytheApplication. InitTrigger:CheckstodeterminewhetherModules havetheinit() methodattheirdisposal. Iftheydo,itisinvoked.

ConfigListener:CheckstodeterminewhetherModules haveagetConfig() methodattheir disposal,which,ifpresent,isinvokedandthereturnedmoduleconfigurationsarrayisunite d withtheotherconfigurations. LocatorRegistrationListener:InsuresthatinstancesofallModule classesthatimplement theServiceLocatorRegisteredInterface are injected into the ServiceManager . ServiceListener:CallsthegetServiceConfig(),getControllerConfig(),getControllerPlu ginConfig(), getViewHelperConfig() methodsintheModule class,ifpresent(orreadsoutdiethe correspondingconfigurations;furtherdetailsonthisinthefollowing),processesthemerg ed configurationsofallmodules,appliesthemtothemServiceManager andaddsfurther Standard-Servicestothelatter.

OnetimeRequestandbackagain Standard services, Part 2 Afteranumberofstandardserviceshavebeenmadeavailableinthecourseofthegenerationoft he ServiceManager amongthem,inadditiontotheEventManager,eventheModuleManager itself the ServiceListener additionallyregisters(attherequestofitsfactory)acolourfulassortmentof additionalServices,whicharerequiredinthecourseoftherequestprocessing.Atthispoint abrief commentisappropriate:atthispointintimetheoperationalmodeofeachservicecanandshoul d notbecompletelyunderstood!ManyoftheservicesthatareregisteredbytheModuleManager atthis timewillcrossourpathagaininthecourseofthischapterorbook.Thefollowinglistthusshou ld servemuchmoreasareferenceandoutlooktothatwhichisstilltocome.Thefollowingservices arethus listedhereaccordingtomannerofregistration registered. Invocables RouteListener (Zend\Mvc\RouteListener):ListenslatertotheMvcresultonRoute andthen ensuresthattherouterischargedwiththeresolutionontheappropriatecontroller. DispatchListener (Zend\Mvc\DispatchListener):ListenslatertotheMvcresultonRoute andthenensuresthattheControllerLoader loadsthepreviouslyselectedcontrollerandruns it. Factories Application (Zend\Mvc\Service\ApplicationFactory):TheApplication (generatedbythe depositedfactory)represents,sotospeak,theentireprocessingchainandingeneraltheent ire application. Configuration (Zend\Mvc\Service\ConfigFactory):ThegeneratedConfig servicereturns themergedconfigurationfortheapplication.ItisalsoavailableviatheConfig alias. ConsoleAdapter (Zend\Mvc\Service\ConsoleAdapterFactory): Serviceforaccessingthe commandline. DependencyInjector

(Zend\Mvc\Service\DiFactory):ZendFrameworkhasitsownimplementationofthesocalled dependencyinjection ,withwhosehelpcomplexobject graphsbasedonacomprehensiveconfigurationareautomatically merged .Insteadofthe DependencyInjector keys,onecanalsouseitsaliasesDi orZend\Di\LocatorInterface.We willlookatZend\Di inmoredetaillater. Router,HttpRouter,ConsoleRouter (Zend\Mvc\Service\RouterFactory): Basedonthe requestURL,thefactory-generatedRouter servicedeterminesthecontrollerthatistobe invoked ifnecessary,alsointhe commandlinemode . Request (Zend\Mvc\PhpEnvironment\Request):Providesaccesstoallrequestinformation, e.g.therequestparameters. Response (Zend\Http\PhpEnvironment\Response):Representstheanswergeneratedinthe courseofprocessingtotheclient.

OnetimeRequestandbackagain 53 ViewManager:TheViewManager performsafunctionfortheadministrationofviewsandtheir processingthatissimilartothatperformedbytheModuleManager forthemodulesandthe ServiceManager fortheservices.Itensuresthatthedatawillsometimebecome,forexample, webpageswithHTMLmarkup. ViewJsonRenderer (Zend\Mvc\Service\ViewJsonRendererFactory):Allowstherealisation ofRESTfulcontrollersandthusofwebservices,whichconformstotheRESTarchitecture style.Thistopicisdiscussedinachapterofitsowninthebook. ViewJsonStrategy (Zend\Mvc\Service\ViewJsonStrategyFactory):EnsuresthattheViewJsonRenderer willbeinvokedwhenrequired.InthescopeofthisStrategy,forexample,thesystemchecks toseewhethertheViewModel returnedbythecontrollerisoftheJsonModel type. ViewFeedRenderer (Zend\Mvc\Service\ViewFeedRendererFactory):Allowstherealisation ofRSSorAtomfeedsofthe viewdata returnedbyacontroller. ViewFeedStrategy (Zend\Mvc\Service\ViewJsonStrategyFactory):EnsuresthattheViewFeedRenderer willbeinvokedwhenrequired. PartofthisStrategy thedeterminationofwhetherthe ViewModel returnedbythecontrollerisoftheFeedModel type. ViewResolver (Zend\Mvc\Service\ViewResolverFactory):Makesitpossibletofind view templates . ViewTemplateMapResolver (Zend\Mvc\Service\ViewResolverFactory):Makesitpossiblefor theViewResolver tofindView-Templatesonthebasisofamap. ViewTemplatePathStack (Zend\Mvc\Service\ViewTemplatePathStackFactory):MakesitpossiblefortheViewResolve r tofindView-Templatesonthebasisofalistofpaths. Andadditionally: ControllerLoader (Zend\Mvc\Service\ControllerLoaderFactory):TheControllerLoader canloadacontrollerthatwaspreviouslylocalisedbyarouting. ControllerPluginManager (Zend\Mvc\Service\ControllerPluginManagerFactory):Makes theControllerPluginManager and,thus,anumberofplugins,whichcanbeusedincontrollers, areavailable;amongthem,forexample,theredirect pluginbymeansofwhichforwardingcanberealised. ThisservicecanalsoberequestedviatheControllerPluginBroker keys,Zend\Mvc\Controller\PluginBroker

orZend\Mvc\Controller\PluginManager. ViewHelperManager (Zend\Mvc\Service\ViewHelperManagerFactory):GeneratestheViewHelperManager, whichisresponsiblefortheadministrationofso-called viewhelpers . Thelatterthreeservicesareparticularlyinteresting,becausethey,inturn,comprisethen ew ServiceManager ,termed ScopedServiceManager inZFjargon.Whew,nowthingsarebeginning togetabitcomplicated! Solet stakeaslowlookatthings stepbystep. Tobeginwithwe shouldrememberthatthereistheone centralServiceManager inthesystem.Alloftheimportant applicationservices aregeneratedbyusingit.ItisbothaServiceManager inatechnicalsense http://de.wikipedia.org/wiki/Representational_State_Transfer

OnetimeRequestandbackagain 54 andtheconceptional centralServiceManager forus.However,therearespecificservicesthat theServiceManager itselfdoesnotprovide,butinsteadaremadeavailablebyspecialised subServiceManagers or scopedServiceManagers ,respectively,whichcanalsoprovideservicesvia theknownmechanisms,i.e. invocables , factories ,etc.AllofthemarealsoServiceManagers in atechnicalsense. Inthiscontext,let sagaintakealookatthelastchapter,inwhichwewroteourowncontroller. Therewefindthefollowingpassageinthemodule.config.php: 1 <?php 2 // [..] 3 'controllers' => array( 4 'invokables' => array( 5 'Helloworld\Controller\Index' 6 => 'Helloworld\Controller\IndexController' 7) 8) 9 // [..] Listing6.20 Whenthisconfigurationfragmentisinterpreted,thisresultsinreferencetotheappropriat e controllerclassundertheHelloworld\Controller\Index key,whichisregisteredasan invocable intheControllerLoader,oneofthestandard scopedServiceManagers .Thus,ifthiscontroller issubsequentlyidentifiedasappropriateinthescopeofroutingandmustthenbeinstantiate d,the systemusestheControllerLoader todothis.Inthiscontext,onecanthenalsocharacterisea controllerasaservice. ThisprocedureofthespecialisedSub-ServiceManagerhasseveraladvantagesforcertaintyp esof

services.Forexample,inthismannerthecentralServiceManagerfortheapplicationservice sis itselfnotoverloadedwithinnumerableservices,anditiseasytodetermineallofthecontrol lers,a taskthatwouldotherwisenotbenearlyaseasy.HerearethedifferentServiceManagersagaina ta glance: ApplicationServices(Zend\ServiceManager\ServiceManager):Configurationviatheservic e_manager keyorthegetServiceConfig() method(definedintheServiceProviderInterface). Controllers(Zend\Mvc\Controller\ControllerManager):Configurationviacontrollers key orgetControllerConfig() method(definedinControllerProviderInterface).Itcanbe obtainedinthe centralServiceManager viatheControllerLoader servicedesignation. Controllerplugins(Zend\Mvc\Controller\PluginManager):Configurationviathecontrolle r_plugins keyorgetControllerPluginConfig() method(definedinControllerPluginProviderInterface). Itcanbeobtainedinthe centralServiceManager viatheControllerPlugin manager service designation.

OnetimeRequestandbackagain Viewhelpers (Zend\View\HelperPluginManager):configurationviatheview_helpers key orthegetViewHelperConfig() method. (definedintheViewHelperProviderInterface). Itcanbeobtainedinthe centralServiceManager viatheViewHelperManager service designation. Loading the modules AftertheModuleManager hasbeenpreparedandtherequiredlistenershavebeenregistered,the actualloadingofthemodulesisinitiatedbyinvocationoftheloadModules()methodofthe ModuleManager. Atthistime,relativelylittlereallyhappensherebecausetheactualprocessing, forexampletheinvocationoftheabove-mentionedmethodsoftheModule.php,indeed occursinthemanyregisteredlisteners. Initially,theloadmodules.pre eventistriggered,and thentheloadModule.resolve eventandloadModule,foreveryactivatedmodule. Finally,the loadModules.posteventisagaintriggered.Andthatwasreallyeverything. The Module Event Object TheconceptoftheEventManager isthatinadditiontobeingthetriggerforaneventandthelisteners registeredfortheevent(receivers),theeventitself representedasindependentobject still exists.It ismadeavailabletoalllisteners.Thisobjectservestotransferadditionalevent-relevant information, forexampleareferencetothelocationinthecodewheretheeventistriggered.Moreover,addi tional data,whicharehelpfulfortheeventprocessinginthelisteners,canbetransferredThus,the module whichisnowbeingloadedisgenerallyofinterestforalistenerduringloadModule. Todojusticetothefactthat,dependingonthecontextoftheevent,otherdataareofinterest, the EventManager ofZendFramework2permitsdepositionofsituation-dependentspecialeventclasses. FortheeventprincipleinthescopeoftheModulManager,thereisasspecialModuleEvent class,which forexamplebearsboththemoduleinquestionandadditionallythenameofthemodule.

Activation of a module Inorderforamoduletobetakenintoaccountatall,anexplicitactivationinapplication.con fig.php intheconfig directoryisrequired:

OnetimeRequestandbackagain 1 <?php 2 return array( 3 'modules' => array( 4 'Application', 5 'Helloworld' 6) 7 ); Listing6.21 Methods of the module class Aswehaveseen,alargenumberofmethodsintheModule classofamoduleareinvokedifwehave implementedthem.InFrameworktherearetworelevantpossibilitiesoffindingoutwhetherth is isthecase.EithertheModule classimplementsaspecificinterface(thiscanindeedbetestedvia instance of) or the respective method is simply implemented (this can be checked via the method_exists() invocation). Let snowagaintakeadetailedlookatthemethodsintheModule class,whichareautomatically invokedbyFrameworkandcanbeusedbyapplicationdevelopers:

getAutoloadingConfig() (definedinAutoloaderProviderInterface): Wehavealready createdthismethodinourHelloworldmodule.Itprovidesinformationonhowtheclassesof themodulecanbeautomaticallyloaded.Ifweomitthismethod,theclassesofthemodule (forexampleitscontroller)normallycannotbeloadedandseriousproblemsoccurwhenthe correspondingURLisinvoked.Consequently,itshouldalwaysbeensuredthatinformation onhowtheclassescanbeautomaticallyloadedhasbeenmadeavailabletoFramework. Incidentally,inapurelytechnicalcontext,Frameworktakestheinformationtoincorporate anappropriateloaderimplementationforthismoduleviaspl_autoload_register(). init() (definedinInitProviderInterface):Thismethodallowstheapplicationdeveloper toinitialisehisorherownmodule,thus,forexample,toregisterhisorherownlisteners forcertainevents.Ifnecessary,theModuleManager isconsignedtothemethodandthelatter canthusaccesstheappropriateevents(oftheModuleManager)oraccessthemodules.The importantthingisthatthis method isalwaysinvoked,thatmeansforeveryrequest and indeedforeverymodule.Oneshouldalsorealizethatthisisagoodplacetoruintheloading timeofanapplication.Thus,onlyveryfewandideallyonlylight-weightoperationsshould beperformedinthescopeoftheinit() method.Ifoneisattemptingtoimprovethespeedof aZF2application,oneshouldalwaysfirsttakealookattheinit() methodsoftheactivated modules. Hereisanexamplefortheuseoftheinit()method:

OnetimeRequestandbackagain 1 <?php 2 namespace Helloworld; 3 4 use Zend\ModuleManager\ModuleManager; 5 use Zend\ModuleManager\ModuleEvent; 6 7 class Module 8{ 9 public function init(ModuleManager $moduleManager) 10 { 11 $moduleManager->getEventManager() 12 ->attach( 13 ModuleEvent::EVENT_LOAD_MODULES_POST, 14 array($this, 'onModulesPost') 15 ); 16 } 17

18 public function onModulesPost() 19 { 20 die("Modules loaded!"); 21 } 22 23 // [..] 24 } Listing6.21 onBoostrap() (definedinBootstrapListenerInterface): Anadditionaloptionforthe applicationdevelopertoimplementmodule-specificbootstrapping. Fundamentally,this methodhasthesamepurposeandutilityasinit(),buttheonBootstrap isinvokedlater intheprocessing;namely,whentheModuleManager hasalreadyfinisheditsworkandhas turnedtherudderovertotheApplication.Thus,whenusingtheonBootstrap(),method, servicesanddatawhichwerenotyetaccessibleintheinit() areavailable. getConfig() (definedinConfigProviderInterface):Wearealsoalreadyfamiliarwiththis method.Itprovidesthepossibilityofreferringtothemodule-specificconfigurationfile, which accordingtoconventionistermedmodule.config.php andisdepositedinthismodulein theconfig subdirectory.However,thisisnotobligatory.Strictlyspeaking,thismethodis absolutelyrequiredinorderforamoduletobeexecutable,but,inpractice,onecannotget alongwithoutamodule-specificconfigurationfile,whichonemakesaccessibletoFramework viathismodule.

Withregardtoconfiguration,Frameworkallowsacertainamountof flexibility.Thus,eitherallconfigurationscanbemadeavailableinoneormoreexternalfil es viagetConfig() orspecial Configmethods canbeimplementedintheModule class.The

OnetimeRequestandbackagain latterrefertothe ServiceManagers thatarepresentinthesystem,i.e.totheServiceManager, ControllerLoader,ViewHelperManager andControllerPluginManager. getServiceConfig():AllowstheconfigurationoftheServiceManager andisequivalentto the configarraykey service_manager inmodule.config.php. getControllerConfig():AllowstheconfigurationoftheControllerLoader andisequivalenttothe configarraykey controllers inmodule.config.php. getControllerPluginConfig():AllowstheconfigurationoftheControllerPluginManager andisequivalenttothe configarraykey controller_plugins inmodule.config.php. getViewHelperConfig():AllowstheconfigurationoftheViewHelperManager andisequivalenttothe configarraykey view_helpers inmodule.config.php. Inthiscontextanotherexample:ourownViewHelper(moreontheconceptofthe viewhelper on thefollowingpages)caneitherbemadeknowninthescopeofthemodule.config.php asfollows 1 <?php 2 'view_helpers' => array( 3 'invokables' => array( 4 'displayCurrentDate' 5 => 'Helloworld\View\Helper\DisplayCurrentDate' 6) 7) Listing6.22 orintheModule.php withtheaidoftheappropriatemethod: 1 <?php 2 public function

getViewHelperConfig() 3{ 4 return array( 5 'invokables' => array( 6 'displayCurrentDate' 7 => 'Helloworld\View\Helper\DisplayCurrentDate' 8) 9 ); 10 } Listing6.23

OnetimeRequestandbackagain 59 Application Now,wheretheServiceManager hasbeenequippedwiththerequiredservices,andtheModuleManager hasloadedtheapplication smodules,theApplication itselfcanbestartedandtherequest processing,initiated.Thisoccursin3steps,partlyintheinit() methodoftheapplicationitself andpartlyintheindex.php:Startingtheapplication(bootstrap()),followedbytheexecuti on (run())andlastbutnotleastreturningthegeneratedresults(send()): 1 <?php 2 $application = $serviceManager->get('Application'); 3 $application->bootstrap() 4 $application->run(); 5 $application->send(); Listing6.24 Generation of the application & bootstrapping TheapplicationobjectisgeneratedviathefactorythatisregisteredintheServiceManager. Inthe scopeofthegeneration,theApplication invokesanumberofstandardservices,amongthemboth theRequest,asthebasisforfurtherprocessing,andtheResponse,whichistobefilledwithli fein thescopeoftheprocessing.Inaddition,theApplication getsareferencetotheModuleManager and itsowninstanceoftheEventManager (whichisindeeddepositedintheServiceManager suchthat itisnotshared;thusanewinstanceofthisserviceisreturned).Thelatterensures analogousl yto theModuleManager thattheApplication cantriggereventsandinformlisteners.TheApplication

setsoffanumberofeventsinthescopeoftheprocessing. bootstrap:Isexecutedwhentheapplicationisstarted. route:OccurswhenacontrollerandanactionaredeterminedfortheURL. dispatch:Takesplacewhenadeterminedcontrollerisidentifiedandinvoked. render:Occurswhentheresultforthereturnispreparedonthebasisoftemplates. finish:Isexecutedwhentheapplicationhasbeencompleted. InthecourseofbootstrappingtheApplication likewiseregisters(incidentallyinthiscasethe Application itselfandnottheApplicationFactory)anumberoflisteners,whichitalsoobtainsvia theServiceManager:RouteListener fortheroute event,DispatchListener forthedispatch event andtheViewManager foracolourfulbunchofadditionallisteners,whichperformtheprocessingof templatesandlayouts.Moreaboutthisinthenextsection. Furthermore,theApplication thencreatestheMVC-specificeventobject(MvcEvent),whichis registeredwiththeEventManager aseventobject.MvcEvent thenenablesthelistenerstoaccess Request,Response,Application andtheRouter.

OnetimeRequestandbackagain Finally,thebootstrap eventisinitiatedandtheregisteredlistenersarerun. Theycanalso particularlybetheapplication sindividualmodules,whichhaveregisteredforjustthiseven t. Execution Therun() method,whichisexecutedsubsequenttobootstrapping,thenactuatesanumberof levers thathadalreadybeenplacedinthecorrectposition. Tobeginwith,theApplication triggerstheroute event.TheRouteListener,whichwasregisteredforthiseventbeforehand,is runandtheRouter fromtheMvcEvent isaskedtoperformitsservices:i.e.tomatchtheURLto adefinedroute.Inthiscase,arouteisthedescriptionofaURLonthebasisofadefinedpattern . Wewilltakeadetailedlookatthemechanicsofroutinglater.Atthemoment,weonlyhaveto rememberthattheRouter noweitherfindsaroutethatfitstheinvokedURLandthusdetermines theappropriatecontrolleraswellastheappropriateactionor,onthecontrary,theRouter returns withbadnewsanddidnotturnupanysearchresultsatall.Butlet sinitiallyremainonthesucces sful pathinthiscase.TheappropriaterouteisdepositedintheformofaRouteMatch objectinMvcEvent bytheRouteListener.MvcEvent isthusincreasinglyprovingtobeacentralobjectinwhicha numberofotherimportantobjectsanddataareavailable.Thenthedispatch eventisinitiated,the ControllerLoader isinvokedandusestheMvcEvent tofindtheidentifiedcontroller,whichisto beinstantiated.Toachievethis,theDispatchListener requeststheControllerLoader fromthe ServiceManager (which,technicallyspeaking,initsownrightisalsoagaina ServiceManager ) andthentheactualcontrollerfromit(i.e. fromtheControllerLoader).Inthecourseofthis, thecontrollerisalsoequippedwithanEventManager ofitsown.Now,itcanthusactuateevents andmanagelisteners. Thenthecontroller sdispatch() methodisinvoked,andperformsthe furtherprocessingitself.Ifanyintermittentproblemsoccurinthisenterprise,thedispat ch.error eventistriggered;otherwise,theresultofthedispatch() invocationisdepositedinMvcEvent and additionallyreturnedandthereuponthedispatchprocesswithinthecontrollerisconcluded .

InZendFramework,controllersaresoconceivedthattheyonlyhavetohaveonedispatch() method attheirdisposal.Thisisexternallyinvoked,intheprocesstheRequestobjectistransferre d,and thecontrollerisexpectedtoreturnanobjectoftheZend\Stdlib\ResponseInterface typewhen theworkhasbeencompleted. Inorderfortheprincipleofcontrollerandactiontofunction, asoneisaccustomedtoandexpects,thislogicmustbeimplementedinthecontrolleritself; otherwiseonlythedispatch methodwouldbeinvoked,butnottheappropriate action method. Toinsurethatthatonedoesnothavetodothisoneself,one sowncontrollerinheritsthisfrom theZend\Mvc\Controller\AbstractActionController.Subsequently,anyarbitraryactions canbe depositedinthecontrollerwhenoneadherestotheconventionthatthemethodnamemustend with action :

OnetimeRequestandbackagain 1 <?php 2 3 namespace Helloworld\Controller; 4 5 use Zend\Mvc\Controller\AbstractActionController; 6 use Zend\View\Model\ViewModel; 7 8 class IndexController extends AbstractActionController 9 { 10 private $greetingService; 11 12 public function indexAction() 13 { 14 return new ViewModel( 15 array( 16 'greeting' => $this->greetingService->getGreeting(), 17 'date' => $this->currentDate() 18 ) 19 ); 20 } 21 } Listing6.25 BackintheApplication ,thetworesultsrender

andfinish arenowinitiatedandtherun() method concluded.Incidentally,thefollowingfactisveryinterestingandhelpful.:Normally,ana ction returnsanobjectoftheViewModel typeattheendofprocessing. Itthusimplicitlysignalsthe subsequentprocessingstepsthattheresultmuststillbeprocessedbeforeitcanbereturned. 1 <?php 2 public function indexAction() 3 { 4 return new ViewModel( 5 array( 6 'greeting' => $this->greetingService->getGreeting(), 7 'date' => $this->currentDate() 8 ) 9 ); 10 } Listing6.26 However,averypracticalimplementationdetailisthefactthatwhenaResponse objectisreturned insteadofaViewModel,thedownstreamrender activitiesareomitted.

OnetimeRequestandbackagain 1 <?php 2 public function indexAction() 3{ 4 $resp = new \Zend\Http\PhpEnvironment\Response; 5 $resp->setStatusCode(503); 6 return $resp; 7} Listing6.27 Thismechanismishelpfulifonedesires,forexample,tobrieflyreturna503code,becausethe applicationisjustundergoingscheduledmaintenanceorwhenonedesirestoreturndataofasp ecific Mimetype,forexamplethecontentsofanimage,ofaPDFdocumentorsomethingsimilar. ViewManager Beforetheprocessinghasended,theViewManagercomesintoplayagain.Intheprevioussectio n, wehavealreadyseenthattheonBootstrap() methodisexecutedinthescopeofthebootstrapping oftheApplication (becauseitisregisteredforthecorrespondingevent)andthatanentireseries ofadditionalpreparationsaremadethere.Uptonow,wehaveblendedthisoutforsimplicity s sake,butnowwealsohavetolookatthedetailsinthiscase. AftertheViewManager withits manycollaboratorshascompleteditswork,wehaveactuallyworkedourwaythroughtheentire processingchainonce. Tobeginwith,theViewManager obtainstheConfig fromtheServiceManager,whichatthistime alreadyrepresentsthemerged totalconfiguration oftheapplicationandofthemodules.The ViewManager looksfortheview_manager keyandusestheconfigurationsdepositedthere.Thenthe oldgameofregisteringdiverselistenersandtheprovisionofadditionalservicesbeginsaga in.

View-oriented listeners Thefollowinglistenersaregeneratedandallofthemareattachedtothedispatch eventofthe ActionController class(ormoreexactly:oftheEventManager oftheActionController): CreateViewModelListener:Ensuresthat,afterexecutionofthecontroller,anobjectofthe ViewModel typeisavailablefortherendering,evenifonlyNULL oranarraywasmadeavailable bythecontroller. RouteNotFoundStrategy:GeneratesaViewModel forthecasethatnocontrollerwasdeterminedandno ViewModel couldbegenerated(404error). InjectTemplateListener:AddstheappropriatetemplatetotheViewModelforsubsequent rendering.

OnetimeRequestandbackagain InjectViewModelListener:AddstheViewModel toMvcEvent.Thislistenerisalsoregistered forthedispatch.error eventoftheApplication inordertoalsobeabletomakeaViewModel availableincaseoferror. Thedispatch eventisincidentallysomewhatnasty:itoccurstwiceinthesystemItisonce triggeredbytheApplication,andagainbytheActionController.Evenifthedesignationofth e eventisidentical(itcanindeedbefreelyselected),duetothefactthatitistriggeredbydif ferent EventManagers,wearedealingwithtwocompletelydifferentevents. Incidentally,atthistime,theEventManager oftherespective,specificmanifestationofthe ActionController doesnotyetexistbecausethelatterhasnotyetbeengeneratedatall.Inthiscase, thisproblemisavoidedbyusingtheSharedEventManager.WiththeaidoftheSharedEventManag er, listenersfortheeventsofanEventManager,whichdoesnotevenexistatthetimeofregistrati on,can beregistered.Forthetimebeing,we llsimpleleavethingsastheyare.We lltakeamoredetailed lookathowthismechanismisrealizedinthenextchapter. View-oriented services Inaddition,anumberofview-orientedservicesaremadeavailableintheServiceManager: View andView Model:TobeginwiththereistheView itselfwithitsView Model,the representationofthe payload generatedfromarequestfortheresponse. DefaultRenderingStrategy:CanaccesstheView andisregisteredfortherender Eventofthe Application.Ifthiseventoccurs,theView istransferredtotheViewModel,whichisobtained fromtheMvcEvent andthenpromptstheView torenderjustthat. ViewPhpRendererStrategy:However,theactualrenderingisnotperformedbytheViewitself, butisinsteaddelegatedtotheViewPhpRendererStrategy, which initially specifies the appropriate renderer

and transfers the finished result to the Response subsequent toprocessing. RouteNotFoundStrategy:Definestheappropriatebehaviourincaseofa404situation. ExceptionStrategy:Definestheappropriatebehaviourincaseofa dispatcherrors . ViewRenderer:Takesovertheactualrenderingwork,i.e.themergingoftheViewModel data andtheappropriatetemplate. ViewResolver:Inordertolocalisetherespectivetemplate,theViewRenderer accessesthe ViewResolver. ViewTemplatePathStack:Makesitpossibleforthe resolver tolocaliseatemplateonthe basisofdepositedpaths. ViewTemplateMapResolver:Makesitpossibleforthe resolver tolocaliseatemplateonthe basisofa keyvalueassignment . ViewHelperManager:Makesitpossibletoaccess ViewHelpers intemplates;thissimplifies thegenerationofdynamicmarkup.

OnetimeRequestandbackagain Incaseofanerrorinthescopeofroutingorinthecourseofdispatching,respectively,the Application triggersadispatch.error event.Thissignalisesthatanerrorhasoccurredinthe processing. Summary Atfirstglance,therelationshipsappearverycomplex;theimplementationoftheMVCpattern in ZendFrameworkinitiallyfeelssomehowover-engineered.Themainreasonforthisfeelingist he factthat,ontheonehand,theentireprocessingprocedureisbrokendownintoextremelysmall individualsteps,whicharerepresentedviaindividualclassesineachcase,andwhichmustal so bechronologicallyandcontentually orchestrated inorderthattheyalsoultimatelymeaningfu lly interact totheextentthatisrequired.Ontheotherhand,theexcessiveuseofeventsandlisten ers makesitdifficulttounderstandtheprocessesandrelationshipswithintheapplication.Nor does theuseofnumerousdesignpatternstexactlycontributetocomprehension,particularlyinth e beginning,especiallynotwhenoneisnotyetaccustomedtothem. Isn titpossibletosimplifythingsgreatly?MustMVCimplementationreallyalwaysbesocomple x? Thquick,unreflectedanswertothesequestionswouldbe:Yes.No.Thereisalargenumberof so-called MicroMVCFrameworks ,suchasSilexorMicroMVC,whichatfirstglanceappearto beabletoachievesimilarresultswithsignificantlylesscomplexitythantheMVCimplementa tion inZendFramework2.However,thisisonlytrueatfirstglance. Tobeginwith,Zend\Mvc isactuallymuchmorethan MVC .Itisinrealityanapplicationplatform thatallows1)thesimpleandeffectiveintegrationofadditionalfunctionintheformofone sow n orthirdpartymodules,2)themodificationorcompleterestructuringofrequestprocessingi n nearlyanyarbitrarymanner,and3)which,asaresultofitsloosecouplingapproachtoindivid ual componentsandservices,alsoallowsfulfilmentoftherequirementsofcompanyapplications with regardtomaintainabilityandextensibilityaswellastestability.Zend\Mvc achievesanenvironment ofsoftwarecomponentsandisableelevatetheabstractionlevel,onwhichanearlierapplicat ion developermovedaboutinthedevelopmentofwebapplicationswithPHP,toanew,substantially moreproductiveone.Atleast,itisbeginningtoachievejustthat.Whetherthiswillreallysu cceed

mustbeproven.IsZendFramework2therightsoftwareformyproject?Ithinkthatthisquestion is moredifficulttoanswerfortheVersion2thanitwasforVersion1.TheadvantagesofVersion2a re particularlyaimedattheprofessionapplication,whichdoesnotalwaysexist.IsZendFramew ork 2therightsoftwareformycompanyapplicationintheweb?Yes,Iwouldcategoricallysaythati n anycase. Let ssummarisethischapterandthecourseofrequestprocessingagainbriefly.Tobeginwith, therequestlandsattheindex.php viatheuseofURLrewriting(forexample,via mod_rewrite andtheappropriate.htaccess file).Theautoloadingisconfiguredthere,whichthusensuresthat bothZendFrameworkitself,butalso,ifneedbe,additionallyusedlibrariesfunctionproper ly. http://silex.sensiolabs.org/ http://micromvc.com/

OnetimeRequestandbackagain Subsequently,theApplication isstarted,whichinitiallyensuresthattheServiceManager,the central serviceaccess ,isgeneratedandequippedwithimportantservices:theModuleManager andtheEventManager.Furthermore,theSharedEventManagerismadeavailableAsinthecaseof theModuleManager andtheEventManager,madeavailablefrequentlymeansthattobeginwithonly thefactories,whichareconsultedforthesubsequentgenerationofactualservicesineachca se, aremadeknown.Whyshouldwedetourviafactories?Ontheonehand,becausetheyallowus toexchangethespecificimplementationoftherespectiveserviceifnecessary.Andontheoth er hand,becauseinthescopeofservicegeneration,notonlytheserviceitselfisgenerated,but also anumberoflisteners,whichareregisteredforthesubsequentprocessingevents,asisthecas efor theModuleManager.Indeed,theModuleManagerFactory,ApplicationFactory andtheViewManager performinasimilarmannerinthegenerationofservices.Theyinitiallygeneratetheactuals ervice, thenanumberofadditional(sub-)services,whichtheservicewillsubsequentlyaccess,andf inally oneormorelistenersfortheeventsofone sownorotherservices.Thelistenersthentakeovera specifictaskthemselvesorreferbacktotheservices. Thisprocedureensuresthatthemethodsrun,loadModules,bootstrap,&co.oftheModuleManag er, Application &co.areveryleanandreallydon tdoanythingthemselvesotherthantotrigger theevents.Everythingelsethenpassesviathelistenerstotheservices.Duringitsexecutio n,the ModuleManager initiallytriggersthefollowingevents:loadModules.pre,theloadModule.resolve, theloadModule andtheloadModules.post.Subsequently,theApplication setsoffthebootstrap, route anddispatchevents;subsequentlythecontrollerwithitsowndispatch event(nottobe confusedwiththatoftheApplication);finallytheApplication againwithrender,beforetheView reportswithrenderer andresponse.Lastbutnotleast,theApplication endsthefireworkevent withfinish.Incaseofanerrorinthescopeofroutingorinthecourseofdispatching,respecti vely, theApplication triggersadispatch.error event.Thissignalisesthataproblemhasoccurredin processandthattheappropriateerrortreatmenthasbeenactivated.

EventManager WesawinthepreviouschaptertheextenttowhichtheFrameworkasawholeisbasedonthe ideaofeventtriggers,eventobjectsandeventlisteners.Intheprocess,theappropriateobj ectsor managers ,eachmakesitsownEventManager available,whichmanagestheeventsoftherespective objectandalsoallowstheadditionandsubtractionoflisteners.BecausetheZend\EventMana ger playssuchanimportantroleforthefunctionofFramework,butalsobecauseitcanbeveryusefu l inthedevelopmentofone sownapplication,wewilltakeadetailedlookatitinthefollowing. Registering a listener Inparticular,theEventManagerprovidestwomethodswhichareinterestingforlisteners: 1 <?php 2 public function attach($event, $callback = null, $priority = 1); 3 public function detach($listener); Listing7.1 Withtheattach() method,alistenercanberegisteredforaspecificevent,whereasdetach() removesalistener.Thelistenersthatareregisteredforaneventareinformedinsequence.Wh ereby, inthiscontext, informed meansthattherespectiveregisteredCallbacks which,asoneisalso accustomedfromthenativePHPfunctions,inadditiontofunctions,classmethods,andobject methodsmayalsobeclosures areinvoked. Inthiscontext,thedesignationoftheeventforwhichthelistenershouldberegisteredmustb e initiallyspecified.Asconventioninthedesignationofanevent,onefrequentlyresortstot hemagic constant__FUNCTION__

sothatthemethodthatistriggeredintheeventalsobecomesthenamegiver fortheeventitself.However,thisisnotrequired:thenamecanbefreelyselected: 1 <?php 2 $greetingService->getEventManager()->attach( 3 'event1', 4 function($e) { 5 // [..] 6} 7 ); Listing7.2 Inthiscase,acallbackfunctionisregisteredforthe event1 event.Insteadofastring,anarray with severaleventdesignationscanalsobetransferredinthecourseoftheregistrationifonedes iresto registeronelistenerforanumberofevents. 66

EventManager 1 <?php 2 $greetingService->getEventManager()->attach( 3 array('event1', 'event2'), 4 function($e) { 5 // [..] 6 } 7 ); Listing7.3 Registering several listeners at the same time However,itisnotonlypossibletoregisteronelistenerforanumberofeventsinonego,butals oto registeranumberoflistenersforoneorevenconcurrentlyforseveraleventsinonefellswoop .To achievethisoneusesso-called listeneraggregates .Theyareparticularlyhelpfulwhenonedes ires togrouptheregistrationofindividuallistenerslogically.Todothis,onemakesaclassofon e sown available,whichimplementstheZend\EventManager\ListenerAggregateInterface andthushas anattach() andadetach() methodatitsdisposal.Theindividuallistenersarethenregistered thereenbloc: 1 <?php 2 namespace Helloworld\Event; 3 4 use Zend\EventManager\ListenerAggregateInterface; 5 use Zend\EventManager\EventManagerInterface; 6 7 class MyGetGreetingEventListenerAggregate 8

implements ListenerAggregateInterface 9 { 10 public function attach(EventManagerInterface $eventManager) 11 { 12 $eventManager->attach( 13 'getGreeting', 14 function($e){ 15 // [..] 16 } 17 ); 18 19 $eventManager->attach( 20 'refreshGreeting', 21 function($e){

EventManager 22 //[..] 23 } 24 ); 25 } 26 27 public function detach(EventManagerInterface $events) 28 { 29 // [..] 30 } 31 } Listing7.4 Addingthelistenersthenbecomesaone-liner,becausetheattach() methoddoesalltheheavy liftingandiscalledautomatically: 1 <?php 2 $greetingService 3 ->getEventManager() 4 ->attach( 5 new \Helloworld\Event\MyGetGreetingEventListenerAggregate() 6 ); Listing7.5 Removing a registered listener Analreadyregisteredlistenercanberemovedbymeansofthedetach() methodoftheEventManager. Toachievethis,onecanuseaListenerAggregateInterface fordetach() inamanneranalogoustoattach() orremovethelistenersindividually. Todothis,oneconsignstherespective CallbackHandler,whichwere,forexample,returnedastheresultofattach(),todetach(): 1 <?php 2

$handler = $greetingService->getEventManager() 3 ->attach('getGreeting', function($e){ // [..] }); 4 5 $greetingService->getEventManager()->detach($handler); Listing7.6

EventManager Trigger an event ThefollowinginvocationissufficienttotriggeraneventifanEventManager isavailableinthe eventManager membervariableoftheobject. 1 <?php 2 $this->eventManager->trigger('event1'); Listing7.7 Allthelistenersthathavebeenregisteredforthiseventarenowinvoked. Theentireprocess naturallyoccurssequentiallyandblockingly. Eachlistenerisindividuallyinvokedandonly subsequenttocompleteprocessingisthenextlistenerprocessed.Intheprocess,theprocess ing sequenceisstipulatedbythelistener spriority,whichcanbedeclaredforalisteneratattach () or, however,bythesequenceinwhichthelistenerswereadded. Thetrigger() methodcanbeinvoked,asshownabove,withastringthatrepresentsthenameof therespectiveevent,or,however,whenacorrespondingeventobjectisconsigned. 1 <?php 2 $event = new Zend\EventManager\Event(); 3 $event->setName('getGreeting'); 4 $this->eventManager->trigger($event); Listing7.7 Incidentally,theinvocationoftrigger() returnsaresultoftheResponseCollection type;namely

everythingthattheinvokedlistenershavepreviouslyreturnedindividuallyisreturnedcol lectively. Byusingitsfirst() andlast() methods,itispossibletoaccessthemostimportantreturnvalues, andtocheckforaspecificreturnvalueinthecollectionwithcontains($value).Inthisconte xt, theexecutionsequenceofthelistenersresults asalreadydescribedabove eitherfrompriority explicitlyspecifiedinthescopeofattach() oralternativelysimplyfromthesequencewithwhich thelistenerswereadded.Inthiscontext,theFIFOprincipleapplies:firstin,firstout.The listener thatwasaddedfirstwillthusbeexecutedfirst. Andanotherinterestingthing:theprocessingoftheindividuallistenerscanalsobeinterru pted intermediately.Butwhatisitgoodfor?Let slookatanexample:imaginethatyouoperatea websiteonwhichuserscanwriteareviewsofabooks.Inordertobeabletoassignthemproperly, youinitiallyretrievetheISBNofthebookandthusensurethatthebook stitle,author,etc.don ot havetobemanuallyinputbytheuser.Todothisyoueitheraccessdatathatarealreadyinyour database(however,thatisonlythenthecasewhenatleastonereviewofthebookhasalreadybee n written)oryouobtainthebook sdetailsfromaremotewebservice,forexamplefromAmazon.How canyounowloadyourdatainthemostefficientmanner?Youwouldpreparetwolisteners,which

EventManager youwouldregisterfortheonBookDataLoad eventofyourapplication,wherebythefirstlistenerlooks forthedesireddatainyourdatabaseandthesecond,inaremotewebservice.However,thesecon d listenerisonlythenexecutedwhenthefirstonewasnotsuccessful.Toachievethis,theexecu tion oftheeventcanbeextendedbyacallback,whichwillbecheckedforaspecificreturnvalue: 1 <?php 2 $results = $this->events()->trigger( 3 __FUNCTION__, 4 $this, 5 array(), 6 function ($returnValue) { 7 return ($returnValue instanceof MyModule\Model\Book); 8} 9 ); Listing7.8 IfthefirstlistenerreturnsanobjectoftheMyModule\Model\Book type,thebook sdetailshavebeen successfullyloaded,andtheprocessingendsatthistime.Alternatively,theprocessingcan alsobe endedwithinalistenerifthestopPropagation() methodoftheeventobjectisactivelyinvoked. SharedEventManager TheSharedEventManager isthesolutiontothefollowingproblem:Whatdoesalistenerdoifit desirestoregisteritselfwithaneventtriggerthatdoesnotyetexistatthetimeofthedesire d registration?Inpractice,thisproblemariseswhenamodulewantstoregisteracallbackfort he controllerevent dispatch inthescopeitsinit()

oronBootstrap() method.Atthistime,the controllerwithitsEventManager managerhasnotyetbeenbroughttolife. Itisthussimply impossible. IfanewEventManager isrequestedviatheServiceManager,thelatterensuresthattheformer isadditionallygivenareferenceonthedividedSharedEventManager foralltheEventManager instancesthathavebeengeneratedinthismanner.Listenersforspecificeventscanberegist ered intheSharedEventManager underdeclarationofalistener identifier .Inthiscontext,itinitially doesnotmakeanydifferencewhethertheEventManager,whichwillsetofftheeventlater,alre ady existsornot.Theonlyimportantthingisthefactthatoneusesan identifier whenregistering alistenerandthattherespectiveEventManager laterrecognizesandexecutesit.Let slookata definiteexamplewhichwillclarifytheprinciple.Whentheinit() methodoftheModule classof ourHelloworld moduleisexecuted,theApplication doesnotyetexist.Itwillbefirstgiventhe breathoflifewhenthemodulehasbeencompletelyloaded.Ifwethusnowdesiretoregistera listenerfortheroute eventoftheApplication,wehavetodothisviatheSharedEventManager,we havenootherchoice:

EventManager 71 1 <?php 2 // [..] 3 public function init(ModuleManager $moduleManager) 4{ 5 $moduleManager 6 ->getEventManager() 7 ->attach( 8 ModuleEvent::EVENT_LOAD_MODULES_POST, 9 array($this, 'onModulesPost') 10 ); 11 12 $sharedEvents = $moduleManager 13 ->getEventManager()->getSharedManager(); 14 15 $sharedEvents->attach( 16 'application', 17 'route',

18 function($e) { 19 die("Event '{$e->getName()}' wurde ausgeloest!"); 20 } 21 ); 22 } Listing7.9 WearenowintheModule.php fileoftheHelloworld module.ViathedenModuleManager,wereach itsEventManager andviathelatterweinturn,theSharedEventManager,whichisautomatically sharedbyallthe EventManagers thathavebeengeneratedbytheServiceManager.Therewenow registeracallback(exemplarilyintheformofaclosure)fortheroute eventthatwilltrigger/start theApplication oritsEventManager,respectively,atsometimeinthefuture.Theapplication key inthiscaseisaconvention,astring,thatonemustknow.Atthemoment,inwhichtheApplicatio n subsequentlytriggerstheroute eventwithitsownEventManager whichwecouldnotuseforthe registrationofthelisteneruptothistime allofthelistenersthathaveregisteredthemselve sbythe SharedEventManager fortheapplication keyandthecorrespondingeventwillalsobeinformed. That sextremelypractical! Thefollowing identifiers arepreconfiguredfortheindividualFrameworkcomponents: FortheModuleManager:module_manager,Zend\ModuleManager\ModuleManager. FortheApplication:application,Zend\Mvc\Application. AbstractActionController (thebasicclassforone sown controller ):Zend\Stdlib\DispatchableInterface Zend\Mvc\Controller\AbstractActionController,thefirstpartofthecontroller snamespac e(

forHelloworld\Controller\IndexController thatwouldbe,forexample,Helloworld).

EventManager View:Zend\View\View TheSharedEventManager isthusparticularlyappropriateforsituationsinwhichtheEventManager, whichwouldactuallyberesponsibleandwhichonewoulddesiretousefortheregistrationofa listener,isnotyetavailable. Besidestheabove-mentioned identifiers ,whichareautomaticallygeneratedbyFramework, additionalidentifierscanbedefinedforone sownpurposes. Using events in one s own classes TheZend\EventManager allowsaclasstobecomeatriggerforeventsandtoadministerlisteners. ItsfunctionisnotrestrictedtoclassesandotherFrameworkcomponents quitethecontrary: Zend\EventManager ishighlyappropriatetoevenserveasanindependentimplementationofeventcontrolledproc essing. Todothis,one sownclass,whichshouldtriggerevents,mustmerelymaintainaninstanceofthe Zend\EventManager inamembervariable.Let sagainusetheGreetingService thatweusedabove. Assumingthatwewouldliketowriteanentryinourlogfilewheneveradatehasbeengenerated suchthatwealwaysknowhowfrequentlythisservicewasinvokedinaspecifictimeinterval.Ho w couldwerealizethis?Let stakealookatonepossibility. Tobeginwith,wesetupanadditionalserviceinourmodule.Toachievethis,wepreparethe src/Helloworld/Service/LoggingService.php filewiththefollowingcontents: 1 <?php 2 namespace Helloworld\Service; 3 4 class LoggingService 5{ 6 public function onGetGreeting()

7{ 8 // Logging-Implementierung 9} 10 } Listing7.10 Wewillignorethespecificimplementationofloggingatthistimebecauseweareprimarilycon sideringtheinteractionofdifferentservicesviath eeventsystem.WewanttoinvokeonGetGreeting() assoonasthegetGreeting eventoftheGreetingService occurs. Inaddition,weensurethattheserviceisknowntothesystem. Toachievethis,weaddthe appropriateclassasaninvocable toourmodule sModule.php inthegetServiceConfig() method.

EventManager 1 <?php 2 // [..] 3 'invokables' => array( 4 'loggingService' => 'Helloworld\Service\LoggingService' 5 ) 6 // [..] Listing7.11 FromnowontheLoggingService canthusberequestedviatheloggingService keyinthe ServiceManager.Sofarsogood.Now,wemustadditionallyensurethattheGreetingService cantriggeraneventonthebasisofwhichtheonGetGreeting methodoftheLoggingService can berun.TomaketheGreetingService availabletoanEventManager andconcurrentlytoregister theexecutionoftheonGetGreeting methodoftheLoggingService,weslotafactoryaheadofthe GreetingService: 1 <?php 2 namespace Helloworld\Service; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class GreetingServiceFactory implements FactoryInterface 8 { 9 public function createService(ServiceLocatorInterface

$serviceLocator) 10 { 11 $greetingService = new GreetingService(); 12 13 $greetingService->setEventManager( 14 $serviceLocator->get('eventManager') 15 ); 16 17 $loggingService = $serviceLocator->get('loggingService'); 18 19 $greetingService->getEventManager() 20 ->attach( 21 'getGreeting', 22 array($loggingService, 'onGetGreeting') 23 ); 24 25 return $greetingService; 26 } 27 }

EventManager Listing7.12 Wefilethefactoryinsrc/Helloworld/Service/GreetingServiceFactory.php;itisthusloca tedin thesamedirectoryastheserviceitself.Initially,thefactorygeneratestheGreetingServi ce,which itultimatelyalsoreturns.Beforehand,thefactoryadditionallyarrangesforanEventManag er forthe GreetingService sothattheGreetingService cannowalsotriggereventsandmanagelisteners. AndthentheLoggingService anditsonGetGreeting() methodareregisteredforthegetGreeting event.Byusingaclosure,wecanevenensurethatthe$loggingService,whichwedirectlyreque st intheaboveexample,isalsofirstrequestedatthemomentoftheactualeventvia lazyloading . 1 <?php 2 namespace Helloworld\Service; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class GreetingServiceFactory implements FactoryInterface 8 { 9 public function createService(ServiceLocatorInterface $serviceLocator) 10 { 11 $greetingService = new GreetingService(); 12 13 $greetingService->setEventManager( 14 $serviceLocator->get('eventManager') 15 );

16 17 $greetingService->getEventManager() 18 ->attach( 19 'getGreeting', 20 function($e) use($serviceLocator) { 21 $serviceLocator 22 ->get('loggingService') 23 ->onGetGreeting($e); 24 } 25 ); 26 27 return $greetingService; 28 } 29 } Listing7.13 Theadvantageisobvious:theLoggingService isactuallyonlythegeneratedwhenitisalsotobe used.Indeed,thegetGreeting eventmaysimplynevertakeplace.

EventManager WestillhavetoadaptthegetServiceConfig()methodofourmodule sgetServiceConfig() class suchthattheServiceManager nowusesthenewfactorytogeneratetheGreetingService.The getServiceConfig() thenappearsasfollows: 1 <?php 2 // [..] 3 public function getServiceConfig() 4{ 5 return array( 6 'factories' => array( 7 'greetingService' 8 9 ), 10 'invokables' => array( 11 'loggingService' 12 13 ) 14 ); 15 } Listing7.14 => 'Helloworld\Service\GreetingServiceFactory'

=> 'Helloworld\Service\LoggingService' WenowteachtheGreetingService howtosetoffthecorrespondingevent: 1 <?php 2 namespace Helloworld\Service; 3 4 use Zend\EventManager\EventManagerInterface; 5 6 class GreetingService 7{ 8 private $eventManager; 9 10 public function getGreeting() 11 { 12 $this->eventManager->trigger('getGreeting'); 13 14 if(date("H") <= 11) 15 return "Good morning, world!"; 16 else if (date("H") > 11 && date("H") < 17) 17 return "Hello, world!";

18 else 19 return "Good evening, world!";

EventManager 20 } 21 22 public 23 { 24 25 } 26 27 public 28 { 29 30 } 31 } Listing7.15 function getEventManager() return $this->eventManager; function setEventManager(EventManagerInterface $em) $this->eventManager = $em; Andthatwasaboutit.IfthegetGreeting() methodisinvoked,thecorrespondingevent,forwhich weregisteredourlogger,willbetriggered. IfweusetheSharedServiceManager,wecansimplifytheGreetingServiceFactory evenmore: 1 <?php 2 namespace Helloworld\Service; 3

4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class GreetingServiceFactory implements FactoryInterface 8{ 9 public function createService(ServiceLocatorInterface $serviceLocator) 10 { 11 $serviceLocator 12 ->get('sharedEventManager') 13 ->attach( 14 'GreetingService', 15 'getGreeting', 16 function($e) use($serviceLocator) { 17 $serviceLocator 18 ->get('loggingService') 19 ->onGetGreeting($e);

20 } 21 ); 22 23 $greetingService = new GreetingService();

EventManager 24 return $greetingService; 25 } 26 } Listing7.16 However,thenonemustensurethat hereinanexemplarymannerdirectlyintheservicebeforethe eventissetoff therespectiveEventManager alsofeelsresponsibleforthat identifier .Toachieve this,oneusedtheaddIdentifiers() method: 1 <?php 2 namespace Helloworld\Service; 3 4 use Zend\EventManager\EventManagerAwareInterface; 5 use Zend\EventManager\EventManagerInterface; 6 use Zend\EventManager\Event; 7 8 class GreetingService implements EventManagerAwareInterface 9 { 10 private $eventManager; 11 12 public function getGreeting() 13 { 14 $this->eventManager->addIdentifiers('GreetingService'); 15 $this->eventManager->trigger('getGreeting'); 16 17 if(date("H")

<= 11) 18 return "Good morning, world!"; 19 else if (date("H") > 11 && date("H") < 17) 20 return "Hello, world!"; 21 else 22 return "Good evening, world!"; 23 } 24 25 public function getEventManager() 26 { 27 return $this->eventManager; 28 } 29 30 public function setEventManager(EventManagerInterface $em) 31 { 32 $this->eventManager = $em; 33 } 34 }

EventManager Listing7.17 The event object Theeventtriggerandtheeventlistenercommunicateviatheeventobject,whichisgeneratedb y theeventtriggerandwhichismadeavailabletotheeventlisteneroninvocation.Whentheeven t istriggeredwiththeaidofitsname 1 <?php 2 $this->eventManager->trigger('event1'); Listing7.18 anobjectoftheZend\EventManager\Event typeistransferredtothelistener,theformeris automaticallygeneratedanddoesnotprovidemuchmorethananinternaldatastructureforthe generictransferofparametersaswellastheinformationontheso-called target ,i.e.theplace wheretheeventitselfistriggeredandthedesignationoftheeventitself.Ifonedesirestoma ke specificdataavailabletothelisteners,thiscanbedonewiththeaidofparameters: 1 <?php 2 $this->eventManager 3 ->trigger('event1', $this, array("key" => "value")); Listing7.19 Oncanthenaccessthedatastructureinthelistenerviatheeventobject sgetParams() method. Thus,practicallyeverythingthatonegenerallyneedscanalreadybeachievedwiththeaidoft he Zend\EventManager\Event.However,ifonedesirestoworkwithspecificdesignatorsandmemb er

variables,whichonecanaccessviagettersandsetters,insteadofthegenericdatastructure ,onehas theoption,asapplicationdeveloper,ofdeclaringone sowneventclass,whichcanbeinstantia ted andfilled onthefly ,asneeded.ZendFramework2itselfactivelyusesthismechanismfor,indeed , thereisaModulEvent,aViewEvent,andalsoaMvcEvent that,forexample,allowsdirectaccessto thefollowingobjects:

EventManager 1 <?php 2 protected $application; 3 protected $request; 4 protected $response; 5 protected $result; 6 protected $router; 7 protected $routeMatch; 8 9 // [..] Listing7.20 Onecanbestderiveone sowneventclassfromZend\EventManager\Event: 1 <?php 2 namespace Helloworld\Event; 3 4 use Zend\EventManager\Event; 5 6 class MyEvent extends Event 7 { 8 private $myObject; 9 10 public function setMyObject($myObject) 11 { 12 $this->myObject =

$myObject; 13 } 14 15 public function getMyObject() 16 { 17 return $this->myObject; 18 } 19 } Listing7.21 Toregistertheeventclass,onecaneithermakethisinformationknownbeforehandandtheninv oke theeventbyusingtheeventname 1 <?php 2 $this->eventManager->setEventClass('Helloworld\Event\MyEvent'); 3 $this->eventManager->trigger('getGreeting'); Listing7.22 oronetransfersthecorrespondingobjectinthescopeoftrigger():

EventManager 1 <?php 2 $event = new \Helloworld\Event\MyEvent(); 3 $event->setName('getGreeting'); 4 $this->eventManager->trigger($event); Listing7.23 Inthismanner,onecouldalsowritecompletelydistincteventclasses,whosenameisalsoalre ady predefined: 1 <?php 2 namespace Helloworld\Event; 3 4 use Zend\EventManager\Event; 5 6 class MyGetGreetingEvent extends Event 7 { 8 private $myObject; 9 10 public function __construct() 11 { 12 parent::__construct(); 13 $this->setName('getGreeting'); 14 } 15 16 public function setMyObject($myObject) 17

{ 18 $this->myObject = $myObject; 19 } 20 21 public function getMyObject() 22 { 23 return $this->myObject; 24 } 25 } Listing7.24 Theactuatorofthiseventwouldthenbelesssusceptibletoerrorsandthecode,evenmorecompa ct:

EventManager 81 1 2 3 <?php $event = new \Helloworld\Event\MyGetGreetingEvent(); $this->eventManager->trigger($event); Listing7.25

Modules InadditiontotheEventManager,the module conceptplaysadecisiveroleinZendFramework 2.Intherequestprocessinginthisframework,theModuleManager isimportantbecauseitindeed insuresthattheactivatedmoduleisalwaysconsideredandloaded,i.e.thattheapplicationi sfully functional. Theimportantthingisthattheapplication smodulesdonotformany closedentities .Theopposite istrue:Thefunctionsoftheindividualmodulesmergeinthescopeofmoduleloadingtoform the overallfunctionality oftheapplication.WehaveseenthattheModuleManager unitesthe configurationsofallmodulesinanapplication-wideconfigurationobject.Thisfacthassev eral consequences,whichone,asapplicationdeveloper,shouldbeawareof.All Services,whichmak e amoduleavailable,makeitavailableinanapplication-widemanner,i.e.forexample controll er plugins or viewhelpers ,aswellas,forexample,thecontrollersofonemodulearealsoalways availabletotheothermodules,thereisnotseparateControllerLoader foreachmodule. The Application module Ifonebeginstodevelopone sownapplicationonthebasisoftheZendSkeletonApplication,one runsintotheApplication modulealmostimmediately.Ifoneisawareoftheabove-mentioned factthatthefunctionalityofallmodulesyieldstheoverallfunctionalityoftheapplicatio nandif onerisksalookintowhatismadeavailablebythismodule,onequicklybecomesawareofthe purposeofthis standardmodule .Itconfiguresanumberofservicesandgeneratesseveralbasic functionswhicheveryapplicationmusthaveandwhoseownimplementationdoesnothavetobe performed.InadditiontothefactthattheZendSkeletonApplication onlyprovidesanexemplary controllerfortheapplication s startpage withitsrouteconfiguration,anumbersettingsforth e viewlayer arecontainedinthemodule.config.php oftheApplication.Thesearethenreadby theViewManagerFactory ortheViewManager andeitherutilizedbythelatterthemselvesorpassed ontootherobjects: 1 <?php 2 // [..] 3 'view_manager'

=> array( 4 'display_not_found_reason' => true, 5 'display_exceptions' => true, 6 'doctype' => 'HTML5', 7 'not_found_template' => 'error/404', 8 'exception_template' => 'error/index', 9 'template_map' => array( 10 'layout/layout' 11 => __DIR__ . '/../view/layout/layout.phtml', 82

Modules 83 12 'application/index/index' 13 => __DIR__ . '/../view/application/index/index.phtml', 14 'error/404' 15 => __DIR__ . '/../view/error/404.phtml', 16 'error/index' 17 => __DIR__ . '/../view/error/index.phtml', 18 ) 19 ) Listing8.1 Viadisplay_not_found_reason,theRouteNotFoundStrategy (thestandardmannerinwhich 404errorsarehandled)isinstructedtomakethereasonforthefactthataURLresultedina 404erroravailableforfurtherpresentationAswesawinthepreviouschapters,everyresult ofrequestprocessingisbasedonthe viewmodel ,whichcontainsboththeuserdataandthe (HTML)templatethataretobeusedforthispurpose.However,whena404erroroccurs,there isgenerallynoviewmodelgeneratedbyacontroller,simplybecausetherewasnoresponsible controller.TheRouteNotFoundStrategy thenensuresthata viewmodel isgeneratedsothat theprocessdescribedabovecanevertakeplace.TheZendSkeletonApplication alsoprovides anappropriatetemplateinview/error/404.phtml.Theproblemcausecanbeaccessedthere via$this->reason. not_found_template explicitlydeterminesthetemplatethisistobeusedin404situations. Asstandard,Frameworksearchesforthe404.phtml template.Itwouldthusbeadequateif oneweretocreateacorresponding404.phtml templateintheview directoryofoneofthe modules.Incidentally,ifonecreatesa404.phtml fileinmorethanonemodule,theonein themostrecentlyactivatedmoduleisused.Asyoumayremember,theconfigurationsofthe individualmodulesaremergedonloadingbytheModuleManager.Theerror/404 template

for404situationisdeterminedintheZendSkeletonApplication,whichinturnindicatestoa physicalfilebymeansofthefollowing template_mapconfiguration. 1 'error/404' => __DIR__ . '/../view/error/404.phtml' Listing8.2 display_exceptions functionssimilarlytodisplay_not_found_reason. Inthiscase,the ExceptionStrategy isanalogouslyinstructedtomakethe$this->display_exceptions availableintheappropriate viewmodel andthusinthedefinedtemplate.Inthismanner,a decisioncanbemadethereastowhetherornotfurtherdetailsoftheexceptionaretobe presented. Withexception_template thetemplatethatisimplementedinexceptionsituations i.e., alwayswhenanexceptionappearssomewhereinprocessingthatisnotnoticedandprocessed bytheapplicationdeveloper isexplicitlydetermined.Asstandard,Frameworksearchesfor theerror.phtml template.Itwouldthusbeadequateifoneweretocreateacorresponding

Modules 84 error.phtml templateintheview directoryofoneofthemodules.Theerror/index template forexceptionssituationsisdeterminedintheZendSkeletonApplication,whichinturn indicatestoaphysicalfilebymeansofthefollowing template_mapconfiguration: 1 'error/index' => __DIR__ . '/../view/error/index.phtml' Listing8.3 The doctypeviewhelper ,whichwewilllookatinmoredetaillater,isconfiguredvia doctype.The doctypedeclaration canbeoutputinatemplateoralayout(moreabout thisalsolater)viathe doctypeviewhelper withoutone shavingtoadditmanually.The depositedvalueisusedbytheViewHelperManagerFactory tofurnishthedoctype viewhelper withtheappropriateconfiguration. Thenthetemplatethatfunctionsasa visualframe isdefinedbymeansoflayout key. Asstandard,Frameworkexpectsthelayouttemplateunderthelayout/layout keyinthe templatemaporinanappropriatefileinthefilesystem.Ifnecessary,adifferenttemplate couldbedeclaredunderthelayout key. 1 'layout' => 'myLayout' Listing8.4 The application moduleisthusbasicallynothingspecial,butdoesreducetheinitialconfigura tion effortbytheapplicationdeveloper.Irecommendconsciousfurtherdevelopmentofthe applica tion moduleandthedepositionofalldefinitionsandconfigurationsthatalloramajorityofthemo dules haveincommonthere.Theoretically,onecanalsodesignanapplicationsuchthatdoesnotcont ain anymodulesotherthan application andinwhichallfunctionsaredirectlyrealisedthere.Insom e casesthisiscertainlyaneffectiveprocedure.

Module-dependent behaviour Duetothefactthat,afterthemoduleshavebeenloaded,therearenomoremodulesintherunning application,butinsteadthereisonlytheapplicationitselfwithallitsfunctionsandconfi gurations, whichhasbeencreatedbymergingtheindividualmodules.Consequently,thereisbasicallyno possibilitytoconfiguremodule-dependentbehaviouratalatertimebecausenoinformationo n thecurrentlyactivemoduleisavailable.However,ifonewantstoexecuteacertainaction,fo r examplewhenthecontrollerofaspecificmoduleisexecuted,onehastoresorttoaccessingthe SharedEventManager andatrick:

Modules 85 1 <?php 2 namespace Helloworld; 3 4 use Zend\ModuleManager\ModuleManager; 5 6 class Module 7{ 8 public function init(ModuleManager $moduleManager) 9{ 10 $sharedEvents = $moduleManager->getEventManager() 11 ->getSharedManager(); 12 13 $sharedEvents->attach( 14 __NAMESPACE__, 15 'dispatch', 16 function($e) { 17 $controller =

$e->getTarget(); 18 $controller->layout('layout/helloWorldLayout'); 19 }, 20 100 21 ); 22 } 23 } Listing8.5 Inthisexample,weconfigureadifferentlayoutforallpagesthataregeneratedbymeansof acontrolleractionofourHelloworld module. Toachievethis,weattachacallbackfunction tothedispatch event,butonlyforthosecontrollersthatfeelresponsiblefortheHelloworld identifier (thisvaluecorrespondstothemagicconstant__NAMESPACE__ inthiscase),i.e. for allthecontrollersoftheHelloworld module. Howexactlydoesthiswork? Inthiscase,we haveaspecialsituationinthatwedesiretoregisteralistener(inthiscaseintheformofa callbackfunction)withan EventManager thatdoesnotyetexistatthetimeofregistration.A controllerisindeedonlyassignedtoaneventmanagerofitsownatthetimeofitsgeneration. ThismeansthatwemustusetheSharedEventManager (seealsopreviouschapter).Andhereisthe trick:AcontrollerinZendFramework2isalwaysconfiguredsuchthat whenitisderivedfrom anAbstractActionController itconsultstheSharedEventManager whenitslistenerisalerted; amongotherthingsitdeclaresthecontrollernamespace(i.e. inthiscase Helloworld ). And weimplementedthecallbackininit() methodaboveforjustthispurpose;theformerisnow invoked,obtainsthedeterminedcontrollerviatheMvcEvent object,andthenstipulatesanother layouttemplatewiththeaidofthe controllerplugin .Thetemplateitselfmustnaturallyexisto rit

mustadditionallybemadeavailableviathetemplatemap.Otherwise,thisresultsinanerror.

Modules 86 Expressedinanotherway,whenoneregisterslistenersfortheeventsofanidentifierthatcor responds tothenamespaceofamodule,theyaretakenintoconsiderationbythecontrollersoftherespec tive module. Installing a third-party module OnofthemajorachievementsofVersion2isthefactthatone sownapplicationcanbeextendedina simplemannerwithouthavingtoprogramitoneself.Fortheinstallationofathird-partymodu le,a fewfundamentaloperationalstepsarerequiredanddependingonthemoduleafewmanipulation s maybenecessary. Sources for third-party modules TwogoodsourcesforobtainingmodulesaretheofficialZFmodulepageandGitHub4. In thiscontext,theZF-Commons-Repository5shouldbeparticularlymentioned. Butadditional repositoriescanbequicklyfoundbyusingGitHub ssearchfunction.Abriefnoteatthistime: notallavailablemoduleshavethequalitythatonesetsastheminimumstandardforone sown code.Manymodules,particularlythosewithverysmallversionnumbersstillcontainnumerou s bugsandsecuritygaps. Becausethefunctionsandconfigurationsofallmodulesaremerged bytheModuleManager inthescopeofmoduleloading,previouslyregisteredservices,etc.can suddenlynolongerbeavailable,forexample,becausetheywereunintentionallyoverwritten by otherimplementations.Itisthereforeimportanttousethird-partymoduleswithcareandtom ake consciousdecisionsfororagainstusingacertainmodule. Installation of a third-party module Therearemanyoptionsformakingadditionalmodulesavailableforone sownapplication.The

simplestwaytomanuallydownloadthemodulecodesandtocopythesourcefilesintoone sown application. Inthismanner,forexample,onecandownloadtheModuleZfcTwig6andcopy thecontentsofthearchiveintothemodule folderofone sownapplication(e.g.ina ZfcTwig directory).Ifonethenrememberstoactivatethemoduleintheapplication.config.php (addthe value ZfcTwig inthemodules section),themoduleisbasicallyreadytorun.However,ZfcTwig isamodulethat,inturn,stillrequiresthePHPlibrary Twig7 inordertobereadyforoperation. Twig isaTemplateEngine8fromthemakersofSymfonyFrameworks,oneofthemajorrivals http://modules.zendframework.com/ 4https://github.com 5https://github.com/ZF-Commons 6https://github.com/ZF-Commons/ZfcTwig/tarball/master 7http://twig.sensiolabs.org/ 8http://de.wikipedia.org/wiki/Template_Engine

Modules 87 ofZendFramework.Inthemeantime Twig hasslightlyoutstrippedtheoldtopdog Smarty. andisgladlyandalreadyfrequentlyused.Incidentally,onecanaskoneselfwhyoneneedsanot her templatelanguagelike Twig forthedevelopmentoftemplatesinPHPatall,especiallysincePHP itselfisatemplatelanguage.Afterall,thetypicalZendFramework phtml filesalsoconsistofo nly HTML,PHPcodeandafew viewhelpers asrequired,andZendFramework2,justlikeVersion 1,doesnotprovideanothertemplatingengine.Thesequestionsareallverycorrectandveryva lid. Thshortanswertothesequestionsis: No,wereallydonotneedanyadditionaltemplatingsystem , whosesyntaxhastobeadditionallylearnedandwhoseroughedgeshavetobeknown. Everything isgoodthewayitis.However,thereareapplicationsituationsinwhichanadditionaltemplat e enginecanbeveryhelpfulifonedesirestoprevent shoddywork frombeingimplementedin thetemplatesApurePHP-basedtemplateprovidesalltheoptionsofPHP,forexampleaccessto allfunctionsofthelanguagecore.Whenwepreventthatandintentionallywanttorestrictthe possibilitiesinthetemplatestoadefinedsetoffunctions,atemplateenginecanbehelpful al so beyondthe syntacticsugar ,theothermainreasonforitsuse. Butlet sgobacktheactualproblemofthedependenceoftheZfcTwig moduleonthe Twig library. TheZfcTwig modulethusfunctionsasakindof gluecode andensuresthat Twig s functionscan beusedinthecontextofaZendFramework2application.Therefore,wecannowdownload Twig next,deposititinthevendor directoryandconfiguretheautoloadingof Twig attheappropriate places.Thenitwillfunction. Becausethisprocessiserror-proneundtime-consuming,itisalsoadvisabletouse Composer4 for theinstallationofthird-partymoduleswheneverpossible,asalreadydoneduringtheinstal lation ofZendSkeletonApplication itself.Ifwedoitinthismanner,the Composer notonlyhandles thedownloadandmakingtheZfcTwig moduleavailable,butalsodealswithitsdependencies,i.e. the Twig library.Toachievethis,weextendthecomposer.json ofourapplicationintherequire section: 1 "require": { 2 "zf-commons/zfc-twig": "dev-master" 3}

Listing8.6 andrun Composer intheprojectdirectoryagain 1 $ php composer.phar update Composer nowdownloadstherequiredlibrariesandalsosetsuptheautoloadingforus.However, ifonenowtakesalookintothemodule directoryofone sownapplication,onediscoversthatno modulehasbeenaddedthere.Instead Composer depositsallofthelibrariesthatitloadsinthe vendor directoryasstandard.Intheapplication.config.php,thefollowingsectioncontrols .http://www.smarty.net/ 4getcomposer.org

Modules 88 1 <?php 2 // [..] 3 'module_paths' => array( 4 './module', 5 './vendor', 6 ), Listing8.7 thepathsinwhichtheapplicationexpectsinstalledmodules.vendor isthuscompletelyokayand evencompletelydifferentpathscanbeconfiguredatthislocation. Ifonenowopens/sayhello,onesees ajumbledmassofletters.Namely,thetemplatesthatweare currentlystillusinginourHelloworld applicationarebasedonPHPandarethusincomprehensible to Twig . Twig indeedrequires Twig -conformtemplatemarkup.Fortunately,ZfcTwig provides alternative Twig -compatibletemplatesfortheZendSkeletonApplication exemplarypages,which onecanuseinsteadofthe normal templatesoftheapplication module.Subsequenttoinstallation ofZfcUser by Composer ,onefindsthematvendor/zf-commons/zfc-twig/examples. One overwritesthecurrenttemplatesinmodule/application/view withthesefiles.Bydoingso,wehave alreadymadethelayoutandthetemplatesfortheerrorpages Twig -compatible.Onemustnow alsodothesamewiththetemplatesthatonehaswrittenoneself.Thenonecaninvoke/sayhello intheaccustomedmanner.Thetemplatesarenowprocessedby Twig . Configuring a third-party module Otherthird-partymoduleshaveastructuremorelikethatof MVC modules;theyprovide controllers,views,routes,etc.AgoodexampleofsuchamoduleisZfcUser4,whichmapsthe functionsofauserregistrationincluding Log-in/Log-out flows.Wewillalsolookatthismodul e againindetailatalatertime.

Forsimplicity ssake,let simagineforamomentthattheHelloworld moduleinthelastchapter wereathird-partymodule,whichwehadinstalledwith Composer .WiththeHelloworld module, theURL/sayhello wouldnowthushaveenteredoursystem.Butwhatwouldwedoifweactually desiredtomakethecorrespondingpageavailableundertheURL/welcome? Naturally,wecouldnowopenthemodule smodule.config.php andmakeourmodificationsthere directly. Butthatwouldmeanthatwehad bifurcated theHelloworld codebaseandthus improvements,whichhadbeenmadeonHelloworld bytheoriginalauthor(we repretendingthatit isathird-partymodule)couldnolongerbeimportedintooursystemaseasily.Wewouldoverwri te ourmodificationseverytimeweupdatedandwouldhavetomanuallyalterthedataagainafter everyupdate.That snotveryoptimal. 4https://github.com/ZF-Commons/ZfcUser

Modules 89 Instead,wewillfileourmodificationsinamoduleofourownandthusseparateitfromthe Helloworld codebase.Inthismanner,Helloworld retainsitsoriginalstructureandcouldbeupdated atanytimewithoutriskthatourmodificationswouldbelost. Changing URLs InordertochangetheURL/sayhello into/welcome,wecreateanewsubdirectoryandthusa newmoduleinthemodule directoryandnameitHelloworldMod.Inthismanner,wemakeitclear thatthisisourmodificationofanothermodule.ThereweonlyneedtohavetheModule.php in whosegetConfig()methodwepickupontheroutedefinitionforsayhello andoverwriteitwith /welcome. 1 <?php 2 namespace HelloworldMod; 3 4 class Module 5 { 6 public function getConfig() 7 { 8 return array ( 9 'router' => array( 10 'routes' => array( 11 'sayhello' => array( 12 'type' => 'Zend\Mvc\Router\Http\Literal', 13 'options' => array(

14 'route' => '/welcome' 15 ) 16 ) 17 ) 18 ) 19 ); 20 } 21 22 // [..] 23 } Listing8.8 Nowwehavetoensurethatthemodulewillbeactivatedandtodothisweextendtheapplication.c onfig.php inthemodules sectionappropriately.

Modules 90 1 <?php 2 return array( 3 'modules' => array( 4 'Application', 5 'Helloworld', 6 'HelloworldMod' 7 ), 8 'module_listener_options' => array( 9 'config_glob_paths' => array( 10 'config/autoload/{,*.}{global,local}.php', 11 ), 12 'module_paths' => array( 13 './module', 14 './vendor', 15 ), 16 ), 17 ); Listing8.9 WhenwenowinvoketheURL/sayhello,wenowreceive,asexpected,a404error.Incontrast, /welcome nowyieldstheaccustomedpage. Inthiscontext,wemakeuseoftwomechanisms.First,theconfigurationofallmodulesismerge d inthescopeoftheirloadingprocesssuchthatwecanquasialsomakeconfigurations forother modules inoneofthemodules. Second,theconfigurationofthemodulesisindividuallyand successivelyreadin.Thus,inourcase,theyarereadinasfollows:initiallythatoftheAppli cation, thenthatofHelloworld,andfinallytheconfigurationoftheHelloworldMod.

Inthismanner, configurationofapreviousmodulecanbeoverwrittenbythefollowingone,aswedidfortherou te withthedesignationsayhello,whichinitiallyisdefinedbyHelloworld andthenissubsequently reconfigured initsrouteoptionfromHelloworld to/welcome. Changing views Thepresentationofthird-partymodulescanalsobeadapted.Thisisgenerallymoreimportant andmorefrequentlyrequiredthantheadaptationofURLs.Toachievethis,theModule classof HelloworldMod isextendedbytheview_managersectionandtherethetemplateoftheindex action oftheindex controllersofHelloworld moduleispointedtotheappropriatelymodifiedtemplatein theHelloworldMod module.

'/view/helloworld-mod/index/index.phtml' Modules 91 1 <?php 2 namespace HelloworldMod; 3 4 class Module { 6 public function getConfig() 7 { 8 return array ( 9 'router' => array( 'routes' => array( 11 'sayhello' => array( 12 'type' => 'Zend\Mvc\Router\Http\Literal', 13 'options' => array( 14 'route' => '/welcome' ) 16 ) 17 ) 18 ), 19 'view_manager' => array(

'template_map' => array( 21 'helloworld/index/index' 22 => __DIR__ . 23 ) 24 ) ); 26 } 27 } Listing8.10 Ifwenowinvoke/sayhello,weseetherenderedpageunderutilisationofthenewtemplate.

Controller Concept & mode of operation Acontroller staskistoprocessaninteractionwiththeuserinterface,inourcasethusawebsit eor inawidersensewiththebrowseritself,respectively. TheMVCpattern(the C herestandsfor Controller )isincidentallyalreadyfairlyold.Itwasfirst usedattheendofthe1970sfortherealisationofuserinterfaceswhentheSmalltalk4programmi ng languagewasstillpopular.However,theuserinterfacesaswellastheprocessingoftheinter action wasgenerallyrestrictedtoaclosedsystematthattime.Thisisdifferentintheweb.Therethe user interfaceismanifestedinthebrowser,i.e.ontheclient,whereastheprocessingtakesplace onthe server.Thebrowsertransmitstheinformationonwhatisoccurringonitoronthewebsitewhich itis displaying,andhowtheuserisinteractingwithit.Iftheuserclicksonalink,thebrowsertra nsmits thefactthattheuserhasjustrequestedanotherwebpagetotheserver.OntheserverFramework nowdetermineswhichcontrollerinthesystemisstipulatedfortheprocessingofthisinterac tion (routing)andpassesthefurtherresponsibilityforthegenerationofaresponsetoit.Subseq uently, itsensuresthattherepresentationischangedontheuser sbrowser.Eitheracompletelynewpag e isloadedor,ifAJAXisbeingused,onlycertainpartsofthepagealreadybeingdisplayedwillb e updated. Normally,onecreatesacontrolleroneone sownforevery pagetype inthesystem.Forexample,in anonlineshoptherewouldbean IndexController forthehomepage,a CategoryController forthe presentationlistofoffereditemsinaspecificcategoryandan ItemController forthepresenta tion ofadetailpageoftheoffereditems.InZendFramework,acontrollerisfurthersubdividedint o so-called actions .Theactualprocessingthusdoesnottakeplaceinthecontrolleritself,buti nits actionsinstead.Inthiscontext,actionsarepublicmethodsofthecontrollerclass.Inaddit ionto theabove-mentionedcontrollers,anonlineshopnormallyprovidesashoppingcart,andthesy stem wouldthereforeprobablyhavea CartController .Inordertopresentthenormalinteractionswit h ashoppingcart,the CartController wouldbeequippedwithanumberofactions,amongthemfor examplethe showaction ,the addaction ,the removeaction ,the removeallaction ,etc.

Anappropriateprocedureistokeepthecodeinthecontrollersoractionsthemselves,respect ively, asleanaspossible.Intheidealcase,acontrollershouldonlyregisterandevaluatetheinter action andthendecidewhichlevertopullinordertogeneratethedesiredresult.Asarule,itusesser vices, whichallowaccessingofdatabases,sessionsorotherinformationorfunctions,todothis. Controller plugins Additionally,controllersfrequentlyuseso-called controllerplugins .Theyencapsulateint eractionrelevantcodethatisoftenrequiredandcanthusbereusedbydifferentcontrollers.

4http://de.wikipedia.org/wiki/Smalltalk 92

Controller 93 Frameworkcomesequippedwithalargenumberofplugins,whichcanbeimmediatelyusedinone s owncontrollers.Allpluginsinheritthesamebasicclass,whichensuresthattherespectivep lugin alwaysalsohasaccesstothecontrollerinwhichitisjustbeingused.Thisispracticalinmany situations;insomeofthemitisindispensible,aswewillseeinthefollowing. Redirect TheredirectpluginmakesitpossibletotransferaclienttoanotherURL:Inthiscontext,this redirect isnotperformed internallybyFramework ,butratherbyfeedbacktotheclient,whichthenactive ly invokesthenewURL.Toachievethis,Frameworksendsaresponsewitha302HTTPstatuscode4 andtheclient,inmostcasestheuser sbrowser,sendsanewrequesttothespecifiedURL. Inthiscontext,atargetURLcanbeentereddirectly. 1 <?php 2 $this->redirect()->toUrl('http://www.meinedomain.de/zielseite'); Listing9.1 OraURLcanbegeneratedonthebasisoftheroutesavailableinthesystem.Thus,ifwedesireto forwardsomethingtotheURLhttp://localhost:8080/sayhello,wecanalternativelyallowth e URLtobegeneratedusingtherouteconfiguration: 1 <?php 2 $this->redirect()->toRoute('sayhello'); Listing9.2 Inthiscase,sayhello isthenameoftheroutewhichmodule.config.php: 1 <?php 2

// [..] 3 'router' => array( 4 'routes' => array( 5 'sayhello' => array( wespecifiedinourmoduleconfigurationfile, 6 'type' => 'Zend\Mvc\Router\Http\Literal', 7 'options' => array( 8 'route' => '/sayhello', 9 'defaults' => array( 10 'controller' => 'Helloworld\Controller\Index', 4http://de.wikipedia.org/wiki/HTTP-Statuscode

Controller 94 11 'action' => 'index', 12 ) 13 ) 14 ) 15 ) 16 ) Listing9.3 But,whathappensif,theURLisnotastaticstringasinourexample,butisdynamicallycompile d,as, Thereisalsoasolutioninthiscase:Inadditiontothenameoftheroute,thedynamiccomponent s, whicharethencombinedtoformtheURL,canbetransferredasadditionalparameters.Inalater chapter,wewillagainexaminetheroutingmechanismindetailandwillthenalsoagainreconsi der thecontentsoftheredirectplugin.Atthattime,wewillseeexactlyhowatargetURLiscreated fromthedynamiccomponents. TheredirectplugintakesadvantageofthefactthatwhenacontrolleraResponse typeobjectreturns, theotherwisecustomaryfurtherprocessingisskipped.Appropriately,nofurtherrendering ofthe viewoccursinthiscase. Inorderfortheredirectplugintofunctionproperly,itisnecessarythatthecontrollerinwh ichitis usedhastheMvcEvent objectbecausetheRouter thatisrequiredtogenerateaURLonthebasisof theroutenameisobtainedfromit.Customarily,onealwaysderivesone sowncontrollerfromthe FrameworkbasicclassAbstractActionController,inwhichalargenumberofcontroller-rele vant interfaceshasalreadybeenimplementedandthus,forexample,ensuresthatMvcEvent isavailable: 1 <?php 2 namespace Zend\Mvc\Controller; 3 4 // use [..]; 5 6 abstract class AbstractActionController

implements 7 Dispatchable, 8 EventManagerAwareInterface, 9 InjectApplicationEventInterface, 10 ServiceLocatorAwareInterface 11 { 12 // [..] 13 } Listing9.4 Framework sAbstractActionController implementstheInjectApplicationEventInterface and thusallowstheControllerLoader,theservicethatisresponsibleforinstantiatingandinvo king forexample,inhttp://www.zalando.de/nike-performance-free-run-3-laufschuh-black-r elfecting-silver-platinum-

Controller 95 thecontrollerappropriatefortherouteafterrouting,toinjecttheMvcEvent object.Incidentally, theimplementationoftheEventManagerAwareInterface ensuresthatanEventManager isavailable tothecontroller,andtheimplementationoftheServiceLocatorAwareInterface ensuresthatthe controllercanaccesstheServiceManager andthusalso,theapplication sservices. PostRedirectGet Thispluginprovidessupportinaspecialredirectprobleminconnectionwithformsthatarese ntvia POST.Iftheuserinitiatesareloadofthepageafterpreviousdispatchofaformtoitsconfirma tion page,thePOSTdataareagaintransmittedtotheserverinmostcases,andinuntowardcases, undesireddoublepurchases,bookings,etc.aremade.Toavoidthisproblem,aconfirmationpa ge, whichisdisplayedsubsequenttoaPOST-basedtransaction,shouldnotbegeneratedinthesame step.Instead,inresponsetothesuccessfulPOSTrequest,theserverinitiallysendsa301/30 2HTTP statuscodeandthenforwardsittoaconfirmationpage.Thelatteristhenrequestedbythebrow ser viaaGETandthuscanalsobeinvokedseveraltimeswithoutrisk.ThePostRedirectGet-Plugin ensuresthatonedoesnothavetodevelopthismechanismoneself. Forward TheredirectpluginsrealisereroutingintheclientviatheappropriateHTTPheader,whereas the forwardpluginallowstheinvocationofanothercontrollerfromwithinacontroller. Actually, thedesignation forward isnotexactlycorrect.Theforwardplugindoesnotactuallyforward anything,butinsteadmerely dispatches anothercontroller.Ifoneexaminesthecontrollersin ZendFramework2againclosely,thefollowingbecomesclear:acontrollerisreallynothingmo re thanaclasswhichhasadispatch() methodatitsdisposal,whichexpectsaRequest typeobject andaResponse typeobject;andwhichthusmeetsthe[conditionsfor]aDispatchableInterface. WhattherespectivecontrollerdoeswiththeRequest sothattheformercansubsequentlyreturnan appropriateResponse isleftuptocontrolleritself. Accordingly,withtheforwardpluginonlythedispatch()

methodofacontrollerisrun,andit returns asisusuallythecasewhenacontrollerisexecuted anobjectoftheViewModel typeas result.Indeed,whatnowhappenstotheresultislefttothecontrollerdiscretion.Ifonedesi resto emulatea controllerforward ,thefollowingone-linershowshowtodoit: 1 <?php 2 return $this->forward()->dispatch('Helloworld\Controller\Other'); Listing9.5 Inthiscase, other allegoricallyrepresentsanothercontroller,whichwasalsopreviouslyals omade knowntothesysteminthescopeofmoduleconfiguration.Inthiscase,theViewModel,whichthe OtherController generates,isreturned1-to-1bytheoriginallyinvokingcontroller,whichnolonger generatesaViewModel itself.

Controller 96 However,theforwardplugincanalsobeusedtoaggregatetheresultsofseveralothercontroll ers. Butwewilldiscussthislaterindepthinthe concept&modeofoperation sectionofthechapter onthe view topic. Incidentally,ifonedesirestoinvokeaspecificAction byanothercontroller,thiscanberealisedas follows: 1 <?php 2 return $this->forward() 3 ->dispatch( 4 'Helloworld\Controller\Other', 5 array('action' => 'test') 6 ); Listing9.6 Inthismanner,otheradditionalinformationcanalsobetransferredtotheinvokedcontrolle r: URL WiththeaidoftheURLplugin,aURLcanbegeneratedonthebasisofapreviouslydefinedroute undunderdeclarationoftheroutedesignationaswellasadditionalparametersasrequired: 1 <?php 2 $url = $this->url()->fromRoute('routenbezeichnung', $params); Listing9.7 Params

Allowssimpleaccesstorequestparameters,whichwouldotherwisebedifficulttoaccess.Act ually, thePOSTparametersinacontrollerwouldhavetobeaccessedasfollows: 1 <?php 2 $this->getRequest()->getPost($param, $default); Listing9.8 Byusingtheplugin,accesscanbeperformedsomewhatmoreelegantlyeveniftheroutineisnot muchshorter:

Controller 97 1 2 <?php $this->params()->fromPost('param', $default); Listing9.9 Ifoneomitstheparameterdesignation,alloftheparametersarereturnedintheformofan associativearray: 1 <?php 2 $this->params()->fromPost(); Listing9.10 Theimportantthingistodeclarethecorrect 1 <?php 2 $this->params()->fromPost(); 3 $this->params()->fromQuery(); 4 $this->params()->fromRoute(); 5 $this->params()->fromFiles(); 6 $this->params()->fromHeader(); Listing9.11 Layout source.Thefollowinginvocationsarepossible: Viathislayoutplugin,thelayouttobeusedcanbeconfiguredatanytime: 1 <?php 2 $this->layout('layout/mein-layout');

Listing9.12 Thelayoutpluginis,forexample,helpfulifonedesirestouseanalternativelayoutinthesco peof acertainaction. Flash messenger Messagesforausercanbetransportedoverapagechangeviatheflashmessenger.Technically speaking,toachievethis,asessionisgeneratedontheserversideandtherespectivemessage thus persistsbriefly.Amessageforausercanbeaddedtoaflashmessengerasfollows:

Controller 98 1 <?php 2 $this->flashMessenger()->addMessage('Der Datensatz wurde gelscht'); Onthetargetsideonecanretrieveanddisplaythemessage(orevenseveralmessagesifsrequir ed) asfollows: 1 <?php 2 $flashMessenger = $this->flashMessenger(); 3 4 if ($flashMessenger->hasMessages()) { 5 $return['messages'] = $flashMessenger->getMessages(); 6 } Listing9.13 Writing one s own controller plugin InZendFramework2,a controllerplugin isreallynothingspecial.Indeed,itisjustamoreor less normalclass ,whichactuallyonlyexpectsa getter anda setter andwhichisinjected viatheControllerinwhichtherespectivecontrollerpluginisnowbeingapplied.However,th e implementationofthismethodcanbeavoidedifonederivesone sowncontrollerpluginfromthe Zend\Mvc\Controller\Plugin\AbstractPlugin class.Thenonemustmerelyfillthe__invoke() methodwithlifeinordertobeabletoeasilyusethe ControllerPlugin inacontroller. 1 <?php 2 3 namespace Helloworld\Controller\Plugin; 4 5 use Zend\Mvc\Controller\Plugin\AbstractPlugin; 6 7 class CurrentDate

extends AbstractPlugin 8 { 9 public function __invoke() 10 { 11 return date('d.m.Y'); 12 } 13 } Listing9.14 Thiscontrollerpluginensuresthatthecurrentdateisgenerated(admittedlyonedoesnotrea lly needacontrollerplugintodothis).TheclassdefinitionislocatedintheHelloworld moduleunder src/Helloworld/Controller/Plugin/CurrentDate.php.Theindex actionoftheIndexController isadaptedasfollowssothatitusesCurrentDate andprovidestheviewwiththisresult:

Controller 99 1 <?php 2 // [..] 3 public function indexAction() 4{ 5 return new ViewModel( 6 array( 7 'greeting' => $this->greetingService->getGreeting(), 8 'date' => $this->currentDate() 9) 10 ); 11 } Listing9.15 CurrentDate canbeveryeasilyinvokedasifitwereamethodofthecurrentcontroller(via $this).However,inorderforthisinvocationtofunction,wemustmakeCurrentDate knownto theControllerPluginManager beforehand.Wecandothiseitherindermodule.config.php ofthe moduleorinitsModule class: 1 <?php 2 // [..] 3

public function getControllerPluginConfig() 4{ 5 return array( 6 'invokables' => array( 7 'currentDate' 8 => 'Helloworld\Controller\Plugin\CurrentDate' 9) 10 ); 11 } Listing9.16 Onceregistered,this controllerplugin canbeinvokedinallcontrollers.Incidentally,Curre ntDate isnotrestrictedtotheHelloworld module,butalsocanbeusedincontrollersofothermodules. Whetherornotthisisreallyappropriatecertainlydependsonthespecificcase.

Views Concept & mode of operation Theview,alsooftenreferredtoasthe presentationlayer inthescopeoftheMVCpattern,is responsibleforthepresentationoftheprocessingresults. Fromthemomentofthesuccessful executionofacontroller(oraction)uptothepresentationofthefinalresultontheuser sbrow ser, theresultpresentationassumesanumberofdifferentformsandissubjecttoalargenumberof transformationprocesses,someofthemstillontheserverandthensomeofthemontheclient. Theinitialrepresentationoftheprocessingresult,theso-called viewmodel ,generatesacont roller oftheapplication.The viewmodelinitiallydoesnotcontainanypresentationinformation(fo r exampleHTML),butinsteadmerelycontainsthe basicdatastructure intheformofkeyvalue pairs: 1 <?php 2 public function indexAction() 3{ 4 return new ViewModel( 5 array( 6 'event' => 'Beatsteaks', 7 'place' => 'Berlin', 8 'date' => $this->currentDate() 9) 10 ); 11 }

Listing10.1 Alternatively,simplyaPHParraycanalsobereturned. 1 <?php 2 // [..] 3 public function indexAction() 4{ 5 return array( 6 'event' => 'Beatsteaks', 7 'place' => 'Berlin', 8 'date' => $this->currentDate() 9 ); 10 } 100

Views 101 Listing10.2 ViaFramework sCreateViewModelListener,whichreactstothedispatch resultofanActionControllers asstandard,thegeneratedarrayissubsequently,automaticallytransformedintoaViewMode l. However,theViewModel ismorethanjustthecontainerforthepayloaddata.Italsocontainsthe informationonthetemplatewithwhichthedataarelatertobeunited.Thisinformationisalso retrospectivelyaddedbyFrameworkandtheInjectViewModelListener;thisalsooccursinthe scopeofthedispatch eventofanActionController. ViewModels canalsobenested. Thisfactisalsoveryhelpfulinpracticalapplicationandis appropriatefordistributingthecreationofasinglepageacrossseveralcontrollers.The For ward controllerplugin,whichwearealreadyfamiliarwith,canbeusedtodothis,i.e.inordertono tonly runasinglecontrolleroraction,butindeedanentiresequenceofcontrollersoractions,res pectively, withinthescopeofrequestprocessingasrequired.Inthismanner,anumberofViewModels canbe generatedandprocessedatthesametime. 1 <?php 2 // [..] 3 public function indexAction() 4{ 5 $widget = $this->forward() 6 ->dispatch('Helloworld\Controller\Widget'); 7 8 $page = new ViewModel(

9 array( 10 'greeting' => $this->greetingService->getGreeting(), 11 'date' => $this->currentDate() 12 ) 13 ); 14 15 $page->addChild($widget, 'widgetContent'); 16 return $page; 17 } Listing10.3 InthisindexAction weinitiallyinsurethattheWidgetController (oritsindexAction,respectively) isrun.ThereturnedViewModel iscachedinthe$widget variable.In$page saving,thereference iscachedintheactualViewModel oftheindexAction.However,beforeitisreturned,addChild attachesthegenerated viewmodel oftheWidgetController tothis,forsimplicity ssakelet scall it, primaryViewModel .Inthismanner,onecanaccesstherenderingresultoftheviewmodelof theWidgetController viathewidgetContent key:

Views 102 1 // [..] 2 <sidebar> 3 <?php echo $this->widgetContent ?> 4 </sidebar> 5 // [..] Listing10.4 Layouts Ifoneknowshowtonest viewmodels ,onecanquicklydeducethemodeofoperationoflayouts. Butperhapsweshouldfirsttakeastepbackwards:TheideabehindlayoutsistoacquireHTML code,whichisrequiredbymanyorevenallcontrollersandactions(forthepresentwe llreducei t tothisforsimplicity ssake;but,ofcourse,datastructuresbeyondHTMLcanalsobegeneratedw ith ZendFramework2).TheseincludeMETAtags,thebasicHTMLframework,referencestoCSSfiles andthelike. Inatechnicalsense,alayoutisreallynothingmorethanaViewModel thatreferencesaViewModel, whichwasgeneratedbyacontrolleraction,as child justasintheexamplegivenabovethe ViewModel ofacontrolleractionreferencedthatofanothercontrolleraction.Toaccessthecontroller action sViewModel withinthelayouttemplate,Frameworkautomaticallyregistersthecontent key ;justaswemanuallygeneratedthewidgetContent keyinthepreviousexample.Thus,inthelayout templatetheresultofacontrolleractioncanbeaccessedinthefollowingmanner: 1 <html> 2 <head> 3 <title>Meine Seite</title> 4

</head> 5 <body> 6 <?php echo $this->content; ?> 7 </body> 8 </html> Listing10.5 ThelayouttemplatethatisautomaticallyconsultedbyFrameworkcanbecontrolledviathe controllerplugin,whichwasdiscussedinthepreviouschapter,orinsteadviaa viewhelper ,whi ch wewillalsoexamineinmoredetailinthefollowing. View Helper ItisfrequentlynecessarytofurtherprocesstheViewModel s datainthecourseofrenderingorto generateadditionaldata.Thus,forexample,inthecontextofsubjectssuchas navigation , META

Views 103 tags ,etc.therearemanyrecurrentview-relatedtaskswhichonecanmasteronceandthenmake re-usableintheformof viewhelpers .Asstandard,ZendFramework2providesalargenumber ofready-to-useviewhelpers,whichonecanimmediatelyuse.Inaddition,onecanwriteone sown viewhelpers. Writing a view helper of one s own InZendFramework2,aviewhelperisreallynothingspecial;itisjustamoreorless normalclass , whichactuallyonlyexpectsa getter anda setter andwhichisinjectedviatheviewinwhich therespective viewhelper isnowbeingapplied.However,onecanavoidimplementingthese methodswhenonederivesone sownviewhelpersfromtheZend\View\Helper\AbstractHelper class.Thenonemustmerelyfillthe__invoke() methodwithlifeinordertobeabletoeasilyuse theviewhelper. 1 2 <?php 3 4 namespace Helloworld\View\Helper; 5 6 use Zend\View\Helper\AbstractHelper; 7 8 9 10 11 12 13 class { } DisplayCurrentDate extends AbstractHelper public function __invoke() { return date('d.m.Y'); } Listing10.6 This viewhelper ensuresthatthecurrentdatecanbeoutputinaview(admittedlyonedoesnot reallyneedaviewhelpertodothis).TheclassdefinitionislocatedintheHelloworld moduleunder

src/Helloworld/View/Helper/DisplayCurrentDate.php.Theindex.phtml file,i.e.theviewofthe index actionintheIndex controller,isadaptedasfollowsandnowusesthisviewhelper: 1 2 <h1><?php <h2><?php echo echo $this->greeting; ?></h1> $this->displayCurrentDate(); ?></h2> Listing10.7 DisplayCurrentDate canbeveryeasilyinvokedasifitwereamethodofthecurrentview(via $this). However,inorderforthisinvocationtofunction,wemustmakeDisplayCurrentDate knowntotheViewHelperManager beforehand.Wecandothiseitherindermodule.config.php ofthemoduleorinitsModule class:

Views 104 1 <?php 2 public function getViewHelperConfig() 3 { 4 return array( 5 'invokables' => array( 6 'displayCurrentDate' 7 => 'Helloworld\View\Helper\DisplayCurrentDate' 8 ) 9 ); 10 } Listing10.8 or 1 <?php 2 'view_helpers' => array( 3 'invokables' => array( 4 'displayCurrentDate' 5 => 'Helloworld\View\Helper\DisplayCurrentDate' 6 ) 7 ) 8 // [..] Listing10.9 Onceregistered,thisviewhelpercanbeinvokedinallviews.Incidentally,DisplayCurrentD ate isnotrestrictedtotheHelloworld module,butalsocanbeusedintheviewsofothermodules.

Whetherornotthisisreallyappropriatecertainlydependsonthespecificcase.

Model Whatexactlyisthe model ofanapplicationinreality?Viewandcontrollerarerelativelyclearl y definedwithrespecttotheircontentsandarewellformulatedintheirformandfunctionin Framework,whereasthisisnotasobviousformodel.Thisisprimarilyduetothefactthatamode l cantakeonaverydifferentstructuredependingontheapplicationtypeandthesituation.For example,wearealreadyacquaintedwiththeviewmodel.Isthisthemodelthatisencapsulatedi n MVC ?No,butitdoeshavesomesimilaritywiththetypeofmodelthatistobeconsidered. Let sapproachthisinitiallyfromthedefinition.Wikipedia44definesamodelasfollows: 1.Representation Amodelisalwaysamodelofsomething,namelyfigure,representationof anaturalorartificialoriginal,whichitselfcanalsobeamodel. 2.Abridgement Amodelgenerallydoesnotincludealltheattributesoftheoriginal,butonly thosewhichappearrelevanttothemodellerormodeluser. 3.Pragmatism Modelsdonotexplicitlycorrespondpersetotheiroriginals.Theyfulfiltheir replacementfunctiona)forcertainsubjects(forwhom?),b)withinacertainintervaloftime

(when?)andc)underrestrictiontocertainconceptualorphysicaloperations(whatfor?). Amodelisthusalwaysasimplifiedrepresentationofreality,whichattheappropriatetimeis adequatefortherespectiveapplicationpurpose.Thus,theviewmodelisasimplifiedreprese ntation ofawebpage,restrictedtothedatatobedisplayed,informationonthestatusofcertainactio n elements(buttonactiveorinactive,boxopenorclosed,etc.)aswellasthereferencetoatemp late withwhichthesedataaretobesubsequentlylinked.Inthescopeoftherendering,theviewmode l becomesafurther,ifyouwill morehighlydeveloped andarepresentationofrealitythatisclose r toreality:Thefullygeneratedmarkupisusedforthesubsequentrepresentationofawebsite. This isreturnedtothecallingprogram,andthebrowserdevelopsanothermodel,theDOM45,fromit. Onthebasisofthe DocumentObjectModel(DOM) ,whichappropriatelyalreadybearstheterm model initsname,colouredpixelsappearmagicallyonthescreeninaconcludingstep.Andeven thisresultisactuallyagainonlyamodel.Andeventhisexplanationitselfisonlyamodelbeca

use, asIseeit,Ihaveomittedirrelevantinformationanddetailsatthistime. Butbeforethisdiscussionbecomesexcessivelyphilosophical,weshouldnowturntothe model of MVC .Inastricterdefinition,thismodelistheimageoftherealitytouchedbytheapplication , i.e.thespecialiseddomainwhicheverythingdependson.Forexample,theimageofe-commerce processesinonlineshopsormarketplaces,themanagementofcustomers,contactsandinciden tsin CRMsystemsordocuments,authorsandrubricsinCMSsystems.Andthosearejustafewofthe possibleexamples!Thisspecialisedmodelisthusessentiallycompletelyapplication-spec ificand ultimatelycannotbegenericallydefinedwithanymoreprecision.Accordingly,thestructur eof thespecialisedmodelanditstechnicalexpressionisnotspecifiedinacertainmanner.Ifone looks 44http://de.wikipedia.org/wiki/Modell 45http://de.wikipedia.org/wiki/Document_Object_Model 105

Model 106 forstandardsinthefieldof enterpriseapplications ortypicalwebapplications,forwhichZen d Framework2isindeedprimarilyintended,thedefinitionoftheDomain-DrivenDesign46prese nts itself.There,thefollowingunitsofanapplicationapplytoaspecialisedmodel: Entites Repositories ValueObjects Aggregates Assoziationen Business-Services Business-Events Factories Thisisnotintendedtobeanintroductionintotheworldof Domain-DrivenDesign ,particularly not,becauseitonlyrepresentsonewayofrealisingamodelofone sownapplication,andtherefo r doesnotpossessanyuniversalvalidity.Nevertheless,someconstructsof Domain-DrivenDesi gn correlatewiththoseofZendFramework,andthusitisworthwhiletotakeacloserlook. Entities, repositories & value objects An entity representsadefiniteobjectinasystem,forexampleacustomeroranorderinashop system.Asarule,definitudeisachievedbyusingIDs,forexampleadefinitecustomernumber. Sometimesitisalsopossibletocombineanobject scharacteristicssuchthatunambiguityexis ts. However,thisis,e.g.,extremelydifficulttoachieveotherwisefornaturalpersons.Thus,f orexample, inacitylikeBerlin,MunichorHamburgthereareseveralpeoplewhohaveexactlythesame name. Someofthemadditionallyevenhavetheirbirthdaysonthesameday. However,these areconceptionallydifferent entities ,whichonemustabsolutelydistinguishbetween. Thedesignationentityalsoagainappearsparticularlyinconnectionwithso-called persiste nce , i.e. storageofsuchobjectsindatabasessuchthattheycansurvivearequest. Thus,theORM SystemDoctrine247usesthedesignation entity ,forexample,forallobjectsthatarestoredand administeredinadatabase. A repository makesitpossibletoaccesstheentitiesstoredinadatabase.

Dependingonthe implementation,arepositoryactsasacontainerfortheSQLrequestforaspecificentitytype . However,sometimesitisalsomore,aswewillsoonsee. Incontrasttoan entity ,a valueobject hasnoidentityofitsown.Forexample,twoinstances of\DateTime class,thePHPstandardobjectfortherepresentationofdataandtime,whichwere generatedwiththeidenticaltimestampandthushavethesamecharacteristics,aresimplyide ntical. 46http://de.wikipedia.org/wiki/Domain-Driven_Design 47doctrine-project.org

Model 107 Differentiationisconceptionallyunnecessary.Asarule,thereisnoneedforavalueobjectt ohave persistence,althoughthiswouldalsobefundamentallyconceivablefromatechnicalpointof view. Anentityorevenavalueobjectcanberepresentedbymeansofasimpleclass: 1 <?php 2 class User 3 { 4 private $name; 5 private $email; 6 private $password; 7 8 public function setEmail($email) 9 { 10 $this->email = $email; 11 } 12 13 public function getEmail() 14 { 15 return $this->email; 16 } 17 18 public function setName($name)

19 { 20 $this->name = $name; 21 } 22 23 public function getName() 24 { 25 return $this->name; 26 } 27 28 public function setPassword($password) 29 { 30 $this->password = $password; 31 } 32 33 public function getPassword() 34 { 35 return $this->password; 36 } 37 } Listing11.1

Model 108 AswewillseelaterinthescopeofZend\Db andZend\Form,entities,butalsovalueobjectsthatare representedinthisway,canbeextremelyusefulatdifferentplacesintheapplication. Business services & factories Wehavealreadydiscussedservicesandfactories.Aservicecanbegeneratedbyafactory orby Zend\Di,aswewillseeinthenextchapter.Functionsthatrefertothetechnicaldomain,butca nnot beunequivocallyclassifiedasentitiesorshouldnotbeclassifiedassuch,arereferredtoas b usiness services .Inashopsystem,onecould,forexample,definea CurrencyConversion service,which isgivenavalueobjectwiththeproperties value and currency togetherwiththeinformation concerningthecurrencyintowhichtheconversionistobepreformed.Theconversionresultis returnedintheformofanewvalueobject.Essentially,itwouldalsobepossibletounderstand the conversionaspartofavalueobject,andtoimplementthefunctionintheappropriateclass let s callit price (whichisdefinedonthebasisof value and currency)inthiscontext.However,the correspondingvalueobjectmustthenhavecognizanceofthecurrenciessupportedbythesyste m, whichhavepresumablybeendepositedasentitiesinadatabase.Whetheronereallywantsthisl ink isquestionable. Thefunctionthusconcernsnotonlyonetypeofobjectinthesystem,butinsteadseveralsimult aneously. The CurrencyConversionService wouldthereforebeagoodcandidateforrepresentation as businessservice andwouldthereforealsobeclassedasamodeloftheapplication. So-called technicalservices or infrastructureservices ,whichprovideanon-specialisedservi ce, forexamplethephysicaldispatchingofemailsorSMSmessages,loggingfunctions,etc.Asaru le, theyarelessconcernedwiththespecialisedobjectsoftheapplicationanddonotprovideany specialisedfunctionthemselves.ZendFramework2alreadyprovidesalargenumberof technica l services ,whichcanbeusedindependentlyoftherespectiveexpertise.Thus,incontrasttothe businessservices , technicalservices arenotconsideredtobeamodeloftheapplication. Business events Inthepreviouschapter,wealreadyencounteredtheeventsystemandFramework smanyeventsin thescopeofrequestprocessing,forexample:route,dispatch

undrender.WithgetGreeting we havealreadyconceivedaneventofourownthatwasnotoftechnical-functionalnature,butins tead primarilyaidsinkeepingthebusinessprocesseswithintheapplicationflexible.Thus,weca nadapt workflows,addorremoveactions,andchangethesequenceofexecutionatanytime.Inaddition , thesespecialisedeventsarealsopartofthemodeloftheapplication.Inashopsystem,forexa mple, typicaleventswouldbe,e.g.,placinganitemintheshoppingcart,theconcludedcheckoutort he registrationofareceivedreturnontheappropriateadministrationconsolebytheshopemplo yees.

Routing Introduction Behindthemechanicsofthe routing isthefundamentalideathatitisnolongernecessaryto beableassignaURLexactlytoanexisting(PHP)file.Instead,URLscanbefreelyselectedandt he appropriateprocessinglogic generallyacontrollerandanaction arelinkedtothem.Notjustsi nce thedisciplineofsearchengineoptimisation48(SEO)existsandtheonlinemarketercannowen sure thattheapplication sURLsarealsodesignedfor thosewhospeakGoogle andcontainthecorrect freewords,isonehappyabouttheacquiredflexibilityintheformulationofURLs.Particular ly forlocalisedapplications,inwhichURLsaretobegeneratedindifferentlanguages,onequic kly reachesone slimitsifonedoesnothavesophisticatedrouting.ZendFramework2,aswasalready thecaseforitspredecessor,providesaveryhigh-performanceandflexible routing solution,w hich hasbeencompletelyreprogrammed,performsbetterthanbeforeandalsohasbeenmorecoherent ly conceived. Incidentally,matchingaURLtoacontrollerisalreadyaspecialcasebecauseitdoesnotneces sarily havetobeaURLthatisconsultedastheinitialvalueformatching.Aswewillseelaterinthesco pe ofZend\Console,freelydefinable commands canalsobeassignedtocontrollers.Thisis,e.g.,v ery practicalforcronjobs.Wewillgointomoredetailaboutthislater.Forsimplicity ssake,Iwil lstick withtheURLasinitialvalueinmyfurtherexplanationsofthesubjectofrouting,evenwhenthe se statementshavegeneralvalidityandarenotfundamentallyrestrictedtoURLs. Definition of routes ThespecificmappingofaURLtoacontrollerisdesignatedasa route . IftheURL X is requested,thecontrollerperforms Y anditsaction, ofmodule.config.php lookslikethis: 1 <?php 2 // [..]

3 'router' => array( 4 'routes' => array( 5 'sayhello' => array( Z .Asimpleroutedefinitioninthescope 6 'type' => 'Zend\Mvc\Router\Http\Literal', 7 'options' => array( 8 'route' => '/sayhello', 9 'defaults' => array( 10 'controller' => 'helloworld-index-controller', 48http://de.wikipedia.org/wiki/Suchmaschinenoptimierung 109

Routing 110 11 'action' => 'index', 12 ) 13 ) 14 ) 15 ) 16 ) 17 // [..] Listing12.1 WhenthepathoftheinvokedURLis/sayhello,thehelloworld index controller anditsindex actionarerun.Notmuchmore,butalsonoless.Afairlycomplexapplicationwillbringalonga largenumberofsuchdefinitions.Wewillgointomoredetaillater. Inthiscase,thehelloworld-index-controller keywasdefinedasanaliasfortheactualcontroller inthescopeofthedi configuration(therewillbemoreinformationonZend\Di furtheroninthis book): 1 <?php 2 // [..] 3 'alias' => array( 4 'helloworld-index-controller' 5 => 'Helloworld\Controller\IndexController' 6)

Listing12.2 ThefollowingoccursinFramework:TherouteracceptstheRequest and reads throughthelistof alldeposited routes .Thisoccurseitherintheformofa stack (i.e.theroutethatwasaddedlast isthefirstonecheckedforcorrespondence,andthefirstone,last)ortheroutesaredeposite dintree form.Inanycase,correspondenceresultsinthegenerationandreturnofanobjectoftheRoute Match type,andtheworkoftheRouters isfinished.ThevaluestransferredintotheRouteMatch objectby theRouter, among others the controller configured for this route, are then evaluated in the further processing of the Request. This is how the Dispatcher knowswhich controlleritistobeinstantiated. Matching test Frameworkprovidesanumberofoptionsfordefiningsimpleand/orcomplex,forexamplenested , matchingrules.

Routing 111 Checking the path Wehavealreadyseenthesimplestvariant,theso-calledLiteral route,above.Acharacterstring thathastocorrespondexactlytothepathoftherequestedURLisdefined.Ifthisisthecase,th e routefunctionsandthenecessaryleversaremovedonthebasisofitsconfiguration. TheRegex route,inwhichthepathoftheURLmustfitaregularexpression,isamoreflexibleoption. Let stakeaconcreteexampleagain:Anonlineshoeshopcoulduse,forexample,thefollowingURL pathfordetailpages,wherebythefrontpartembodiesthe slug oftheproductdesignation,where as thenumberfollowingitrepresentstheitemnumber:/converse-as-ox-can-sneaker-black/37 3682726 .ARegexroutethatwouldworkforthistypeofURLlookslikethis: 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'detailPage' => array( 6 'type' => 'Zend\Mvc\Router\Http\Regex', 7 'options' => array( 8 'regex' => '/(?<slug>[a-zA-Z0-9_-]+)/(?<id>[0-9]+)', 9 'spec' => '/%slug%/%id%', 10 'defaults' => array( 11 'controller' =>

'helloworld-index-controller', 12 'action' => 'index', 13 ) 14 ) 15 ) 16 ) 17 ) 18 // [..] Listing12.3 Thecontrollercanthenaccessthevalueforthe slug ,forexample,asfollows: 1 <?php 2 $this->getEvent()->getRouteMatch()->getParam('slug'); Listing12.4 Ifwenowtakeanotherexactlookattheregularexpression/(?<slug>[a-zA-Z0-9_-]+)/(?<id> [0-9]+): Thepathhastobeginwitha / .Subsequently,anyarbitrarynumberofdigits,letters,theunderli ne andthehyphencanfollow,butatleastone plussign (+)mustoccuratthislocation.Everything

Routing 112 thatislocatedbetweenthefront / andtherear / ,istermedthe slug andisaccordingly subsequentlymadeavailableviatheRouteMatch object.Afterthemandatorysecondslash( / ), oneormoredigitsbetween0and9mayfollow.Thisnumberistermedthe id andisalsomade availableintheRouteMatch object. We lltakeanotherlookatthe spec objectlater.Itisusedforthe returntrip ,i.e.thegeneration ofaURLonthebasisofjustthisroute. Asanalternativetothis,onecouldalsohavemetthechallengebymeansofaSegment route: 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'detailPage' => array( 6 'type' => 'Zend\Mvc\Router\Http\Segment', 7 'options' => array( 8 'route' => '/:slug/:id', 9 'defaults' => array( 10 'controller' => 'helloworld-index-controller', 11 'action' => 'index', 12 ) 13 )

14 ) 15 ) 16 ) 17 // [..] Listing12.5 Wecandispensewiththe spec optioninthiscasebecauseitresultsfromtheroute.Inaddition, optionalsegmentscanbedefinedwhenoneplacestheminbrackets,e.g. [/:id] .Thecontrollerca n thenagainaccessthevaluefortheslug,forexample,asfollows: 1 <?php 2 $this->getEvent()->getRouteMatch()->getParam('slug'); Listing12.6 TheSegment routecanalsoberealisedbymeansofchecksbaseonregularexpressionswhenone incorporates constraints ,butinthiscaseoftheindividualsegments:

Routing 113 1 <?php 2 // [..] 3 'constraints' => array( 4 'slug' => '[a-zA-Z0-9_-]+', 5 'id' => '[0-9]+' 6) 7 // [..] Listing12.7 Checking hostname, protocol, etc. WiththeaidofaHostname route,itisalsopossibletocheckfor 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'detailPage' => array( 6 'type' =>

'Zend\Mvc\Router\Http\Hostname', 7 'options' => array( 8 'route' => 'blog.meinedomain.de', 9 'defaults' => array( ahostname. 10 'controller' => 'helloworld-index-controller', 11 'action' => 'index', 12 ) 13 ) 14 ) 15 ) 16 ) 17 // [..] Listing12.8 Ifonehasdrawnupthisrule,thepathstatementintheURLnolongerplaysanyroleatall.Every URLthatcontainsthehostnameblog.meinedomain.de willbemappedtothespecifiedcontroller. ViatheScheme route,onecancheckwither https isbeingused verypracticalwhenonewantsto makecertaincontents,forexamplethe customeraccount ,onlyaccessibleviahttps. ViatheMethod route,itispossibletousethe mode inwhichtheHTTPrequestwassent(suchas POST , GET or PUT )asthebasisforthecheck.Withtheaidofthisroute,REST4.,itispossible, e.g.,tostructurewebservices(However,aswewillseelater,withtheAbstractRestfulContr oller thereisamuchbettersolution.)ortoseparateformdisplayandprocessinginanelegantmanne

r: 4.http://de.wikipedia.org/wiki/Representational_State_Transfer

Routing 114 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'blog' => array( 6 'type' => 'Zend\Mvc\Router\Http\Literal', 7 'options' => array( 8 'route' => '/contactform', 9 ), 10 'child_routes' => array( 11 'formShow' => array( 12 'type' => 'method', 13 'options' => array( 14 'verb' => 'get', 15 'defaults' => array(

16 'controller' => 'form-controller', 17 'action' => 'show', 18 ) 19 ) 20 ), 21 'formProcess' => array( 22 'type' => 'method', 23 'options' => array( 24 'verb' => 'post', 25 'defaults' => array( 26 'controller' => 'form-controller', 27 'action' => 'process', 28 ) 29 ) 30 ) 31 ) 32 ) 33 ) 34 ) 35 // [..] Listing12.9

Inthiscase,wheneveroneisdealingwithaGETrequestontheURL/contactform,theshow action oftheform-controller willberunandwhenoneisdealingwithaPOSTrequest,theprocess actiontakesover.Otherwise,onewouldhavetoimplementaungainly isPost() logicintheaction itselfinordertobeabletodifferentiatebetweentheinitialrepresentationoftheform(GET )and itsprocessing(POST)(inthepracticesectionwewilldevelopalovelysolutionfortheproces singof

Routing 115 formsonthebasisofspecialcontrollertypes). Combining rules Somerulesmakenosensewhenoneconsidersthemoutofcontext.Tobeabletocheckforthe hostnamewithouthavingtoconsiderthepathinformationinanywayisusuallyveryhelpful.Fo r thisreason,rulescanbecombinedwithoneanotherintheformoftrees,asonecouldalready seeinthelastexample. Let sconsideranotherexample:We rerunninganonlineshopunder www.meinedomain.de andthecorrespondingblogunderblog.meinedomain.de.Themostrecenttest reportistobedisplayedundertheURLblog.meinedomain.de/testberichte;ontheotherhand,

theURLwww.meinedomain.de/testberichte doesnoexist,thismustthusgeneratea404error.To achievethis,aHostname routeandaLiteral routecanbecombined: 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'blog' => array( 6 'type' => 'Zend\Mvc\Router\Http\Hostname', 7 'options' => array( 8 'route' => 'blog.meinedomain.de',

9 'defaults' => array( 10 'controller' => 'helloworld-index-controller', 11 'action' => 'index', 12 ) 13 ), 14 'child_routes' => array( 15 'tests' => array( 16 'type' => 'literal', 17 'options' => array( 18 'route' => '/testberichte', 19 'defaults' => array( 20 'controller' => 'helloworld-index-controller', 21 'action' => 'tests', 22 ) 23 ) 24 ) 25 ) 26 ) 27 )

28 ) 29 // [..]

Routing 116 Listing12.10 OnlywhentheURLpathis/testberichte andatthesametimethehostnameisblog.meinedomain.de, doesthisroutebecomeeffective.Thisisdefinedinthechild_routes section.Achild_route can, inturn,againhaveitsownchild_routes ,suchthatanentiretreestructureiscreated.Ifthereare severalchild_routes onthesame level ,theyaretobeconsideredasalternatives: 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'blog' => array( 6 'type' => 'Zend\Mvc\Router\Http\Hostname', 7 'options' => array( 8 'route' => 'blog.meinedomain.de', 9 'defaults' => array( 10 'controller' => 'helloworld-index-controller', 11 'action' => 'index', 12 ) 13 ), 14 'child_routes'

=> array( 15 'tests' => array( 16 'type' => 'literal', 17 'options' => array( 18 'route' => '/testberichte', 19 'defaults' => array( 20 'controller' => 'tests-controller', 21 'action' => 'show', 22 ) 23 ) 24 ), 25 'testArchive' => array( 26 'type' => 'literal', 27 'options' => array( 28 'route' => '/testberichte/archiv', 29 'defaults' => array( 30 'controller' => 'tests-controller',

31 'action' => 'archive', 32 ) 33 ) 34 ) 35 ) 36 )

Routing 117 37 ) 38 ) 39 // [..] Listing12.11 Inthiscase,theroutesfortheURLpaths/testberichte and/testberichte/archiv havebeen definedindecentlyofeachother;however,bothproceedundertheassumptionthatthehostnam eis blog.meinedomain.de. Generation of a URL Ifonedesirestogeneratealinkinanapplicationthatreferstoaninternalpage,whichalsoca nbe reachedviaadefinedroute,onecangeneratethe href attributethatisrequiredfortheURLin asimplemanner.TheadvantageincomparisontothemanualgenerationoftheURListhatone cansubsequentlyadapttheformofaURLatacentrallocationwithouthavingrepeatedtheentir e applicationandindividuallyadapteverylink. Let stakealookatthe(Regex)routedefinitionagain. 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'detailPage' => array( 6 'type' => 'Zend\Mvc\Router\Http\Regex', 7 'options' => array( 8 'regex' => '/(?<slug>[a-zA-Z0-9_-]+)/(?<id>[0-9]+)',

9 'spec' => '/%slug%/%id%', 10 'defaults' => array( 11 'controller' => 'helloworld-index-controller', 12 'action' => 'index', 13 ) 14 ) 15 ) 16 ) 17 ) 18 // [..] Listing12.12 TheinterestingpartofthegenerationofaURLonthebasisofthisroutedefinitionisthe spec . That swhereonedefineshowaURLforthispageisschematicallycomposed.Onceithasbeen defined,aURLcanbegeneratedviatheurl controllerplugin:

Routing 118 1 <?php 2 //[..] 3 $href = $this->url() 4 ->fromRoute( 5 'detailPage', 6 array("slug" => "adidas-samba-sneaker", "id" => 34578347) 7 ); Listing12.13 Inthiscontext,itisimportanttousethecorrectnamefortherouteandconsignalltherequire dURL components. Standard routing Standard-Routing ,whichprovidessomecomfortatthecostofflexibility,hasbeenknownand appreciatedsinceZendFrameworkVersion1. Standardrouting ensuresthatthepathintheURL ismappedontoamodule,acontrollerandanactionasstandard.The/blog/entry/add URLwould thusinitiatetheAdd actionintheEntry controlleroftheBlog module. Thisstandardroutingwas baked (hardcoded)intotheMVCmechanicsofFrameworkVersion1, thisisnolongerthecaseinVersion2:Standardroutinginthisformactuallydoesnotexistany more,butitcanbeeffortlesslyemulatedifonereadtheprevioussectionsattentively.Weena ble standardroutingfortheHelloworld moduleviathefollowingdefinition: 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array(

5 'helloworld' => array( 6 'type' => 'Literal', 7 'options' => array( 8 'route' => '/helloworld', 9 'defaults' => array( 10 '__NAMESPACE__' => 'Helloworld\Controller', 11 'controller' => 'Index', 12 'action' => 'index', 13 ), 14 ), 15 'may_terminate' => true, 16 'child_routes' => array( 17 'default' => array(

Routing 119 18 'type' => 'Segment', 19 'options' => array( 20 'route' => '/[:controller[/:action]]', 21 'constraints' => array( 22 'controller' => '[a-zA-Z][a-zA-Z0-9_-]*', 23 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', 24 ), 25 'defaults' => array( 26 ) 27 ) 28 ) 29 ) 30 ) 31 ) 32 ) 33 // [..] Listing12.14 However,thecorrespondingcontrollermuststillbeinitiallymadeknowninthesystem(thatw as notthecaseinFrameworkVersion1): 1 <?php 2 //

[..] 3 'controllers' => array( 4 'invokables' => array( 5 'Helloworld\Controller\Widget' 6 => 'Helloworld\Controller\WidgetController', 7 'Helloworld\Controller\Index' 8 => 'Helloworld\Controller\IndexController' 9 ) 10 ) Listing12.15 Inthismanner,theURLs/helloworld und/helloworld/widget/index,forexample,cannowbe invoked. Herearetwomorenotesonroutedefinition:Themay_terminate configurationinformstheRouter, whichindeedevaluatestherules,thateven/helloworld alone,consideredindividually,represents avalidrouteandthatitisnotabsolutelynecessarytoalsoconsultthedefinitionsofchild_r outes. However,ifoneomitsmay_terminate (andthussetsitimplicitlytofalse),/helloworld wouldno longerfunction,butonlythe/helloworld/widget/index would(ifwesticktotheabove-mentioned exampleinthiscase).Andwiththeaidofthe'__NAMESPACE__' option,thefullyqualifiedclass

Routing 120 nameisusedforthecontrollerthatwascutoutoftheURLsuchthatitfitsthedepositedcontrol ler configuration.Otherwise,forexample,therewouldbeanunsuccessfulsearchforacontrolle r,which isknownunderthename Widget ,forthe/helloworld/widget/index URL.However,itscorrect name, which was automatically correctly generated in this manner, is indeed just Helloworld\Controller\Widget. Creative routing: A/B tests Withabitofcreativity,routingcanbeusedforpurposeswhichoneThus,onecan,forexample,r outepartofaURL straffic,inthiscaseanalternativeimplementation. 1 <?php 2 // [..] 3 'router' => array( 4 'routes' => array( 5 'detailPage' => array( 6 'type' => 'Zend\Mvc\Router\Http\Literal', 7

'options' => array( 8 'route' => '/beispielseite', 9 'defaults' => array( wouldnotinitiallythinkof. (theoretically)halfofit,onto 10 'controller' => 'helloworld-index-controller', 11 'action' => rand(0,1) ? 'original' : 'variation' 12 ) 13 ), 14 ) 15 ) 16 ) 17 // [..] Listing12.16 Inthiscase,theremustnowbeanoriginal actionundavariation actioninthehelloworld-index-controller, whichontheonehandcontainstheinitialversionofapageandontheotherhandahypotheticall y improvedvariantofthispage.Thelatterversionshouldproveitssuperiorityinthescopeofa nA/B test5,forexample,intheconversionrate5.IncollaborationwithaWebtracking5tool,suchasth e freeGoogleAnalytics5,evencomplexA/Btestscenarioscanbesetupandevaluated. 5http://de.wikipedia.org/wiki/A/B-Test

5http://de.wikipedia.org/wiki/Konversion_(Marketing) 5http://de.wikipedia.org/wiki/Web_Analytics 5http://www.google.com/intl/de/analytics/

Dependency injection Introduction Theideabehindthe dependencyinjection(DI) isthefollowing:Anobjectdoesnotprocure additionalobjects,whichitneeds,butinsteadtheyaregiventoitfromanoutsidesource,thu s inamannerofspeakingtheyare injected .Thelargestadvantageofthisprocedureisthefactthat thedependentobjectitselfmustnolongerknowexactlywhatitisdependenton.Theinformatio n onthisdependencyisextractedfromthedependentobjectandmanagedseparately.Thisallows theapplicationdevelopertoconsignalternativeimplementationstothedependentobjectin certain situations,forexample,whenconductingunittexts. WiththeuseofourGreetingServiceFactory wehavethusalreadypracticed dependencyinjection becauseweextractedtheinformationconcerningthedependencyoftheGreetingService on theLoggingService fromtheGreetingService itself.Ifweonceagaindisregardthecodeforevent triggeringandprocessing,thecodeisrepresentedasfollows:TheGreetingServiceFactory, which ensuresthatthedependenceoftheGreetingService ontheLoggingService isresolved,precedes theGreetingService: 1 <?php 2 namespace Helloworld\Service; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class GreetingServiceFactory implements FactoryInterface 8{ 9 public function createService(ServiceLocatorInterface

$serviceLocator) 10 { 11 $greetingService = new GreetingService(); 12 13 $greetingService->setLoggingService( 14 $serviceLocator->get('loggingService') 15 ); 16 17 return $greetingService; 18 } 19 } Listing13.1 121

Dependencyinjection TheGreetingService simplyaccessestheinjectedLoggingService withouthavingconsideredthat italreadyhastheLoggingService atitsdisposal.Inthiscontext,itbanksonthefactthatjustthis servicewasmadeavailablebeforeitwastobeused,regardlessofwhoprovidedit: 1 <?php 2 namespace Helloworld\Service; 3 4 class GreetingService 5 { 6 private $loggingService; 7 8 public function getGreeting() 9 { 10 $this->loggingService->log("getGreeting ausgefuehrt!"); 11 12 if(date("H") <= 11) 13 return "Good morning, world!"; 14 else if (date("H") > 11 && date("H") < 17) 15 return "Hello, world!"; 16 else

17 return "Good evening, world!"; 18 } 19 20 public function setLoggingService($loggingService) 21 { 22 return $this->loggingService = $loggingService; 23 } 24 25 public function getLoggingService() 26 { 27 return $this->loggingService; 28 } 29 } Listing13.2 TheLoggingService thenaccordinglycarriesoutitswork:

Dependencyinjection 1 <?php 2 namespace Helloworld\Service; 3 4 class LoggingService 5{ 6 public function log($str) 7{ 8 // code for logging 9} 10 } Listing13.3 Whenwenowwanttoavoidthat,inthescopeoftheunittests,weclogupourlogfilewith many spurious accesses,wecaninjectaFakeLoggingService intotheGreetingService during theexecutionofthetest,whichontheonehandallowstheGreetingService toperformitswork asusualwithoutproblemsandontheotherhandtosubsequentlysimplydiscardtheuselesslogs . 1 <?php 2 namespace Helloworld\Service; 3 4 class FakeLoggingService 5{ 6 public function log($str) 7{ 8

return; 9} 10 } Listing13.4 WecantheninjecttheFakeLoggingService duringthetest: 1 <?php 2 namespace Helloworld\Service; 3 4 class GreetingService extends \PHPUnit_Framework_TestCase 5{ 6 public function testGetGreeting() 7{ 8 $greetingService = new GreetingService(); 9 $fakeLoggingService = new FakeLoggingService(); 10 $greetingService->setLoggingService($fakeLoggingService)

Dependencyinjection 11 $result = $greetingService->getGreeting(); 12 $greetingSrv = $serviceLocator$this->assertEquals(/* [..] */); 13 } 14 } Listing13.5 AsamatterofformandtoensurethattheGreetingService isreallyalwaysprovidedwitha logging-likeservice ,aninterfacecanbeemployed: 1 <?php 2 namespace Helloworld\Service; 3 4 interface LoggingServiceInterface 5 { 6 public function log($str); 7 } Listing13.6 TheLoggingServiceInterface wouldthenbeimplementedbyboththe real LoggingService 1 <?php 2 namespace Helloworld\Service; 3 4 class LoggingService implements LoggingServiceInterface 5 { 6 public function log($str) 7

{ 8 // code for logging 9 } 10 } Listing13.7 andthe fakeimplementation :

Dependencyinjection 1 <?php 2 namespace Helloworld\Service; 3 4 class FakeLoggingService implements LoggingServiceInterface 5 { 6 public function log($str) 7 { 8 return; 9 } 10 } Listing13.8 Ifanappropriate setter isused,areferencetoatypecanalsobeplacedintheGreetingService in ordertoforcethePHPinterpretertosetanobjectoftherighttype: 1 <?php 2 // [..] 3 public function setLoggingService(LoggingServiceInterface $loggingService) 4 { 5 return $this->loggingService = $loggingService; 6 } 7 // [..] Listing13.9 Incidentally,theuseofthenativedate functionintheGreetingService makesunittesting extremelydifficult.

Inthefollowingchapter,whenweexamineunittestingindetail,wewill considerapossiblesolutionforthisspecificexample. Zend\LogIncidentally,aswewillsoonseethat,asapplicationdevelopers,wedonothavetoex pendtheefforttoprogramaloggingfunctionalityourselvesatall:ZendFramework2alreadyp rovidesaveryflexibleloggingimplementationintheformofZend\Log.Wewillgointomoredet ailonthislater.

Dependencyinjection 126 Zend\Di for object graphs Initially,onecanbestimagineZend\Di asagenericfactory,whichonealsodesignatesasa DependencyInjectionContainer . Incontrast,ourGreetingServiceFactory wasveryspecific becausewehadalreadyresolvedthedependencyontheLoggingService explicitlyandmanually. Alternatively,wecanalsochargeZend\Di theresolutionofthisdependency:. 1 <?php 2 namespace Helloworld\Service; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class GreetingServiceFactory implements FactoryInterface 8 { 9 public function createService(ServiceLocatorInterface $serviceLocator) 10 { 11 $di = new \Zend\Di\Di(); 12 13 $di->configure(new \Zend\Di\Config(array( 14 'definition' => array( 15 'class' => array(

16 'Helloworld\Service\GreetingService' => array( 17 'setLoggingService' => array( 18 'required' => true 19 ) 20 ) 21 ) 22 ), 23 'instance' => array( 24 'preferences' => array( 25 'Helloworld\Service\LoggingServiceInterface' 26 => 'Helloworld\Service\LoggingService' 27 ) 28 ) 29 ) 30 )); 31 32 $greetingService = $di->get('Helloworld\Service\GreetingService'); 33 return $greetingService; 34 } 35 }

Dependencyinjection Listing13.10 Beforewetakealookattheactualconfiguration,let sfirstmakeafewadditionalimprovements :We reallydonotneedtheGreetingServiceFactory anymorebecauseZend\Di performstheworkfor usasagenericimplementationofthefactory.Instead,werelocatetheconfigurationcodedir ectly intheModule classoftheHelloworld-module.Inthecourseofthis,wetakeadvantageofthefact thatFrameworkhasalreadyautomaticallymadeaninstanceofZend\Di availabletousasaservice intheServiceManager. We can reach this instance in two different ways. Either we request the DependencyInjectormanually from the ServiceManager : 1 2 3 4 <?php // [..] $di = $serviceManager->get('DependencyInjector'); // [..] Listing13.11 andobtaintheGreetingService via 1 2 3 4 5 <?php // [..] $di = $serviceManager->get(`DependencyInjector`);

$greetingService = $di->get('Helloworld\Service\GreetingService'); // [..] Listing13.12 oralternativelywemakethingsalittleeasierforourselves,andusethe fallbackmechanism of theServiceManager:IftheServiceManager cannotprovidearequestedservicebecauseitsimply doesnotknowanythingabouttheserviceinquestion,itaskstheDependencyInjector onceagain whetheritcanperhapsprovidehelpandmaketherespectiveserviceavailable.Andinourcase, itcould. Consequently,wedispensewiththeGreetingServiceFactory entirelyandshiftthe configurationcodeforZend\Di directlyintodiemodule.config.php oftheHelloworld module: 1 2 3 4 5 6 7 8 9 <?php return array( 'di' => array( 'definition' => 'class' array( => array( 'Helloworld\Service\GreetingService' => 'setLoggingService' => array( 'required' => true ) array(

Dependencyinjection 128 ) 11 ) 12 ), 13 'instance' => array( 14 'preferences' => array( 15 'Helloworld\Service\LoggingServiceInterface' 16 => 'Helloworld\Service\LoggingService' 17 ) 18 ) 19 ), 'view_manager' => array( 21 'template_path_stack' => array( 22 __DIR__ . '/../view' 23 ) 24 ), 25 'router' => array( 26 'routes' => array( 27 'sayhello' => array( 28 'type' => 'Zend\Mvc\Router\Http\Literal', 29 'options'

=> array( 'route' => '/sayhello', 31 'defaults' => array( 32 'controller' => 'Helloworld\Controller\Index', 33 'action' => 'index', 34 ) 35 ) 36 ) 37 ) 38 ), 39 'controllers' => array( 'factories' => array( 41 'Helloworld\Controller\Index' 42 => 'Helloworld\Controller\IndexControllerFactory' 43 ), 44 'invokables' => array( 45 'Helloworld\Controller\Widget' 46 => 'Helloworld\Controller\WidgetController' 47 ) 48 ), 49 'view_helpers' => array( 'invokables' =>

array( 51 'displayCurrentDate'

Dependencyinjection 52 => 'Helloworld\View\Helper\DisplayCurrentDate' 53 ) 54 ) 55 ); Listing13.13 Theuppersectionwiththedi keyisnewandcorrespondstothecodewhichweoriginallyhad intheGreetingServiceFactory. Frameworksearchesforanentrybelowdi andtransfersthe configuration,ifpresent,totheDependencyInjector,whichisindeedmadeavailableautoma tically. WecannowremovethegetServiceConfig() methodfromtheModule classofHelloworld.We don tneeditanymore,ifwemakeasmalladjustmentintheIndexControllerFactory andensure thatweusethefullyqualifiednameHelloworld\Service\GreetingService whenwerequestthe service(uptonowwehadusedadesignationthatdidnotcorrespondtotheclassinthiscase. However,toavoida namecollision betweenservicesofdifferentmodules,itisadvisabletoalwa ys usethefullyqualifiedclassnameforthedesignationofservices): 1 <?php 2 namespace Helloworld\Controller; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class IndexControllerFactory implements FactoryInterface 8 { 9 public function createService(ServiceLocatorInterface $serviceLocator) 10 { 11

$ctr = new IndexController(); 12 $serviceLocator = $serviceLocator->getServiceLocator(); 13 14 $greetingSrv = $serviceLocator->get( 15 'Helloworld\Service\GreetingService' 16 ); 17 18 $ctr->setGreetingService($greetingSrv); 19 return $ctr; 20 } 21 } Listing13.14 Whathappensnow?TheServiceManager receivestherequestfortheGreetingService andbecause itdoesnothaveagoodanswerathand(weindeedremovedtheserviceconfigurationandmoved

Dependencyinjection everythingintotheDependencyInjector),itnowconsultstheDependencyInjector.Thelatte rhelps theformer,instantiatestheGreetingService andintheprocessensuresthattheLoggingService is onstandbyandthattheGreetingService ismadeavailable.TheServiceManager nowfinallyhas justthisoperationalserviceonhandandhappilyreturnsittothecallingprogram. Hereisanothershortpieceofinformation:WesometimesusethetermServiceManager andat othertimes,thetermServiceLocator.Thiscanbeabitconfusing.TheServiceLocator isan interface,whereastheServiceManager isitsspecificimplementation,whichFrameworkprovides directly.Thus,theServiceManager istheonethatperformstheactualwork.Asisthecaseat manylocationsinFramework,theunderlyingideaisthatonecouldindeedconsiderreplacing theServiceManager withanotherimplementation.InorderthattherestofFrameworkandthe applicationbaseonitwouldstillfunction,thisalternativeimplementationmustconformto the templateoftheServiceLocator interface.Thus,whenoneusesthetermServiceLocator,oneis reallyalwaysdiscussingitsspecificstandardimplementation,the ServiceManager. Let sreturntothedetailsoftheDIconfigurationmentionedabove.Initially,oneinformsZend \Di thatthereisaHelloworld\Service\GreetingService class,whichhasasetLoggingService() methodatitsdisposalandthatinanycaseadependencymustbemadeavailable: 1 <?php 2 // [..] 3 'definition' => array( 4 'class' => array( 5 'Helloworld\Service\GreetingService' => array( 6 'setLoggingService' =>

array( 7 'required' => true 8) 9) 10 ) 11 ) 12 // [..] Listing13.15 Butwhichoneismeantexactly?Inthiscase,theRuntimeDefinition isbroughtintoplay:Zend\Di activelysearchesforitinthecorrespondingmethoddeclarationoftheGreetingService

Dependencyinjection 131 1 2 3 4 5 6 7 <?php // [..] public function setLoggingService(LoggingServiceInterface $loggingService) { return $this->loggingService = $loggingService; } // [..] Listing13.16 andindependentlylocatestheinformationastothetypeofobjectthatistobeinjectedinthis case. Ifwehaddeclaredaspecificclass,asasortofhint,insteadoftheLoggingServiceInterface, we wouldalreadybefinished:Zend\Di wouldinstantiatetheappropriateclassandtransferittothe GreetingService viasetLoggingService() assoonasthelatterwasrequested. That sit!However,inourcase,Zend\Di doesnotfindadefiniteclassasreferencetoatype,but ratherapointertoaninterface.Consequently,nowwemustprovideadditionalinformation,w hich actuallyguidesZend\Di totheclassthatistobeusedinthiscase: 1 2 3 4 5 6 7 8 9 <?php // [..] 'instance' => array( 'preferences' => array( 'Helloworld\Service\LoggingServiceInterface'

=> 'Helloworld\Service\LoggingService' ) ) // [..] Listing13.17 Thisconfigurationstatesthefollowing: WhenyoucomeupontheHelloworld\Service\LoggingS e(anddon tknowhatyoushoulddo),usetheHelloworld\Service\LoggingService .It sasimple asthat! NowtheHelloworld\Service\GreetingService canbeindirectlyrequestedviathe ServiceManager: rviceInterface 1 2 3 4 <?php // [..] $greetingSrv = $serviceLocator->get('Helloworld\Service\GreetingService'); // [..] Listing13.18 Afewchaptersago,wealsocreatedafactoryofourownfortheIndexController oftheHelloworld modulesothatwecouldresolveitsdependenceontheGreetingService.WecanalsouseZend\Di inthismannerasneeded,butonlywhenwehaveinitiallyactivated( unlocked )therespective controllerforloadingviaZend\Di:

Dependencyinjection 132 1 <?php 2 return array( 3 'di' => array( 4 'allowed_controllers' => array( 5 'helloworld-index-controller' 6 ), 7 'definition' => array( 8 'class' => array( 9 'Helloworld\Service\GreetingService' => array( 10 'setLoggingService' => array( 11 'required' => true 12 ) 13 ), 14 'Helloworld\Controller\IndexController' => array( 15 'setGreetingService' => array( 16 'required' => true 17 ) 18 )

19 ) 20 ), 21 'instance' => array( 22 'preferences' => array( 23 'Helloworld\Service\LoggingServiceInterface' 24 => 'Helloworld\Service\LoggingService' 25 ), 26 'Helloworld\Service\LoggingService' => array( 27 'parameters' => array( 28 'logfile' => __DIR__ . '/../../../data/log.txt' 29 ) 30 ), 31 'alias' => array( 32 'helloworld-index-controller' 33 => 'Helloworld\Controller\IndexController', 34 ), 35 ) 36 ), 37 // [..] 38 ); Listing13.19 Thefollowingadjustmentsofthemodule.config.php arenecessarysothattheloadingofthe

controllerviaZend\Di alsofunctions:

Dependencyinjection 133 TheIndexControllerFactory isremovedfromthecontroller section.Weindeeddonot desiretouseitanymore,butinsteadwanttogeneratetheIndexController viaZend\Di in thenearfuture. Thealias sectionintheinstance sectionbelowdi hasbeenrecentlyadded. Sinceno backslashesareallowedasaliasesinZend\Di,weusethehelloworld-index-controller inthiscase.Previously,wewerestillabletousetheHelloworld\Controller\Index and consequentlycouldrefertothefactory.Thisisunfortunatelynolongerpossible,butthat doesn treallymakeanydifference. Thesayhello routehasalsobeenadaptedsuchthatthecontrolleraliashelloworld-index-controller isused. Theallowed_controllers sectionhasbeennewlyaddedandacquiresthehelloworld-index-controller value,i.e. thealiasofthecontrollerthatwewanttoloadviaZend\Di.Ifweforgetto whitelist thecontrollerinthismanner,itcannotbeloadedviaZend\Di evenifalltheother configurationsarecorrect thisisasafetyfeature. Lastbutnotleast,wemustnowensurethatthedependenceontheGreetingService isresolved. Thefactorywhichhadensuredtheresolutionofthedependenceonthe GreetingService uptonowhasbeendisabled. ToallowZend\Di assumethistask,an entryforHelloworld\Controller\IndexController hasbeenaddedinthedi >definition >class section,viawhichweinformZend\Di thatthesetGreetingService() methodin theIndexController mustmanditorilybeinvoked.Whenwenowequipthatmethodofthe Helloworld IndexController withareferencetoatype,Zend\Di canagaindeterminefor itselfwhichclasshastobeinjectedinthiscase: <?php//[..]publicfunctionsetGreetingService(\Helloworld\Service\GreetingService$ service){$ this->greetingService=$service;}

Listing13.20 Incidentally,theControllerManager hasaZend\Di instanceofitsown,viawhichtherespective controllercannowbeobtainedifrequired. WecouldnowthuseliminateallofourfactoriesviaZend\Di.Ifonespinsthisthreadfurther,o ne couldbasicallydispensewithfactoriescompletelyandsetupalltheobjectgraphswithallth eir dependenciesdescriptivelywithoutwritingtheotherwisenecessaryinitialisationcode. Zend\Di shouldreallyalsobecomethecentralpivotalelementofZend\Mvc andmakeitsindividual factoriesredundant. However,ifonetakesacloselookatFramework srequestprocessing,it becomesapparentthatZend\Di ispracticallyneverusedandnearlyallservicesaregenerated viatheirownfactories.TheServiceManager andtherespectivefactorieshaveassumedthetasks intendedforZend\Di atmanylocations.Thereasonsforthiswerenotreallyofatechnicalnature, butratherofastrategicone:toensurethatZendFramework2remainseasyfornewcomersto learn.AndZend\Di unavoidablybringsagreatdealofhidden Magic intoplay.Basically,the situationisalsosimilartoone sowndecisionfororagainstZend\Di andfactories,respectively. Bothapproachesareprovenmethodsofachievingaloosecouplingoftheindividualplayers,an d thustodevelopasystemthatalsoremainssustainablytestable,maintainableandextensible .The

Dependencyinjection applicationdevelopercandecideforhim-orherself.Bymeansoftheabove-described fallback mechanism,bothprocedurescanalsobecombinedwitheachotherwithoutproblemsandthusallo w onetoselectthebestprocedureforagivensituation. Haseverythingsuddenlybecometoodifficult?Don tworry itlooksmuchworsethanitreallyis. Alloneneedsisalittlebitofpractice! Alternativeapproachestothe definition InadditiontotheRuntimeDefinitionshownhere,whichmakesuseofthereflectionmechanismt ounderstandthecodestructurefortheresolutionofdependencies,othervariants,suchasth eCompilerDefinition,whichcanprovidetheadvantageofspeedatthecostofconvenience,can beusedasrequired. Zend\Di for configuration management Dependencyinjection ingeneralandZend\Di inparticularareessentiallyhelpfulintwocontexts: Ontheonehand,complexobjectgraphs(assketchedinanexemplarymannerabove)canbe combinedinruntimewithoutrequiringthedependentobjectstobecomeactiveorthatthe hardcodeddependencies,forexample,wouldbeahindranceofanykindtounittesting.Onthe otherhand,individualobjectscanalsobeconfiguredbeyondtheirdependencewiththeaidof dependencyinjection .Thus,toaccessadatabaseoranexternalwebserviceatanyarbitrary location,informationonthehostsaswellasanyavailableaccessdataarerequired.Admitted ly,the respectiveserviceofourapplicationcouldnowactivelyaccesstheapplication sconfigurati onvia theServiceManager andobtaintheappropriatevaluesthereinasimilarmanner. 1 <?php 2 // [..] 3 $config = $serviceManager->get(`Config`); 4 $host = $config['dbConfig']['host']; 5 $user = $config['dbConfig']['user']; 6 $pwd =

$config['dbConfig']['pwd']; 7 // [..] Listing13.21 However,thenboththedependenciesontheServiceManager andtheinformationontheinternal configurationstructurewouldbehardcoded,whichwouldbeextremelydisadvantageous.Here is anotherexample:HowdowebestinformourLoggingService astowhichfilewouldbethebest

Dependencyinjection oneforittologinto?Ourbestbetwouldbetoextendtheconstructoroftheservicesuchthatwe couldinjecttheconfigurationfromanexternalsource: 1 <?php 2 namespace Helloworld\Service; 3 4 class LoggingService implements LoggingServiceInterface 5 { 6 private $logfile = null; 7 8 public function __construct($logfile) 9 { 10 $this->logfile = $logfile; 11 } 12 13 public function log($str) 14 { 15 file_put_contents($this->logfile, $str, FILE_APPEND); 16 } 17 } Listing13.22 ZustzlicherweiternwirdDI-Konfigurationindermodule.config.php: 1 <?php

2 // [..] 3 'di' => array( 4 'definition' => array( 5 'class' => array( 6 'Helloworld\Service\GreetingService' => array( 7 'setLoggingService' => array( 8 'required' => true 9 ) 10 ) 11 ) 12 ), 13 'instance' => array( 14 'preferences' => array( 15 'Helloworld\Service\LoggingServiceInterface' 16 => 'Helloworld\Service\LoggingService' 17 ), 18 'Helloworld\Service\LoggingService' => array(

Dependencyinjection 19 'parameters' => array( 20 'logfile' => __DIR__ . '/../../../data/log.txt' 21 ) 22 ) 23 ) 24 ) 25 // [..] Listing13.23 Thebestthingaboutthisisthatwecancarryouttheconfigurationofthe onsite logfile,i.e. physicallyclosetothecorrespondingserviceconfiguration,butwithouthavingtohardcode it directlyintheserviceclassitself.Incontrasttoanendlessconfigurationfile,inwhichin numerable disjointedconfigurationvaluesarestrungtogether aswas,forexample,stillthecaseinFram ework Version1withtheapplication.ini onenowknowsexactlywhereonehastosearchifsomething hastobealtered.

Persistence with Zend\Db Persistence,i.e.thelong-termstorageofdata,forexampleindatabases,hasbeensupported byPHP foralongtime.ForthePDOextension,forexample,therearedatabase-specificdriversforth emost commonmanufacturersandsystemssuchthatonenearlyalwayseffortlesslymanagestoconnect toadatabaseandtostoredata.WithZend\Db ZendFramework2providesadditionalsupportfor workingwithdatabasesandthusmakespersistenceabiteasier. Incidentally,Zend\Db isnotORM54systemandthusdoesnotcompete,forexample,withDoctrine55 orPropel56,butinsteadcanbeunderstoodasa light-weight alternative,asanadditional abstraction,andbeusedaccordingly.Anyonewhouses Doctrine oranotherORMsystemwith thusveryprobablydispensewithZend\Db.Thetopicof persistence isunfortunatelycompletely overloadedwithextremelydifferent,sometimescontrarydesignationsanddefinitionssuch thatitis reallydifficulttounderstandwhatisconcealedbehindanimplementation.Inordertobebett erable toclassifyZend\Db evenifI mrunningtheriskofbeingcriticisedforhavingsimplifiedthingstoo greatly let stakeabrieflookatthemostimportantapproachesto persistence : ObjectRelationalMapper(ORM):ThestartingpointforanORMsystemisthemind-set thatweindeedprograminanobject-orientedmanner,butthenstorethedatarelationally. Thereareindeedsomeparallelsbetweenthetwoparadigms,buttherearealsosubstantial differences.OnecouldsaythatanORMsystemthereforeattemptstomakethe worldthatis foreigntothespecies ,inamannerofspeaking,asinvisibleaspossiblefortheobject-oriente d thinkingandactingapplicationdeveloper,i.e.onegeneratesandmanipulatesobjectsandth e ORMsystemtakesovereverythingelsethatreferstodatabases.ORMsystemsfrequently alsoprovideSQLalternatives:thus, Doctrine2 ,forexample,hastheDQL(DoctrineQuery Language),whichlooksalotlikeSQL(ThisisdefinitelyanadvantageforexperiencedSQL users),butalsohasanumberofadditionalpropertiestoaddresstheobject-orientationas effectivelyaspossibleandthustosimplifydatabaseoperations. DataMapper:A DataMapper isfrequentlyconsideredtobethesameasanORMsystem,but canalsosupportcompletelydifferent backends thanrelationaldatabases.Thus, Doctrine 2 ,forexample,providestheoptionofrealisingpersistencein MongoDB or CouchDB ,both document-orientedsystems.IfonementionsORMand Doctrine2 ,oneshouldcorrectly speakabout Doctrine2ORM becausethereisalso Doctrine2ODM (ObjectDocument Mapper).Themaincharacteristicofadatamapperisthefactthatitcompletelydecouples oneworld(e.g.theobject-orientedone)fromtheotherworld(e.g.therationalone)and functionsasanintermediate transformer betweenthem.Anotherimportantpropertyofa datamapper isalsothefactthatitcanstoreorloadobjectsfromseveraldifferentsources, forexampleseveraldatabasetables. 54http://de.wikipedia.org/wiki/Objektrelationale_Abbildung 55http://www.doctrine-project.org/ 56http://www.propelorm.org/

137

PersistencewithZend\Db TableDataGateway:A DataMapper oranORMsystem,respectively,conceptionally attemptstoconcealinformationaboutthedetaileddatastructuresof theotherworld to thegreatestpossibleextent,whereasthe TableDataGateway usesacompletelydifferent approach.Inthiscase,theprogram playswiththecardsonthetable andgeneratesanobject foreverytableinthedatabasethatmanagestheoperationsaroundthistable.Theimportant thingisthatincontrasttothe datamappers ,thefocusisonatableinarelationaldatabase. RowDataGateway:Whereasthe TableDataGateway representsatablewithanobject, a rowdatagateway standsasobjectforarow,i.e.foran entry ifyouwill,ofatable inthedatabase.Animportantcharacteristicofa rowdatagateway isthefactthatthe respectiveobject incontrasttoa datamapper carriestherequisitecodetoloadorstore itself.Theyarethusautonomous.Incontrast,datathataredownloadedfromadatabaseina datamapper ,frequentlyalsotermed entities inthiscontextarenotcapableofdoingthis. Instead,acentral entitymanager ismadeavailabletoensurethepersistenceoftheobjects. Anadvantageofthe entitymanager isthefactthatitoperatesinaso-called unitofwork andthuscancombineseveralsimilarSQLstatementstoformasinglestatement. ActiveRecord:An activerecord iscomparabletothe rowdatagateway . Theonly difference,whichisalsoratheradefinitionaldifference,isthat(incontrasttoa rowdata gateway )an activerecord inadditiontotherequirementsofpersistence isalsoallowed todealwithspecialistcircumstances,i.e.italsocontainscodethatnotonlydealswiththe representationofanobjectinatablerow,butalsoconcernsitselfwiththeexpertiseofthe application. DataAccessObject(DAO):The dataaccessobject isdifficulttodefine.Ifoneconsiders thedefinitionfromtheCoreJ2EEPatternCatalogue57,itallowsaccesstoadatasource,i.e.i t formstheconnectiontoadatabase,andwouldbeaccordinglyvery lowlevel andcouldserve asthebasisforthepreviously-mentionedconstructs,whichfortheirpartdon tevenpayany attentiontothedefinitionalside.Indeed,J2E(previously:J2EE)hasalwayshadasomewhat widerconceptionalbasis.Thus,onequicklysaysthatspecificobjecttypes(User,Product, Category,etc.)couldindeedalsobeobtainedfromdifferentsources,suchasfromdatabase systems,butalsofromflatfiles,anLDAPimplementationorsimilarsources.Dependingon thedatasourcetherewouldthenbearespectiveappropriate dataaccessobject .However, sinceallpersistentobjectsarefrequentlylocatedinidenticalstorage, dataaccessobjects d o notplayamajorroleor,inotherwords,theyratherremaininthebackground.Exceptwhen someonesays dataaccessobject ,butreallymeans tabledatagateway .Thesetwoterms areunfortunatelyindeedoftenusedassynonyms. Connecting to databases TheZend\Db\Adapter isresponsibleforconnectingtodatabasesandforunifyingthecharacteristics oftheSQLimplementationofindividualsystems,aswellasthedifferenttypesofdatabase connectionimplementedbyPHP.For,ontheonehand,notallproviderssupportallSQLstandard s 57http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

PersistencewithZend\Db withtheirsystemsorfrequentlyadditionallyprovideproprietaryextensionsand,ontheoth er hand,PHPhasalargenumberofdifferentoptionsfordatabaseinteractionatitsdisposal,in additiontoPDO,forexample,alsoMySQLiandtheolderversionMySQL(theextension,notthe DBMS).Thus,whenoneusesZend\Db\Adapter insteadofthenativefunctionsandobjects,one increasesportability.Ifweagainreturnagaintothedefinitionsgivenabove,wecoulddesig nate Zend\Db\Adapter asour dataaccessobject . InorderforZend\Db tofunctionproperly,thedatabaseconnectionmustinitiallybemadeviathe Adapter: 1 <?php 2 $adapter = new \Zend\Db\Adapter\Adapter( 3 array( 4 'driver' => 'Pdo_Mysql', 5 'hostname' => 'localhost' 6 'database' => 'app', 7 'username' => 'root', 8 'password' => '' 9) 10 ); Listing14.1

UsingpasswordsInaproductionsystem,wenaturallyensurethatweusestrongpasswordsandev enbetterdonotworkwiththeuser root atall,butwithspecialuserswhoseoptionscanberestrict edtothemostessentialones. TheMySQLservicewasmadeavailablebeforehandonthelocalhost,thedatabaseapp wassetup andtheuserroot wasmadeaccessiblewithoutpassword(thisiscertainlyokayinandevelopment system).Inaddition,thelog tablewassetupwiththeaidofthefollowingSQLstatement: 1 CREATE TABLE log ( 2 id int(10) NOT NULL auto_increment, 3 ip varchar(16) NOT NULL, 4 timestamp varchar(10) NOT NULL, 5 PRIMARY KEY (id) 6 );

PersistencewithZend\Db Generating and running SQL-Statements Ifonehasaddedafewdatasetstothetablemanually,theycanbeaccessedviathepreviously generatedAdapter. 1 <?php 2 $stmt = $adapter->createStatement('SELECT * FROM log'); 3 $results = $stmt->execute(); 4 5 foreach($results as $result) 6 var_dump($result); Listing14.2 Averyelegant,object-orientedprocedureforgeneratingSQLstatementsisZend\Db\Sql: 1 <?php 2 $sql = new \Zend\Db\Sql\Sql($adapter); 3 $select = $sql->select(); 4 $select->from('log'); 5 $statement = $sql->prepareStatementForSqlObject($select); 6 $results = $statement->execute(); Listing14.3 ItisalsopossibletoeasilyaddaWHERE clause: 1 <?php 2 $sql

= new \Zend\Db\Sql\Sql($adapter); 3 $select = $sql->select(); 4 $select->from('log'); 5 $select->where(array('ip' => '127.0.0.1')); 6 $statement = $sql->prepareStatementForSqlObject($select); 7 $results = $statement->execute(); Listing14.4 Thesameappliesfortheothercustomaryconstructives suchasLIMIT,OFFSET,ORDER,etc. whose usageisfundamentallyself-explanatory.AddingaJOIN isalsoessentiallysimple:

PersistencewithZend\Db 1 <?php 2 $sql = new \Zend\Db\Sql\Sql($adapter); 3 $select = $sql->select(); 4 $select->from('log'); 5 $select->join('host', 'host.ip = log.ip'); 6 $select->where(array('log.ip' => '127.0.0.1')); 7 $statement = $sql->prepareStatementForSqlObject($select); 8 $results = $statement->execute(); Listing14.5 Inthiscase,theadditionalhost tableisreferenced;indeed,thelatterisgeneratedwiththefollowing statement: 1 CREATE TABLE host ( 2 id int(10) NOT NULL auto_increment, 3 ip varchar(16) NOT NULL, 4 hostname varchar(100) NOT NULL, 5

PRIMARY KEY (id) 6 ); This join isan innerjoin .Othertypesof joins aresupportedinasimilarmanner: 1 <?php 2 $sql = new \Zend\Db\Sql\Sql($adapter); 3 $select = $sql->select(); 4 $select->from('log'); 5 6 $select->join('host', 7 'host.ip = log.ip', 8 array('*'), 9 \Zend\Db\Sql\Select::JOIN_LEFT 10 ); 11 12 $select->where(array('log.ip' => '127.0.0.1')); 13 $statement = $sql->prepareStatementForSqlObject($select); 14 $results = $statement->execute(); Listing14.6 Inthiscase,thearray('*') parameterindicatesthecolumnswhicharetobetransferredtotheresult fromthehost tableinthescopeofthe join .Whenarray('*') isused,allcolumnsareutilised; underspecificationofspecificcolumns,theresultcanberestricted;andanassociativearr aycanbe employedtoutilisealiasvaluesforthecolumnsunderconsideration. Inthismanner,datacanalsobewrittenelegantlyintothedatabase

PersistencewithZend\Db 1 <?php 2 $sql = new \Zend\Db\Sql\Sql($adapter); 3 $insert = $sql->insert('host'); 4 $insert->columns(array('ip', 'hostname')); 5 $insert->values(array('192.168.1.15', 'michaels-ipad')); 6 $statement = $sql->prepareStatementForSqlObject($insert); 7 $results = $statement->execute(); Listing14.7 updated 1 <?php 2 $sql = new \Zend\Db\Sql\Sql($adapter); 3 $update = $sql->update('host'); 4 $update->set(array('ip' => '192.168.1.20')); 5 $update->where('hostname = "michaels-ipad"'); 6 $statement = $sql->prepareStatementForSqlObject($update); 7 $results = $statement->execute(); Listing14.8 anddeleted:

1 <?php 2 $sql = new \Zend\Db\Sql\Sql($adapter); 3 $delete = $sql->delete(''); 4 $delete->from('host'); 5 $delete->where('hostname = "michaels-ipad"'); 6 $statement = $sql->prepareStatementForSqlObject($delete); 7 $results = $statement->execute(); Listing14.9 ThemultifariousoptionsAdditionaldetailsaboutZend\Db\Sql\Sqlcanalsobefoundintheof ficialdocumentation.asneeded. .http://packages.zendframework.com/docs/latest/manual/en/modules/zend.db.sql.htm l

PersistencewithZend\Db Working with tables and entries UsingZend\Db\TableGateway makesthingsmuchsimpler.Asdiscussedintheintroductiontothis chapter,aTableGateway objectrepresentsatableinadatabase.Aquerycanberealisedasfollows: 1 <?php 2 $hostTable = new \Zend\Db\TableGateway\TableGateway('host', $adapter); 3 $results = $hostTable->select(array('hostname' => 'michaels-mac')); 4 5 foreach ($results as $result) 6 var_dump($result); Listing14.10 Alsointhiscase,theappropriateadapterwasgeneratedbeforehand: 1 <?php 2 $adapter = new \Zend\Db\Adapter\Adapter( 3 array( 4 'driver' => 'Pdo_Mysql',

5 'database' => 'app', 6 'username' => 'root', 7 'password' => '' 8) 9 ); Listing14.11 Ifonedesirestofurtherprocessthereturneddata,forexamplereviseordeletedatasets, rowd ata gateway objectscanberequested;theyrepresentadatasetintherespectivetableandprovide functionstomanipulatethem.Todothis,therequestsgivenabovemustbemodifiedasfollows:

1 <?php 2 $hostTable = new \Zend\Db\TableGateway\TableGateway( 3 'host', 4 $adapter, 5 new \Zend\Db\TableGateway\Feature\RowGatewayFeature('id') 6 ); 7 8 $results = $hostTable->select(array('hostname' => 'michaels-mac')); 9 10 foreach ($results as $result) 11 var_dump($result);

PersistencewithZend\Db 144 Listing14.12 IngeneratingtheTableGateway,wetransfertheRowGatewayFeature asadditionalparameter.As aresultofthis,theTableGateway nolongermakestheindividualresultsofthequeryavailableas arraysorArrayObjects ,butinsteadasacollectionofRowGateway objects.Thelatterprovidea save() unddelete() methodthatallowsonetoreturnalterationsinthedatasettothedatabaseor eventodeletetheformer: 1 <?php 2 $hostTable = new \Zend\Db\TableGateway\TableGateway( 3 'host', 4 $adapter, 5 new \Zend\Db\TableGateway\Feature\RowGatewayFeature('id') 6 ); 7 8 $results = $hostTable->select(array('ip' => '127.0.0.1')); 9 $result = $results->current(); 10 $result->hostname = 'michaels-macbook'; 11 $result->save(); // oder: $result->delete(); Listing14.13 TheRow Data Gateway objectsthatarereturnedbyarequestarealloftheZend\Db\RowGateway\RowGateway type.Theythusprovidetheaddedvaluethattheycanensuretheirownpersistence,but,ontheo ther hand,theydonotcarryanyspecialistinformation.Inthiscase,theso-called hydration ,inwhi ch

thedataloadedfromthedatabasearetransferredtransparentlyintoaspecialistobject,com esinto playIntheprocess,theobjectindeedloosesitspersistencefunctions,butinreadingoperat ionsone caninitiallyeasilydispensewiththat. Tobeginwith,wecreatethespecialistobject,aso-called entity ,intheHelloworld moduleunder /src/Helloworld/Entity/Host.php: 1 <?php 2 namespace Helloworld\Entity; 3 4 class Host 5 { 6 protected $ip; 7 protected $hostname; 8 9 public function getHostname() 10 { 11 return $this->hostname; 12 }

PersistencewithZend\Db 13 14 public function getIp() 15 { 16 return $this->ip; 17 } 18 } Listing14.14 Initially,theHost hasonlytwoprotected characteristicsand getter .Thefollowingrequestnow resultsinobjectsofthehost typebeingmaintainedinthehost variable: 1 <?php 2 $hostTable = new \Zend\Db\TableGateway\TableGateway( 3 'host', 4 $adapter, 5 new \Zend\Db\TableGateway\Feature\RowGatewayFeature('id') 6 ); 7 8 $results = $hostTable->select(array('ip' => '127.0.0.1')); 9 10 $hosts =

new \Zend\Db\ResultSet\HydratingResultSet( 11 new \Zend\Stdlib\Hydrator\Reflection(), 12 new \Helloworld\Entity\Host() 13 ); 14 15 $hosts->initialize($results->toArray()); Listing14.15 TheresultofthequeryistransferredintoaHydratingResultSet.Toachievethis,adecisiono nhow thedatashouldbeassigned(Reflection)andwheretheyshouldultimatelyendup(Host)mustbe made.Toachievethis,theso-called prototypepattern isused,inwhichanexemplaryobject(in thiscasethenewlygeneratedinstanceoftheHost class)duplicatesthequery(clone)forevery datasetintheResultSet andequipsthemwiththedata.Thus,oneinsertsa prototypical object andobtainsasmanyclonesofthisobject(eachinitialisedwiththecorrectdata)asrequired.

Butwhyaren ttherequiredobjectssimplyinstantiatedviathenew operatorasrequired?Theidea behindthisisthattheobject(inadditiontothedatafieldsthataretobefilled)canalsohave additionaldependenciestofurtherobjectsthatcouldnotberesolvedautomaticallyandwith out problems.Instead,analreadyconfiguredobjectissimplyclonedandtheproblem,thusavoide d. Theassumptionwhichismadeinthiscaseisthatthenamesofthetablecolumnsagreewiththe objectcharacteristics.Ifthisisnot(always)thecase,onethusre-acquiresonlypartially filledobjects insomecases.

PersistencewithZend\Db 146 1 object(Helloworld\Entity\Host)#236 (2) { 2 ["ip":protected]=> string(9) "127.0.0.1" 3 ["hostname":protected]=> NULL 4 } Thehostname isemptybecausethecorrespondingdatabasefieldisdesignatedasworkstation. Now,onecaneitherrenamethedatabasefield hostname ortheobjectcharacteristic workstation . However,sincethisisnotalwaysappropriateorpossible,onecaninsteadmakedowithaderive d hydrator ofone sown,whichtakescareoftherequired mapping : 1 <?php 2 namespace Helloworld\Mapper; 3 4 use Zend\Stdlib\Hydrator\Reflection; 5 use Helloworld\Entity\Host; 6 7 class HostHydrator extends Reflection 8 { 9 public function hydrate(array $data, $object) 10 { 11 if (!$object instanceof Host) { 12 throw new \InvalidArgumentException(

13 '$object must be an instance of Helloworld\Entity\Host' 14 ); 15 } 16 17 $data = $this->mapField('workstation', 'hostname', $data); 18 return parent::hydrate($data, $object); 19 } 20 21 protected function mapField($keyFrom, $keyTo, array $array) 22 { 23 $array[$keyTo] = $array[$keyFrom]; 24 unset($array[$keyFrom]); 25 return $array; 26 } 27 } Listing14.16 Thisclassislocatedinthesrc/Helloworld/Mapper/HostHydrator.php directoryandaccordingly mapstheworkstation fieldontothehostname field.Now,onemustonlymodifytheinvocation suchthattheappropriate hydrator isapplicable:

PersistencewithZend\Db 1 <?php 2 $hostTable = new \Zend\Db\TableGateway\TableGateway( 3 'host', 4 $adapter, 5 new \Zend\Db\TableGateway\Feature\RowGatewayFeature('id') 6 ); 7 8 $results = $hostTable->select(array('ip' => '127.0.0.1')); 9 10 $hosts = new \Zend\Db\ResultSet\HydratingResultSet( 11 new \Helloworld\Mapper\HostHydrator(), 12 new \Helloworld\Entity\Host() 13 ); 14 15 $hosts->initialize($results->toArray()); 16 var_dump($hosts->current());

Listing14.17 Now,theloadingofthedataagainfunctionsasdesired: 1 object(Helloworld\Entity\Host)#236 (2) { 2 ["ip":protected]=> string(9) "127.0.0.1" 3 ["hostname":protected]=> string(12) "michaels-mac" 4} Organisation of database queries Untilnowwehavemadeorexecutedthedatabaseconnectionandthedatabasequeriesdirectlyin acontroller. Fordemonstrationpurposesthatwascertainlyacceptable. However,inanactual applicationonerequiresabetterprocedurefordealingwithdatabasequeries. Thefollowing procedureisrecommended:Thecreationofadatabaseadapter,whichallowsaccesstothedatab ase, isshiftedintotheServiceManager,theconnectiondatadepositedinaconfigurationfile,an dthe dataqueriesencapsulatedaroundtheindividualtablesorentities,respectively,inspecia lobjects. Creating a database adapter via the ServiceManager InordertopreparetheServiceManager forthecreationofadatabaseadapter,weinitiallymodify themodule.config.php oftheHelloworld moduleasfollows:

PersistencewithZend\Db 1 <?php 2 // [..] 3 'service_manager' => array( 4 'factories' => array( 5 'Zend\Db\Adapter\Adapter' => function ($sm) { 6 $config = $sm->get('Config'); 7 $dbParams = $config['dbParams']; 8 9 return new Zend\Db\Adapter\Adapter(array( 10 'driver' => 'pdo', 11 'dsn' => 12 'mysql:dbname='.$dbParams['database'] 13 .';host='.$dbParams['hostname'], 14 'database' => $dbParams['database'], 15 'username' => $dbParams['username'], 16 'password' => $dbParams['password'], 17

'hostname' => $dbParams['hostname'], 18 )); 19 }, 20 ), 21 ) 22 // [..] Listing14.18 Anyalreadyexistingservicedefinitionsshouldnaturallyberetainedasrequired.Incident ally,the moduleinwhichthisservicedefinitionislocatedisabsolutelyarbitrary.Asshownaboveint he HelloWorld module,wecanalsoaccommodateitinApplication insomecases.Ifonedesires tousethedatabaseadapteracrossanumberof functionmodules ,itisappropriatetomovethe definitionintotheApplication module,simplybecauseoneknows byconvention whereone mustlookwhenoneislookingforthedefinitionofaservicethatisusedacrossseveralmodules . Theabove-mentionedcallbackfunctioncreatesthedatabaseadapterandtoachievethisacces ses connectiondatathataredepositedinaconfigurationfile.Iusedthedev1.local.php fileforthis purpose:

PersistencewithZend\Db 1 <?php 2 return array( 3 'dbParams' => array( 4 'database' => 'app', 5 'username' => 'root', 6 'password' => '', 7 'hostname' => 'localhost', 8 ) 9 ); Listing14.19 Thus,wecanchargetheServiceManager withthecreationofthedatabaseadapterwhereeveritis required:Outof 1 <?php 2 $adapter = new \Zend\Db\Adapter\Adapter( 3 array( 4 'driver' => 'Pdo_Mysql', 5 'database' => 'app',

6 'username' => 'root', 7 'password' => '' 8) 9 ); Listing14.20 Located,forexample,inacontroller,thereforebecomes 1 <?php 2 $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter'); Listing14.21 Capsules of similar queries Toavoidscatteringdatabasequeriesacrosstheentireapplicationandtopreventalteration sof dataschemesoreventhequeryitselffrombecomingmaintenancenightmares,itisadvisableto consolidateallquerieswhichrefertothesame entity ordatabasetable,respectively,atonelo cation fromtheverybeginning.Toachievethis,onecaneffectivelyusetheTableGateway,whichwe arealreadyfamiliarwithandwhichallowssimpleaccesstodatabasetables.Wesetupaspecifi c TableGateway foreveryentity.Inthiscontext,weorientourselvestotherespectiveentity:

PersistencewithZend\Db <?php namespace Helloworld\Mapper; use Helloworld\Entity\Host as HostEntity; use Zend\Stdlib\Hydrator\HydratorInterface; use Zend\Db\TableGateway\TableGateway; use Zend\Db\TableGateway\Feature\RowGatewayFeature; class Host extends TableGateway { protected $tableName = 'host'; protected $idCol = 'id'; protected $entityPrototype = null; protected $hydrator = null; public function __construct($adapter) { parent::__construct($this->tableName, $adapter, new RowGatewayFeature($this->idCol)

); $this->entityPrototype = new HostEntity(); $this->hydrator = new HostHydrator(); } public function findByIp($ip) { return $this->hydrate( $this->select(array('ip' => $ip)) ); } public function hydrate($results) { $hosts = new \Zend\Db\ResultSet\HydratingResultSet( $this->hydrator, $this->entityPrototype ); return $hosts->initialize($results->toArray()); }

PersistencewithZend\Db 43 } Listing14.22 Wedepositthecodeforthemapperinsrc/Helloworld/Mapper/Host.php andthusinthesame directoryinwhichourHostHydrator isalsoalreadylocatedandwhichwealsoagainmakeuseof inthiscase. Naturally,thiscodecanbefurtheroptimised;thereisnoquestionaboutthat.Forexample,it would bemoreappropriatetoinjectalltheconfigurationvaluesanddependenciesthattohardcodet hem there.Butforthisexampleitissufficienttodoitsimply.Whenonedesirestomakeiteveneasi erfor oneself,onedrawsontheready-to-useAbstractDbMapper58oftheZF-Commons5.gitrepositor ies. Thisworkhasalreadybeendone,quasibythe headoffice (therepositoryismaintainedby developerswhoarealsodirectlyinvolvedinFrameworkthemselves).It sveryworthwhiletotak e alookatitinanycase. Theinvocationstillrequiredinthecontrollerisalreadyverycompact: 1 <?php 2 $adapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter'); 3 $mapper = new \Helloworld\Mapper\Host($adapter); 4 $hosts = $mapper->findByIp('127.0.0.1'); Listing14.23 WecouldshortenthecodeevenmoreifweweretoinjecttheAdapter automaticallyatthecreation ofthe mapper : 1 <?php 2 // [..] 3 'service_manager' => array( 4 'factories' => array( 5 'Zend\Db\Adapter\Adapter' =>

function ($sm) { 6 $config = $sm->get('Config'); 7 $dbParams = $config['dbParams']; 8 9 return new Zend\Db\Adapter\Adapter(array( 10 'driver' => 'pdo', 11 'dsn' => 12 'mysql:dbname='.$dbParams['database'] 13 .';host='.$dbParams['hostname'], 14 'database' => $dbParams['database'], 58https://github.com/ZF-Commons/ZfcBase/blob/master/src/ZfcBase/Mapper/AbstractD bMapper.php 5.https://github.com/ZF-Commons

PersistencewithZend\Db 15 'username' => $dbParams['username'], 16 'password' => $dbParams['password'], 17 'hostname' => $dbParams['hostname'], 18 )); 19 }, 20 'Helloworld\Mapper\Host' => function ($sm) { 21 return new \Helloworld\Mapper\Host( 22 $sm->get('Zend\Db\Adapter\Adapter') 23 ); 24 } 25 ), 26 ), 27 // [..] Listing14.24 Now,theinvocationstillrequiredinthecontrollerisjustaone-liner: 1 <?php 2 $hosts = $this->getServiceLocator() 3 ->get('Helloworld\Mapper\Host')->findByIp('127.0.0.1'); Listing14.25 ByutilisingtheTableGateway,wecannowalsoelegantlysolvetheproblemoftheentitiesloos ing theirpersistencefunctionsasaresultofhydration.However,analterationofanentitycann owno longerbedirectlywrittenintothedatabasebyusingsave().WenowinstructtheHost mapperto

dothis: 1 <?php 2 3 namespace Helloworld\Mapper; 4 5 use Helloworld\Entity\Host as HostEntity; 6 use Zend\Stdlib\Hydrator\HydratorInterface; 7 use Zend\Db\TableGateway\TableGateway; 8 use Zend\Db\TableGateway\Feature\RowGatewayFeature; 9 use Zend\Db\Sql\Sql; 10 use Zend\Db\Sql\Insert; 11 12 class Host extends TableGateway 13 { 14 protected $tableName = 'host';

PersistencewithZend\Db 15 protected $idCol = 'id'; 16 protected $entityPrototype = null; 17 protected $hydrator = null; 18 19 public function __construct($adapter) 20 { 21 parent::__construct($this->tableName, 22 $adapter, 23 new RowGatewayFeature($this->idCol) 24 ); 25 26 $this->entityPrototype = new HostEntity(); 27 $this->hydrator = new HostHydrator(); 28 } 29 30 public function findByIp($ip) 31 { 32 return $this->hydrate( 33 $this->select(array('ip'

=> $ip)) 34 ); 35 } 36 37 public function hydrate($results) 38 { 39 $hosts = new \Zend\Db\ResultSet\HydratingResultSet( 40 $this->hydrator, 41 $this->entityPrototype 42 ); 43 44 return $hosts->initialize($results->toArray()); 45 } 46 47 public function insert($entity) 48 { 49 return parent::insert($this->hydrator->extract($entity)); 50 } 51 52 public function updateEntity($entity) 53 { 54 return parent::update( 55 $this->hydrator->extract($entity), 56 $this->idCol . "=" . $entity->getId()

PersistencewithZend\Db 154 57 ); 58 } 59 } Listing14.26 Theinsert() undupdateEntity() methodsarenewhere. However,wemustalsoextendthe HostHydrator ifthecolumndesignationsdeviatefromtheobjectcharacteristics: 1 <?php 2 namespace Helloworld\Mapper; 3 4 use Zend\Stdlib\Hydrator\Reflection; 5 use Helloworld\Entity\Host as HostEntity; 6 7 class HostHydrator extends Reflection 8 { 9 public function hydrate(array $data, $object) 10 { 11 if (!$object instanceof HostEntity) { 12 throw new \InvalidArgumentException( 13 '$object must be

an instance of Helloworld\Entity\Host' 14 ); 15 } 16 17 $data = $this->mapField('workstation', 'hostname', $data); 18 return parent::hydrate($data, $object); 19 } 20 21 public function extract($object) 22 { 23 if (!$object instanceof HostEntity) { 24 throw new \InvalidArgumentException( 25 '$object must be an instance of Helloworld\Entity\Host' 26 ); 27 } 28 29 $data = parent::extract($object); 30 $data = $this->mapField('hostname', 'workstation',

$data); 31 return $data; 32 } 33 34 protected function mapField($keyFrom, $keyTo, array $array)

PersistencewithZend\Db 35 { 36 $array[$keyTo] = $array[$keyFrom]; 37 unset($array[$keyFrom]); 38 return $array; 39 } 40 } Listing14.27 Wecannowalterapreviouslyloadeddatasetinthecontroller: 1 <?php 2 $hosts = $this->getServiceLocator() 3 ->get('Helloworld\Mapper\Host')->findByIp('127.0.0.1'); 4 5 $host = $hosts->current(); 6 $host->setHostname('my-mac'); 7 8 $this->getServiceLocator() 9 ->get('Helloworld\Mapper\Host')->updateEntity($host); Listing14.28 TheinsertionofnewdatasetsbytheHost-Mappernowfunctionssimilarly: 1 <?php 2 $newEntity = new \Helloworld\Entity\Host(); 3 $newEntity->setHostname('michaels-iphone'); 4 $newEntity->setIp('192.168.1.56'); 5 6 $this->getServiceLocator() 7 ->get('Helloworld\Mapper\Host')->insert($newEntity); Listing14.29

Forcompletenesssake,hereistheHost-Entityinitsfinalconfigurationlevelagain:

PersistencewithZend\Db <?php namespace Helloworld\Entity; class Host { protected $id; protected $ip; protected $hostname; public function getHostname() { return $this->hostname; } public function getIp() { return $this->ip; } public function setIp($ip) { $this->ip = $ip;

} public function setHostname($hostname) { $this->hostname = $hostname; } public function setId($id) { $this->id = $id; } public function getId() { return $this->id; } } Listing14.30

PersistencewithZend\Db Inparticular,theid characteristicisalsostillimportantherebecausethedatasetthatistobeupdated isdeterminedinthismanner. Zend\DB alternative: Doctrine 2 ORM Ifwearecompletelyhonest:Zend\Db isindeedusefulbutitquicklyreachesitslimits.Thechallenge of persistence incomplexsystemscannotbesatisfactorilymasteredinmorecomplexsystemsby usingit.Zend\Db doesnotprovideeitheran activerecord implementationorthefunctionalityof an ORM ;which,however,soonerorlaterverynoticeablycontributestokeepingthecomplexity ofanapplicationmanageable.Inaddition,onehastowritealargeamountofcodedealingwith persistence oneself.IthereforeabsolutelyrecommendthatyoutakealookatDoctrine26.Zend Framework2andDoctrine2complementeachothersuperblyandreallyonlydeveloptheirstreng ths completelywhenusedtogether. 6http://www.doctrine-project.org/

Validators Standard validators WithZend\Validator,Frameworkprovidesasimple,butveryhelpfulmechanismforvalidating

valueswithregardtodefinedrequirements,forexampleentriesrequestbytheclientForthem ostcommonrequirements,italsoimplementations.Forexample,withjustafewlinesofcode,a valuewiththeISBNStandard: 1 <?php 2 $validator = new \Zend\Validator\Isbn(); 3 4 if($validator->isValid('315000017')) 5 echo "In Ordnung!"; Listing15.1 sentinthescopeofaPOST additionallyprovidesspecific canbecheckedforconformity Thefactthatthisisasyntacticandnotasemanticcheckisimportant.Thus, O.K. isalsodisplaye d ifanISBNisspecifiedthatisindeedcorrect,buthasnotyetbeenassignedtoanybook. Validatorsgenerallyprovideerrormessagesforextremelydifferenterrorsituations,whic hcanbe invokedsubsequenttoavalidationprocedurebymeansofgetMessages(): 1 <?php 2 $validator = new \Zend\Validator\Isbn(); 3 4

if($validator->isValid('315090017')) 5 echo "In Ordnung!"; 6 else { 7 foreach ($validator->getMessages() as $messageId => $message) { 8 echo $message; 9} 10 } Listing15.2 Inthiscase,wesee 1 The input is not a valid ISBN number onthescreen.However,ifweinvoke, 158

Validators 159 1 <?php 2 $validator = new \Zend\Validator\Isbn(); 3 4 if($validator->isValid('12.23')) 5 echo "In Ordnung!"; 6 else { 7 foreach ($validator->getMessages() as $messageId => $message) { 8 echo $message; 9 } 10 } Listing15.3 thisresultsinoutputofthefollowing: 1 Invalid type given. String or integer expected TheIsbn validatorthustwoencompassestwodifferenterrorcases.Ascanalreadybeexpected,one cansetindividualerrormessagesbymeansofthesetMessage() method: 1 <?php 2 $validator = new \Zend\Validator\Isbn(); 3 4

$validator->setMessage( 5 'Es wird ein String oder ein Integer-Wert zur Validierung bentigt!', 6 \Zend\Validator\Isbn::INVALID 7 ); 8 9 if($validator->isValid(12.23)) 10 echo "In Ordnung!"; 11 else { 12 foreach ($validator->getMessages() as $messageId => $message) { 13 echo $message; 14 } 15 } Listing15.4 Toachievethis,themessagekeythatisusedinthevalidatoristobespecifiedineachcase. Thefollowingself-explanatorystandardvalidatorsalreadyexistinFramework: Barcode

Validators 160 Between Callback CreditCard Crsf Date DateStep Digits EmailAddress Explode GreaterThan Hex Hostname Iban Identical InArray Ip Isbn LessThan NotEmpty Regex Step StringLength Uri PerformingseveralvalidationssimultaneouslyWiththeaidoftheZend\Validator\Validato rChainclass,severalvalidatorscanbelinkedforsequentialchecks,asrequired. Writing your own validators Ifonerequiresadditionalvalidationfunctions,onecaneasilywriteone sownvalidatorsifth ey inheritfromAbstractValidator:

Validators 161 <?php class Helloworld\Validator\Float extends Zend\Validator\AbstractValidator { const FLOAT = 'float'; protected $messageTemplates = array( self::FLOAT => "'%value%' ist kein Float-Wert." ); public function isValid($value) { $this->setValue($value); if (!is_float($value)) { $this->error(self::FLOAT); return false; } return true; } }

Listing15.5

Webforms Webformsareintegralcomponentsofwebapplications:basicallytheyaretheonlypossibilit ythat auserhastotransferdatatotheserver,i.e.totheapplication.Webformsalwaysprimarilypr esent theapplicationdeveloperwithmany-facetedchallenges.Comprehensivesupportforwebform sare anintegralpartofagoodweb-framework,andthusexorcisesourfearofthemtosomeextent. WithZend\Form,ZendFramework2providesahigh-performancesolution,whichcandonearly everythingthatonedesires.Evenifitisnotparticularlyeasytounderstand. Tobeginwithweneedabitoftheory:Eachwebformisfundamentallybasedononeormore objectsoftheZend\Form\Element type.TheyformthebasicunitofZend\Form-basedforms.Inthis context,oneelementinitiallycorrespondsfundamentallytothecustomaryHTMLformelement s, whichoneisalreadyfamiliarwith,i.e.inputfields(text),radiobuttons(radio),selectio nlists (select)andsoonThefilterthatshouldbeemployedfortherespectivelyinputdataandtherul es thataretobeusedforthevalidationofthatdatacanbespecifiedviaaso-called input ,assoonas theformerhavebeenreceived.Typicalfiltersare,forexample,StripTags,whichremoveallH TML tagsintheenteredcharacterstringsorStringToLower,which(asalreadyexpected)converts all inputcharacterstosmallletters.Validationmeanstheexaminationoftheinputdataforcomp liance withpreviouslydefinedconditions.Thus,forexample,theISBNValidatorcanbeusedforanin put fieldtocheckwhetherthedatacorrespondtoanISBNnumber,whereasNotEmpty checkswhether atleastsomethinghasbeenenteredintothefield.Theindividual inputs areaggregatedinthe so-called InputFilter andtherespectivewebformismadeavailable.Theindividualformelemen ts can,inturn,besemanticallygroupedasso-called fieldsets .Incidentally,fromatechnicalpo int ofview,aZend\Form\Fieldset isalsoagainonlyan Element inthiscontext,derivedfromthe Zend\Form\Element class. Fleldsets orindividualelements(oralsobothtogether)arethen,in turn,combinedtoformaZend\Form\Form,whichistechnicallyagainonlyan Element it,too,is derivedfromZend\Form\Element. Preparing a form AwebformisquicklypreparedwithHTML: 1 <form action="#"

method="post"> 2 <fieldset> 3 <label for="name">Ihr Name:</label> 4 <input type="text" id="name" /> 5 <label for="email">Ihre E-Mail-Adresse:</label> 6 <input type="email" id="email" /> 7 <input type="submit" value="Eintragen" /> 8 </fieldset> 9 </form> 162

Webforms 163 Listing16.1 However,theexperiencedapplicationdeveloperknowsthatthisdoesnotmeanthateverything hasbeenachieved notbyalongshot:Thereceiveddatahastobesyntacticallyandsematically validatedforfurtherprocessing forexample,forstorageinadatabase anderrorsituations, treatedwithprovenmeans,notleastbecausechecksoftheinputdatabytheclient,evenjustfo r safetyreasons,areneveradequate.Allthiscodemustnormallybedevelopedmanuallyandthe correspondingcoderealised,forexample,inthecontroller.Frameworkcanhelpinthiscase. The fundamentalideaistorealiseawebformasadistinctconstructthatcontainsalltheinformat ionon thecontainedfieldsandtheirbasicsyntacticandsemanticconditionsaswellasonthenecess ary informationwithregardtohowthedataenterandsubsequentlyexitviathebestpathways.In Framework,anumberofdifferentclassesandcomponentsareusedforwebforms. Zend\Form:corecomponentsviawhichtheapplicationdevelopersinteractwithwebforms. Zend\InputFilter:enhanceswebformswithfilterandvalidationcapabilities. Zend\View\Helper:allowsthevisualpresentationofforms. Zend\Stdlib\Hydrator:enhanceswebformswiththecapabilitytoautomaticallytransfer datafromtheformintootherobjectsortoimportitfromthere. Frameworkallowstheapplicationdeveloperanumberofdecisionsastohowawebformisto bestructured.Inthiscontext,thependulumoscillates asisoftenthecaseinZendFramework 2 betweenwritingcodeandwritingconfiguration. Otherthanthat,thereisawiderangeof optionsfordealingwithwebforms.Arecommendableapproachistomapformsviaone sownForm classes.Ifweweretodesiretodepicttheformshownabovefortheregestrationtoanewsletter via aZend\Form Object,wehavetodefinethewebforminthesrc/Helloworld/Form/SignUp.php file. 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Form; 5 6 class SignUp extends Form 7{ 8 public

function __construct() 9{ 10 parent::__construct('signUp'); 11 $this->setAttribute('action', '/signup'); 12 $this->setAttribute('method', 'post'); 13 14 $this->add(array( 15 'name' => 'name', 16 'attributes' => array(

Webforms 164 17 'type' => 'text', 18 'id' => 'name' 19 ), 20 'options' => array( 21 'label' => 'Ihr Name:' 22 ), 23 )); 24 25 $this->add(array( 26 'name' => 'email', 27 'attributes' => array( 28 'type' => 'email', 29 'id' => 'email' 30 ), 31 'options' => array( 32 'label' => 'Ihre E-Mail-Adresse:' 33 ),

34 )); 35 36 $this->add(array( 37 'name' => 'submit', 38 'attributes' => array( 39 'type' => 'submit', 40 'value' => 'Eintragen' 41 ), 42 )); 43 } 44 } Listing16.26 Newelementsorfleldsetsareaddedtotheformviatheadd() method.Toachievethis,theadd() method,inturn,invokestheZend\Form\Factory;thelatterknowshowtointerpretthespecifi cation (termed Spec ),whichistransferredasanarray,andtogeneratetheappropriateelements.Themo st importantcomponentsofthespecificationarename,type,attributes undoptions.Thename can befreelyselected,whereasthetype representsanelementtype,whichreallyexistsinFramework (inthiscaseelementsoftheZend\Form\Element\Text typearegeneratedbecausenothingelsehas beenspecified)andattributes theestablishmentofallattributesoftheultimatelygeneratedHTML element(<input name="name" type="text" id="name">).Theoptions sectionallowsthedefinition offieldlabels(i.e.thatwhichisadditionallydisplayedbeforeorabovetheinputfieldprop er)via label or,ontheotherhand,alsotheattributeofthelabelvialabel_attributes,respectively. 6https://gist.github.com/3919928

Webforms 165 Inadditiontotheindividualelements,anumberofattributeswillbestipulatedbytheapplic ation developersviasetAttribute(),amongthemaction andmethod,whicharebothabsolutely necessarysothatthewebformcanalsobesubsequentlysentoff. Displaying a form IhavegoodnewsforallthosewhohaveusedVersion1ofFramework:Therearenolongeranyform decorators:Form-decoratorswereaverycommitted,butatthesametimealsoverycomplicated , approach,whichallowedtherenderingofwebformsbymeansofnestingofviewobjects,each ofwhichthengeneratedasmallpartofthefinalHTMLmarkup.IbelievethatForm-Decorators warjustaboutthemostdifficultaspectofZendFramework1andalsogeneratedalargenumberof problemsotherwise,whichonehadnoteventhoughtofbeforehand.But,that senoughreminiscin g; theynolongerexistinVersion2.Theadvantageisveryclear:thesimpleruseofforms,butthec ode isslightlylesscompactifonejustusesthemeansavailableintheprogramme.Todisplaythefo rm definedabove,somewhatmoreviewcodeisrequiredasforthe<?php echo $this->form; ?> of formertimes.However,thiscanalsoberealisedwithalittlebitofeffort,aswewillseeinthe practicepartofthebook. 1 <?php 2 $this->form->prepare(); 3 echo $this->form()->openTag($this->form); 4 echo $this->formRow($this->form->get('name')); 5 echo $this->formRow($this->form->get('email')); 6 echo $this->formSubmit($this->form->get('submit')); 7 echo $this->form()->closeTag();

Listing16.3 ThiscodehastobeintheView fileoftheAction inwhichtheFormisinstantiatedandwillbe returnedinthescopeoftheViewModel: 1 <?php 2 return new ViewModel( 3 array( 4 'form' => new \Helloworld\Form\SignUp() 5) 6 ); Listing16.4 Anumberofviewhelpersandtheformitselfareusedforthedepiction.Roughlystated,thereis anappropriateViewHelper,whichcanbeusedforthedepiction,foreverytypeofformelement

Webforms 166 thatHTMLdefines. Moreover,thereareanumberofadditionalauxiliaryconstructs,suchas FormRow,whichensuresthataformfieldwithitslabel,thefielditselfand,asrequired,anye xisting errormessagearedisplayedanalogously inasequence .TheView Helpers areallnearlyselfexplanatory. Itisimportantthatprepare() isinvokedtobeginwith,beforeanyotherelementsare accessed;otherwise,thisresultsinanerror. Editing form entries Basically,therearetwopossibilitiesofrealisingformprocessing:eitherinthesameactio ninwhich theemptywebformwasgeneratedorinaseparateaction.Iftheprocessingistotakeplaceinthe sameaction,an isPost()check canbeusedtodeterminewhethertheformistobedisplayedor whethertheentriesaretobeedited: 1 <?php 2 if ($this->getRequest()->isPost()) { 3 // Formularverarbeitung 4 } else { 5 return new ViewModel( 6 array( 7 'form' => new \Helloworld\Form\SignUp() 8 ) 9 ); 10 } Listing16.5 Toaccessthesentdata,onecannoweitherdirectlyaccessthePOSTdata: 1 <?php 2 $form

= new \Helloworld\Form\SignUp(); 3 4 if ($this->getRequest()->isPost()) { 5 $data = $this->getRequest()->getPost(); 6 var_dump($data);exit; 7 } else { 8 return new ViewModel( 9 array( 10 'form' => $form 11 ) 12 ); 13 }

Webforms 167 Listing16.6 inwhichthefollowingoutputisgenerated: 1 2 3 4 5 class } Zend\Stdlib\Parameters#74 (3) { public $name => string(13) "Michael Romer" public $email => string(24) "zf2buch@michael-romer.de" public $submit => string(9) "Eintragen" oronecanaccessthesentdataviatheForm object. previouslyvalidatedthedataviathewebform. However,thatisonlythenpossibleifonehas Validating form entries Uptonow,theadvantageofZend\Form isadmittedlyrelativelystraightforward,butnowwe regoing totakeoff.IfwedonotobtainthesentdataviathePOSTarrayofPHP,butratherviatheform itself,theentriescanbeautomaticallyvalidatedinasimplemannerbasedonpreviouslydefi ned rulesandthedatafiltered. Toachievethis,theso-called InputFilter hastobedefinedinitially.Theterm InputFilter isabi t misleadingbecausenotonlythefiltersbutalsothevalidatorsaredefinedinthisway.Filter smodify theinputdataasrequired,whereasvalidatorstestthedataforcertainconditions,forexamp lea maximumstringlength. Thereareanumberofoptionsforthedefinitionofthe InputFilter .Forexample,thedefinitions canbemovedintoaclassoftheirownoralternativelytheyaremadeavailablebytheformclass viathegetInputFilter() method.Inthiscase,werealizethe InputFilter inaclassofitsownin thesrc/Helloworld/Form/SignUpFilter.php file:

1 2 3 4 5 6 7 8 9 10 11 12 13 <?php namespace Helloworld\Form; use Zend\Form\Form; use Zend\InputFilter\InputFilter; class SignUpFilter extends InputFilter { public function __construct() { $this->add(array( 'name' => 'email', 'required'=> true,

Webforms 168 14 'validators' => array( 15 array( 16 'name' => 'EmailAddress' 17 ) 18 ), 19 )); 20 21 $this->add(array( 22 'name' => 'name', 23 'required' => true, 24 'filters' => array( 25 array( 26 'name' => 'StringTrim' 27 ) 28 ) 29 )); 30 } 31 } Listing16.7 Inthiscase,theindividualinputsareaddedtothe InputFilter bytheadd() method. Inthis context,aninputbasicallycorrespondstoaformelement,i.e.aninputoption,whichisrefer ence

viathename andissubsequentlytakenintoaccount.Whetherornotthefieldismandatorycan becontrolledbymeansofrequired. Analogously,thefiltersandvalidatorstobeusedcanbe definedusingvalidators andfilters,wherethename mustcorrespondtothefiltersorvalidators suppliedwithFrameworkortosubsequentlyaddedfiltersorvalidators,respectively.Alook atthe sourcecodeoftheZend\Validator\ValidatorPluginManager revealsthestandardvalidatorsand theirsymbolicnames,viawhichtheycanbeaccessed/invoked.Zend\Filter\FilterPluginMan ager providesinformationonFramework sfilters. NowwemustfirstextendtheformdefinitionwithregardtotheSignUpFilter. 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Form; 5 6 class SignUp extends Form 7 { 8 public function __construct() 9 { 10 parent::__construct('signUp');

Webforms 169 11 $this->setAttribute('action', '/signup'); 12 $this->setAttribute('method', 'post'); 13 $this->setInputFilter(new \Helloworld\Form\SignUpFilter()); 14 15 $this->add(array( 16 'name' => 'name', 17 'attributes' => array( 18 'type' => 'text', 19 ), 20 'options' => array( 21 'id' => 'name', 22 'label' => 'Ihr Name:' 23 ), 24 )); 25 26 $this->add(array( 27 'name' => 'email', 28 'attributes' => array( 29

'type' => 'email', 30 ), 31 'options' => array( 32 'id' => 'email', 33 'label' => 'Ihre E-Mail-Adresse:' 34 ), 35 )); 36 37 $this->add(array( 38 'name' => 'submit', 39 'attributes' => array( 40 'type' => 'submit', 41 'value' => 'Eintragen' 42 ), 43 )); 44 } 45 } Listing16.86 ThesetInputFilter() methodlinkstheformwiththe InputFilter inthiscase.Butalsointhis case,thegeneraladvicethatitisadvisablenotthehardwiretheSignUpFilter applies;insteadit shouldbeinjectedviaanappropriatefactoryandtheServiceManager oralternativelyviaZend\Di. Now,formprocessingcanbeeffectedinthecontrollerasfollows: 6https://gist.github.com/3920184

Webforms 170 1 <?php 2 public function indexAction() 3 { 4 $form = new \Helloworld\Form\SignUp(); 5 6 if ($this->getRequest()->isPost()) { 7 $form->setData($this->getRequest()->getPost()); 8 9 if ($form->isValid()) { 10 var_dump($form->getData()); 11 } else { 12 return new ViewModel( 13 array( 14 'form' => $form 15 ) 16 ); 17 } 18 } else { 19 return new ViewModel(

20 array( 21 'form' => $form 22 ) 23 ); 24 } 25 } Listing16.96 Inthiscase,asuccessfulinputresultsinthepleasantoutput: 1 array(2) { 2 ["email"]=> string(24) "zf2buch@michael-romer.de" 3 ["name"]=> string(13) "Michael Romer" 4 } Thus,thedataarenowpresentintheformofanarrayforfurtherprocessing.Anyfilterswhich wereregisteredfortherespectivefieldshavealreadybeenappliedbythistime. Incidentally,inthismanner,errormessagesarenowalsoalreadydisplayed;ifthisisthecas e,the formwillnotbevalidated,andwereturnittotheviewforrepeateddisplay.Ifoneshouldhappe n tosendtheformwithoutentries(whichshouldresultinanerroraccordingtotheSignUpFilter definition),oneseesthefollowingforthetwofields: 6https://gist.github.com/3920196

Webforms 171 1 "Value is required and can't be empty" Inordertochangethedisplayederrormessage,oneusestheoptions arrayinthevalidatordefinition. However,theerrormessageshownabovehasaspecialfeature,becausetheSignUpFilter doesnot yetevenusetheNotEmpty validator,whichwewereabletoprovidewiththenecessary options . Instead,therequirementthatthefieldmustbefilledinisexpressedvia'required' => true.Based onthis,Frameworkautomaticallygeneratestheappropriatevalidator.Thus,the'required' => true expressionisa shortcut .Whenweadaptthefilterasfollows,wecandepositindividualerror messages: 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Form; 5 use Zend\InputFilter\InputFilter; 6 7 class SignUpFilter extends InputFilter 8 { 9 public function __construct() 10 { 11 $this->add(array( 12 'name' => 'email',

13 'validators' => array( 14 array( 15 'name' => 'NotEmpty', 16 'options' => array( 17 'messages' => array( 18 \Zend\Validator\NotEmpty::IS_EMPTY => 19 'Bitte geben Sie etwas ein.' 20 ) 21 ) 22 ), 23 array( 24 'name' => 'EmailAddress', 25 'options' => array( 26 'messages' => array( 27 \Zend\Validator\EmailAddress::INVALID_FORMAT => 28 'Bitte richtige E-Mail-Adresse eingeben.' 29 ) 30 ) 31

), 32 ),

Webforms 172 33 )); 34 35 $this->add(array( 36 'name' => 'name', 37 'filters' => array( 38 array( 39 'name' => 'StringTrim' 40 ) 41 ), 42 'validators' => array( 43 array( 44 'name' => 'NotEmpty', 45 'options' => array( 46 'messages' => array( 47 \Zend\Validator\NotEmpty::IS_EMPTY => 48 'Bitte geben Sie etwas ein.' 49 ) 50 ) 51

) 52 ) 53 )); 54 } 55 } Listing16.10 Ifwenowsendtheformwithoutenteringanemailaddress,webecomethefollowingerrormessage : 1 Please input something. 2 Please enter the correct email address. Ortobemoreexact:Wesee2errormessagesatthesametime.Oneshouldknowthatinthe courseofprocessingthevalidatorsareprocessedoneafteranotherandanyoccurringerrorme ssages arecollected.Thenalltheerrormessagesaredisplayedwhentheappropriate ViewHelper is employed.Inmostcases,however,thatisnotreallyhelpfulfortheuser;instead,asingleerr or messagewouldbeadequate.Toensurethis,the'break_chain_on_failure' => true optioncanbe set.Itinsuresthat,afteravalidatorfailure,thesubsequentonesarenotimplementedatall andno possibleadditionalerrormessagesaregenerated:

Webforms 173 <?php namespace Helloworld\Form; use Zend\Form\Form; use Zend\InputFilter\InputFilter; class SignUpFilter extends InputFilter { public function __construct() { $this->add(array( 'name' => 'email', 'validators' => array( array( 'name' => 'NotEmpty', 'break_chain_on_failure' => true, 'options' => array( 'messages' => array( \Zend\Validator\NotEmpty::IS_EMPTY => 'Bitte

geben Sie etwas ein.' ) ) ), array( 'name' => 'EmailAddress', 'options' => array( 'messages' => array( \Zend\Validator\EmailAddress::INVALID_FORMAT => 'Bitte richtige E-Mail-Adresse eingeben.' ) ) ), ), )); $this->add(array( 'name' => 'name', 'filters' => array( array( 'name' => 'StringTrim' ) ),

Webforms 174 43 'validators' => array( 44 array( 45 'name' => 'NotEmpty', 46 'options' => array( 47 'messages' => array( 48 \Zend\Validator\NotEmpty::IS_EMPTY => 49 'Bitte geben Sie etwas ein.' 50 ) 51 ) 52 ) 53 ) 54 )); 55 } 56 } Listing16.1164 Sincetheindividualvalidatorscangeneratedifferenterrormessagesdependingonthesitua tion,the messagemustbedepositedattheappropriatekey,whichisaccessibleviaaconstantintheresp ective class.Ifyouhaveanydoubts,alookatthecodeoftherespectivevalidatoralsohelps. Standard form

elements Asshowninthepreviousexamples,onecangenerateanyarbitraryHTMLelementsbychoosingthe appropriatefiltersandvalidators.However,dependingontheelement,thisprocedurecanre quirea lotofeffort,particularlybecausealltheconfigurationsmustbemademanually.Forthissit uation, Frameworkhasanumberofoptionsstillopen:alargenumberof preconfigured elements,which immediatelyprovidethenecessaryconfiguration. Button Captcha Checkbox Collection Color Csrf Date DateTime DateTimeLocal Email 64https://gist.github.com/3920507

Webforms 175 File Hidden Image Month MultiCheckbox Number Password Radio Range Select Submit Text Textarea Time URL Week TakeforexampletheNumber element. Wewouldliketogenerateaninputfieldinwhichthe numericalvaluesofadefinedrangemaybeentered.Ifweweretosetuptheserulesourselves, wewouldhavetoconfigurealargenumberofvalidators,amongthem: NumberValidator:onlynumberscanbeentered. GreaterThanValidator:theenteredvaluemustbemorethanorequaltotheminimum. LessThanValidator:theenteredvaluemustbelessthanorequaltothemaximum. StepValidator:onlywhole-numberedvaluesareaccepted. WecandispensewiththisworkwhenweusetheZend\Form\Element\Number directly: 1 <?php 2 // [..] 3 $this->add(array( 4 'name' => 'age', 5 'type' => 'Zend\Form\Element\Number', 6 'attributes' => array( 7 'id' => 'age', 8

'min' => 18, 9 'max' => 99, 10 'step' => 1 11 ), 12 'options' => array(

Webforms 176 13 'label' => 'Wieviel Jahre sind sie alt?' 14 ), 15 )); 16 // [..] Listing16.12 DependingonthebrowseranditsHTML5support,itbecomesimmediatelyobviousthat,when usingtherules,notonlythecheckontheserverfunctionswell,buterroneousentriesarequer ied directlyintheclient.Indeed,theconfigurationintheattributes arrayisnotonlysubsequently consideredbytheappropriateviewhelperduringthegenerationoftheHTMLcode,butisalsous ed bythevalidators. Incidentally,theNumber elementalsosimultaneouslyattachesaZend\Filter\StringTrim suchthat thisnolongerhastooccurinone sowncode. Fleldsets Themoreattentiveamongushaveperhapsalreadynoticedthatwehavenotcompletelyrecreated theoriginalwebform,whichwegeneratedmanually,withourobjectversion.Thefleldsetismi ssing. AtthistimeyoushouldalreadyrealisethatwhenIusetheterm fleldsets inthefollowing,Idonot explicitlymeanthepreviouslymentioned,lackingHTMLfleldset,butrather fleldsets inthese nse oftheZendFrameworkdefinition,whichinitiallymustnotfundamentallyhaveanythingtodow ith HTMLfleldsets,butcanrefertothem,asrequired.Thisdifferentiationiscruciallyimporta ntfor yourcomprehension. A fleldset servestogroupindividualfields.

Thisisparticularlyappropriatewhenaformis composedofelementshavingdifferentrequirements.Forexample,ifweweretoexpandtheform developedabovefortheaddressoftheuser,thedatawouldprobablybeadministeredinanentit y ofitsown,storedinadatabasetableofitsown,andreferencedbythe user entityortableproper. Thetechnicaladvantageoffleldsetsisthefactthattheycanbereusedindifferentforms.Ifw estick toourexample,thismeansthattheuserwouldstatehisorheraddressinthescopeofthenewslet ter registration(let sjustpretendthatthiswouldbeappropriate),butcouldalsobesubsequentl yadapted tothecustomeraccountviatheAddress-ChangeForm.Inbothcases,thefleldsetthatwasdefin ed oncewouldbeused.Let slookatafleldsetdefinitionforbothfieldsoftheforminanexemplary manner:

Webforms 177 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Fieldset; 6 class UserFieldset extends Fieldset 7 { 8 public function __construct() 9 { parent::__construct('user'); 11 12 $this->add(array( 13 'name' => 'name', 14 'attributes' => array( 'type' => 'text', 16 'id' => 'name' 17 ), 18 'options' => array( 19 'id' => 'name', 'label' => 'Ihr Name:',

21 ) 22 )); 23 24 $this->add(array( 'name' => 'email', 26 'attributes' => array( 27 'type' => 'email', 28 ), 29 'options' => array( 'id' => 'email', 31 'label' => 'Ihre E-Mail-Adresse:' 32 ), 33 )); 34 } } Listing16.1365 Thedefinitionofthefleldsethasgreatsimilaritywiththatoftheformdevelopedearlier,ex ceptfor thefactthatthefleldsetisnolongerpresentedaloneasawebformforexternalpurposes,buti nstead hastobeincorporatedintoaZend\Form inordertobedisplayed: 65https://gist.github.com/3922738

Webforms 178 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Form; 5 6 class SignUp extends Form 7 { 8 public function __construct() 9 { 10 parent::__construct('signUp'); 11 $this->setAttribute('action', '/signup'); 12 $this->setAttribute('method', 'post'); 13 $this->setInputFilter(new \Helloworld\Form\SignUpFilter()); 14 15 $this->add(new \Helloworld\Form\UserFieldset()); 16 17 $this->add(array( 18 'name' => 'submit', 19 'attributes' => array( 20 'type' => 'submit', 21 'value'

=> 'Eintragen' 22 ), 23 )); 24 } 25 } Listing16.1466 Thus,hereistheSignUp Formagain,butthistimewiththeUserFieldset,insteadofitsindividual fields,whicharenowgroupedintheUserFieldset.Toensurethattheformwillstillbecorrect ly displayed,westillhavetoadapttheviewcode: 1 <?php 2 $this->form->prepare(); 3 echo $this->form()->openTag($this->form); 4 echo $this->formRow($this->form->get('user')->get('name')); 5 echo $this->formRow($this->form->get('user')->get('email')); 6 echo $this->formSubmit($this->form->get('submit')); 7 echo $this->form()->closeTag(); Listing16.15 66https://gist.github.com/3922739

Webforms 179 Wenowinitiallyaccessthefleldsetviaget('user')andfromthereaccesstheelementsofthe fleldset.Ifwedonotmakethisadjustmentnow,wehavemadeanerror.Sofarsogood.But wearestillnotyetcompletelyfinished,becauseinasmuchasourformisnownolongercorrectl y validatedbecausetheSignUpFilter isstillassignedtothewebform,buttheconfigurationsthere arenolongerappropriate. 1 <?php 2 // [..] 3 $this->setInputFilter(new \Helloworld\Form\SignUpFilter()); 4 // [..] Listing16.16 Now,wemustthusensurethattheUserFieldset itselfhastherequiredconfiguration.Wedothat byprovidingtheUserFieldset withthegetInputFilterSpecification() methodandmakethe requiredconfigurationsthere: 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Fieldset; 5 6 class UserFieldset extends Fieldset 7 { 8 public function __construct() 9 { 10 parent::__construct('user'); 11 12 $this->add(array( 13 'name'

=> 'name', 14 'attributes' => array( 15 'type' => 'text', 16 'id' => 'name' 17 ), 18 'options' => array( 19 'id' => 'name', 20 'label' => 'Ihr Name:', 21 ) 22 )); 23 24 $this->add(array( 25 'name' => 'email', 26 'attributes' => array(

Webforms 180 27 'type' => 'email', 28 ), 29 'options' => array( 30 'id' => 'email', 31 'label' => 'Ihre E-Mail-Adresse:' 32 ), 33 )); 34 } 35 36 public function getInputFilterSpecification() 37 { 38 return array( 39 'email' => array( 40 'validators' => array( 41 array( 42 'name' => 'NotEmpty', 43 'break_chain_on_failure' => true, 44 'options'

=> array( 45 'messages' => array( 46 \Zend\Validator\NotEmpty::IS_EMPTY => 47 'Bitte geben Sie etwas ein.' 48 ) 49 ) 50 ), 51 array( 52 'name' => 'EmailAddress', 53 'options' => array( 54 'messages' => array( 55 \Zend\Validator\EmailAddress::INVALID_FORMAT 56 => 'Bitte richtige E-Mail-Adresse eingeben.' 57 ) 58 ) 59 ), 60 ), 61 ), 62 'name' => array( 63 'filters' =>

array( 64 array( 65 'name' => 'StringTrim' 66 ) 67 ), 68 'validators' => array(

Webforms 181 69 array( 70 'name' => 'NotEmpty', 71 'options' => array( 72 'messages' => array( 73 \Zend\Validator\NotEmpty::IS_EMPTY => 74 'Bitte geben Sie etwas ein.' 75 ) 76 ) 77 ) 78 ) 79 ) 80 ); 81 } 82 } Listing16.1767 So,whathavewenowaccomplishedhere?WetookthedefinitionsoutoftheSignUpFilter and transferredthemdirectlyintothegetInputFilterSpecification() methodofthefleldset.This isnecessarybecausethefleldset ssupraordinateformreceivesallofits InputFilter specifica tions fromthegetInputFilterSpecification() methodsofthereferencedfleldsets. Now,weobtainthefollowing,desiredresultiftheformhasbeensentwithvalidinputs. 1 array(2) { 2 'submit' =>

string(9) "Eintragen" 3 'user' => array(2) { 4 'name' => string(13) "Michael Romer" 5 'email' => string(24) "zf2buch@michael-romer.de" 6 } 7 } Linking entities with Forms Asarule,anapplication swebformsarerelatedtoitsentities.Aproducttogetherwithinforma tion astoquantityisplacedintheshoppingbasketandthusanorderisgenerated,auserisregister ed onlogin,theshippingaddressisregisteredinthescopeofthecheckout,etc.Forthisreasono ne isfrequentlyengagedintransferringthevalidateddatafromawebformtotheappropriateent ity, whichthen,forexample,persistsinthedatabase.Or,theotherwayaround,datafromthedatab ase aretransferredtoanentityinawebform,forexample,tomakeitscharacteristicseditableth ere. 67https://gist.github.com/3922744

Webforms 182 Tosimplifythisprocedure,so-called hydrators ,whichwehavealreadybecomeacquaintedwith inthescopeofourworkwithZend\Db,areused.Thus,onecouldsaythatwehavenowcomefull circle.Initially,weneedtheappropriateUser entityfortheform: 1 <?php 2 namespace Helloworld\Entity; 3 4 class User 5 { 6 protected $id; 7 protected $email; 8 protected $name; 9 10 public function setEmail($email) 11 { 12 $this->email = $email; 13 } 14 15 public function getEmail() 16 { 17 return $this->email; 18 } 19 20 public function

setId($id) 21 { 22 $this->id = $id; 23 } 24 25 public function getId() 26 { 27 return $this->id; 28 } 29 30 public function setName($name) 31 { 32 $this->name = $name; 33 } 34 35 public function getName() 36 { 37 return $this->name; 38 } 39 }

Webforms 183 Listing16.1868 Inorderforthisentitytobeusedasa datacontainer bytheSignUp form,onemustperformthe requiredconfigurationforthisatthecorrespondinglocation.Inthisexample,wenowinitia llyagain workwithoutfleldsets.Thus,wearedealingwitha normal forminwhichtheindividualelements areactuallylocated. 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Form; 5 6 class SignUp extends Form 7 { 8 public function __construct() 9 { 10 parent::__construct('signUp'); 11 $this->setAttribute('action', '/signup'); 12 $this->setAttribute('method', 'post'); 13 14 $this->add(array( 15 'name' => 'name', 16 'attributes' => array( 17 'type' => 'text', 18 'id'

=> 'name' 19 ), 20 'options' => array( 21 'id' => 'name', 22 'label' => 'Ihr Name:', 23 ) 24 )); 25 26 $this->add(array( 27 'name' => 'email', 28 'attributes' => array( 29 'type' => 'email', 30 ), 31 'options' => array( 32 'id' => 'email', 33 'label' => 'Ihre E-Mail-Adresse:' 34 ), 35 )); 68https://gist.github.com/3923101

Webforms 184 36 37 $this->add(array( 38 'name' => 'submit', 39 'attributes' => array( 40 'type' => 'submit', 41 'value' => 'Eintragen' 42 ), 43 )); 44 } 45 } Listing16.196. Forsimplicity ssake,wewillneglectthedefinitionsofvalidatorsandfiltersatthistime.We now linkanentityinthecontrollerandconfigureittotheemployedhydrator: 1 <?php 2 3 namespace Helloworld\Controller; 4 5 use Zend\Mvc\Controller\AbstractActionController; 6 use Zend\View\Model\ViewModel; 7 8 class IndexController extends AbstractActionController 9 { 10 public function

indexAction() 11 { 12 $form = new \Helloworld\Form\SignUp(); 13 $form->setHydrator(new \Zend\Stdlib\Hydrator\Reflection()); 14 $form->bind(new \Helloworld\Entity\User()); 15 16 if ($this->getRequest()->isPost()) { 17 $form->setData($this->getRequest()->getPost()); 18 19 if ($form->isValid()) { 20 var_dump($form->getData()); 21 } else { 22 return new ViewModel( 23 array( 24 'form' => $form 25 ) 6.https://gist.github.com/3923105

Webforms 185 26 ); 27 } 28 } else { 29 return new ViewModel( 30 array( 31 'form' => $form 32 ) 33 ); 34 } 35 } 36 } Listing16.207 Ifwenowsendthewebformwithvalidateddata,acompletelyfilledentityisreturnedvia $form->getData() (insteadofanarrayaswaspreviouslythecase): 1 class Helloworld\Entity\User#186 (3) { 2 protected $id => NULL 3 protected $email => string(24) "zf2buch@michael-romer.de" 4

protected $name => string(13) "Michael Romer" 5} Theessentialconfigurationistheinvocationofbind(),inwhichweconsigntheobject(orits class, respectively)totheform,intowhichthedataistobetransferredwiththeaidofthepreviousl y definedReflection hydrator.Thefollowing hydrators areincludedinFrameworkasstandard. ArraySerializable:Thisisthestandardhydrator,whichZend\Form usesifnothingelse hasbeendefined.ItexpectsthattherespectiveobjectimplementsthegetArrayCopy() and exchangeArray() orpopulate(),respectively,andmakestherequiredinformationavailable inthismanner. ClassMethods:usestheobject sGetter/Settermethods(ortheclass,respectively)toinsert (hydrate())orreadout(extract()),respectively,theappropriatedata.. ObjectProperty:usestheobject spublicproperties. Reflection:usesPHP sReflectionClass todeterminetheobject spropertiesandtosetor readouttheappropriatevalues.Sincethishydratormakesuseof$property->setAccessible( true), private propertiescanalsobemanagedinthismanner. Andnowlet slookateverythingagainusingfleldsetsbecausethereisalsoausefulfeatureinth is contextwhenoneworkswithseveralobjectswhichreferenceoneanother.Tobeginwith,wecrea te 7https://gist.github.com/3923106

Webforms 186 anotherentity,whichwecall UserAddress andinwhichwemaptheuser saddress,whichwe wouldliketoquerydirectlyinthescopeofthelogin.Thedatashouldbemanagedintheapplicat ion, butalsobeindependentlytreatedasanentityandalsosubsequentlybestoredinthedatabasei na tableoftheirown. 1 <?php 2 namespace Helloworld\Entity; 3 4 class UserAddress 5 { 6 private $street; 7 private $streetNumber; 8 private $zipcode; 9 private $city; 10 11 public function setStreet($street) 12 { 13 $this->street = $street; 14 } 15 16 public function getStreet() 17 { 18 return $this->street; 19 } 20

21 public function setCity($city) 22 { 23 $this->city = $city; 24 } 25 26 public function getCity() 27 { 28 return $this->city; 29 } 30 31 public function setStreetNumber($streetNumber) 32 { 33 $this->streetNumber = $streetNumber; 34 } 35 36 public function getStreetNumber() 37 { 38 return $this->streetNumber;

Webforms 187 39 } 40 41 public function setZipcode($zipcode) 42 { 43 $this->zipcode = $zipcode; 44 } 45 46 public function getZipcode() 47 { 48 return $this->zipcode; 49 } 50 } Listing16.217 WeextendtheUser entitybythe$userAddress property,whichsymbolisesthereferencetothe appropriateUserAddress entity. 1 <?php 2 namespace Helloworld\Entity; 3 4 class User 5 { 6 protected $id; 7 protected $email; 8 protected $name;

9 protected $userAddress; 10 11 public function setEmail($email) 12 { 13 $this->email = $email; 14 } 15 16 public function getEmail() 17 { 18 return $this->email; 19 } 20 21 public function setId($id) 22 { 23 $this->id = $id; 7https://gist.github.com/3924042

Webforms 188 24 } 25 26 public 27 { 28 29 } 30 31 public 32 { 33 34 } 35 36 public 37 { 38 39 } 40 41 public 42 { 43 44 } 45 46 public 47 { 48 49 } 50 } Listing16.227 function getId() return $this->id; function setName($name)

$this->name = $name; function getName() return $this->name; function setUserAddress($userAddress) $this->userAddress = $userAddress; function getUserAddress() return $this->userAddress; Additionally,wegeneratethenewUserAddressFieldset togetherwiththerequiredfilterspecification, whichwearealreadyfamiliarwith,aswellasthereferencetothehydratorthatistobeused andtheappropriateentity,intowhichthisfleldset sdataistobetransferred. 7https://gist.github.com/3924043

Webforms 189 <?php namespace Helloworld\Form; use Zend\Form\Fieldset; class UserAddressFieldset extends Fieldset { public function __construct() { parent::__construct('userAddress'); $this->setHydrator(new \Zend\Stdlib\Hydrator\Reflection()); $this->setObject(new \Helloworld\Entity\UserAddress()); $this->add(array( 'name' => 'street', 'attributes' => array( 'type' => 'text', ), 'options' => array( 'label' => 'Ihre Strasse:', ) ));

$this->add(array( 'name' => 'streetNumber', 'attributes' => array( 'type' => 'text', ), 'options' => array( 'label' => 'Ihre Hausnummer:', ) )); $this->add(array( 'name' => 'zipcode', 'attributes' => array( 'type' => 'text', ), 'options' => array( 'label' => 'Ihre Postleitzahl:', ) ));

Webforms 190 43 44 $this->add(array( 45 'name' => 'city', 46 'attributes' => array( 47 'type' => 'text', 48 ), 49 'options' => array( 50 'label' => 'Ihre Stadt:', 51 ) 52 )); 53 } 54 } Listing16.237 WeincorporatetheUserAddressFieldset appropriately,butnotdirectlyintheSignUp form,but nested intheUserFieldSet instead: 1 <?php 2 namespace Helloworld\Form; 3 4 use Zend\Form\Fieldset; 5 6 class UserFieldset extends Fieldset

7 { 8 public function __construct() 9 { 10 parent::__construct('user'); 11 12 $this->add(array( 13 'name' => 'name', 14 'attributes' => array( 15 'type' => 'text', 16 'id' => 'name' 17 ), 18 'options' => array( 19 'id' => 'name', 20 'label' => 'Ihr Name:', 21 ) 22 )); 23 7https://gist.github.com/3924047

Webforms 191 24 $this->add(array( 25 'name' => 'email', 26 'attributes' => array( 27 'type' => 'email', 28 ), 29 'options' => array( 30 'id' => 'email', 31 'label' => 'Ihre E-Mail-Adresse:' 32 ), 33 )); 34 35 $this->add(array( 36 'type' => 'Helloworld\Form\UserAddressFieldset', 37 ) 38 ); 39 } 40 } Listing16.2474 Theactualformnowlookslikethis: 1 <?php 2 namespace Helloworld\Form;

3 4 use Zend\Form\Form; 5 6 class SignUp extends Form 7 { 8 public function __construct() 9 { 10 parent::__construct('signUp'); 11 $this->setAttribute('action', '/signup'); 12 $this->setAttribute('method', 'post'); 13 14 $this->add(array( 15 'type' => 'Helloworld\Form\UserFieldset', 16 'options' => array( 17 'use_as_base_fieldset' => true 18 ) 19 )); 74https://gist.github.com/3924051

Webforms 192 20 21 $this->add(array( 22 'name' => 'submit', 23 'attributes' => array( 24 'type' => 'submit', 25 'value' => 'Eintragen' 26 ), 27 )); 28 } 29 } Listing16.2575 Inthiscase,theimportantthingisthe'use_as_base_fieldset' => true,sothattheassignment ofvaluesandobjectpropertiesfunctionscorrectly,andproceedingfromtheUserFieldset thatthe dataaredistributedacrosstheappropriateentities. Andfinally,don tforgettoadjustthefleldset sviewfileappropriatelysuchthatthefieldsare all correctlydisplayedandthatnoerrorsoccur: 1 <?php 2 $this->form->prepare(); 3 echo $this->form()->openTag($this->form); 4 5 echo $this->formRow($this->form->get('user') 6 ->get('name')); 7 8 echo $this->formRow($this->form->get('user') 9

->get('email')); 10 11 echo $this->formRow($this->form->get('user') 12 ->get('userAddress')->get('street')); 13 14 echo $this->formRow($this->form->get('user') 15 ->get('userAddress')->get('streetNumber')); 16 17 echo $this->formRow($this->form->get('user') 18 ->get('userAddress')->get('zipcode')); 19 20 echo $this->formRow($this->form->get('user') 21 ->get('userAddress')->get('city')); 22 75https://gist.github.com/3924059

Webforms 193 23 echo $this->formSubmit($this->form->get('submit')); 24 echo $this->form()->closeTag(); Listing16.2676 Ifwenowsendtheformwithvaliddata,wenolongerreceiveonlytheUser object,butalsothe filled-out,referencedUserAddress object: 1 class Helloworld\Entity\User#201 (4) { 2 protected $id => NULL 3 protected $email => string(24) "zf2buch@michael-romer.de" 4 protected $name => string(13) "Michael Romer" 5 protected $userAddress => 6 class Helloworld\Entity\UserAddress#190 (4) { 7 private $street => string(14) "Grevingstrasse" 8 private $streetNumber => string(2) "35"

9 private $zipcode => string(5) "48151" 10 private $city => string(8) "Mnster" 11 } 12 } FurthersimplifyprocessingbymeansofannotationsThesketched-outpathfortheprocessing offormshasalreadymadetheapplicationdeveloper slifedistinctlyeasier.Nevertheless,agooddealofcodeisstillrequiredtoperformtheconfigurations.Analternativewayistheuseofannotationsintheentit yclasses,fromwhichalargepartoftheotherwisemanuallygeneratedconfigurationcanbedyn amicallygeneratedbyFramework.Furtherinformationonthistopiccanbefound,asneeded,in theofficialFrameworkdocumentation. 76https://gist.github.com/3924070

Developers Dairy Introduction Uptothispoint,wemainlytalkedabouttheframework scoreconceptsanditsindividual components.Now,wewilljumpstraightintoanexampleapplicationandtacklethechallenges oftheday-to-daybusinesswithZendFramework2. Fordevelopmentoftheexampleapplicationwewillusethe Scrum method,wheneverpossible. Scrumisaniterativeandincrementalagilesoftwaredevelopmentframeworkformanagingsoft ware projectsandproductorapplicationdevelopment. IadoptedagilesomeyearsagoandScrum specificallyaround2008.IthinkScrumreallyishelpingmasteringtheartofprofessionalso ftware developmentandithelpedmealotinmyprofessionalprojects.ThisiswhyIhighlyrecommend consideringScrumwheneverpossible.It ssimplynotenoughtoonlywritegoodcodee.g.byusing ZendFramework2tosucceedinacomplexsoftwareproject.Youwillalsoneedtoorganizeyourse lf. Westartwiththesocalled Envisioning whichhelpsshapingtheideaofthefinalproductwe ll developthenwitheachsocalled Sprints ;fixedwindowsofdevelopmenttime. Envisioning Inthefollowing,wewanttoimplementthecorefunctionalityof ZfDeals .ZfDealsisanapplicati on forsellingproductsonlinetospecialreducedprices. Theideaistonotdevelopastand-alone application,butaZF2modulethatmaybeusedbyothersintheirapplications.Fordemonstrati on anddevelopmentpurposes,wewillcomeupwithasample hostapplication forthemoduleas well. Sprint 1 -Code Repository, Development Environment and the initial Codebase ThefirstSprintismeanttogetmyselfreadyfordevelopment. Set

up git repository and first commit First,Ineedacoderepositorytomaintainmycode.Iwillwanttoswitchbetweenversions,bran ch andmergeandsimplyknowmycodemanagedwell.IchoosegitlocallyaswellasGitHubasmy externalrepositoryIcanpushcodetoregularly.IalreadyhaveaGitHubaccount,soIdon tneed toregisteragain.However,itonlytakesminutestosignupandbythewayit sforfreeifoneuses itforopensourceprojects.Nice! 194

Developers Dairy Gitisalreadyinstalledonmylocalbox,soIcando 1 $ git clone https://github.com/zendframework/ 2 ZendSkeletonApplication.git ZfDealsApp inadirectoryofmychoicetoclonethe ZendSkeletonApplication thatwillactasthestartingpoi nt formyindividualcoding. ComposerdownloadsZendFramework2intothevendor directorysimplybyexecuting: 1 $ cd ZfDealsApp 2 $ php composer.phar install Now,IsetupanewgitrepositoryonGitHub.Itactsasacodebackuptomylocalrespositoryas wellitmakessharingthecodewithotherseasy.First,Iremovethecurrentoriginreferencefr om mylocalrepository 1 $ git remote rm origin andIreplaceitwiththenewGitHubrepository: 1 $ git remote add origin

https://github.com/michael-romer/ZfDealsApp.git Asimple 1 $ git push -u origin master pushesthecodetotherepositoryonGitHub77. Local development environment InsteadofsettingupApache,PHPandallothersoftwareonmylocalbox,Icreateavirtualmachi ne actingasmylocalruntimeenvironment.IuseacodelibraryavailableinmyGitHub-Account78. AgainComposerhelpstodownloadandinstallitbyaddingitasadependencytomycomposer.json : 77https://github.com/michael-romer/ZfDealsApp 78https://github.com/michael-romer/zfb2-vm

Developers Dairy 196 1{ 2 "name": "zendframework/skeleton-application", 3 "description": "Skeleton Application for ZF2", 4 "license": "BSD-3-Clause", 5 "keywords": [ 6 "framework", 7 "zf2" 8 ], 9 "homepage": "http://framework.zend.com/", 10 "require": { 11 "php": ">=5.3.3", 12 "zendframework/zendframework": "dev-master", 13 "zfb/zfb-vm": "dev-master" 14 } 15 } Executing 1 $ php composer.phar update

ontheshellthendownloadsthelibrary.LastbutnotleastIneedtocopyandrename/vendor/zfb /zfb-vm/Vagrantfile.to/Vagrantfile andinstallsometoolsonmylocalbox: VirtualBox7. Ruby8 Vagrant8 Yes,it sabitofwork,however,it saone-time-effortandoncedone,onecaneasilyspinupnew virtualmachinescapableofrunningZendFramework2applicationsinamatterofminutes.This is helpfule.g.ifyouworkondifferentprojectsatthesametime,especially,ifaslightlydiffe rent runtimeconfigurationisneeded,suchasadifferentPHPversion. Furthermore,settingupthe runtimeenvironmentonmylocalboxwouldnothavebeenfasteranyway. Now,twomoreshellcommandsbeforegoingtoanextendedlunch: 1 $ vagrant box add precise64 http://files.vagrantup.com/precise64.box 2 $ vagrant up Oncethevirtualmachinehasbeencreated, Via 7.https://www.virtualbox.org/wiki/Downloads 8http://www.ruby-lang.org/de/ 8http://vagrantup.com/ onemayopenlocalhost:8080.

Developers Dairy 1 $ vagrant ssh orbyusingPuttyifyouhaveawindowsbox8,onemayconnecttothevirtualmachine sshell(use exit togetoutagain).Onthevirtualmachine,the/vagrant-folderissharedbetweenthehost systemandthevirtualmachine,allowingyoutowritecodewithintheIDEofyourchoiceonyour localboxandatthesametimeputthecodeintoexecutiononthevirtualmachine sLAMPstack. Asyoumyhavenoticed,port8080ofyourlocalboxhasbeenconfiguredtoforwardtoport80of thevirtualmachine. Ifyoustopdevelopingforawhile,youmayrun 1 $ vagrant suspend toputthevirtualmachineintostandby.Youcanwakeitupagainrunning 1 $ vagrant resume Ifyoudon tneedtheVMforlonger,youmayhaltthesystemvia 1 $ vagrant halt andrestartitvia 1 $ vagrant up RestartingastoppedVMtakeslongerthanresumingasuspendedone. Forsure,avirtualmachineisnotneededtoworkwithZendFramework2.Itabsolutelyfinetouse

XAMPP8orsetupyourownLAMP-likestackbyhanddirectlyonyourbox.Justmakesureat somepointyouhaveaproperconfiguredPHP5.3.3web-environmentavailabletorunyourcode in.Makesurealsotoconfigureyouwebservertotreattheapplication spublic directoryasits DocumentRoot .Thisiswheretheindex.php fileisstoredwhichservesasthe mainentrance forallfunctionsofaZF2application. Atsomepointyouhopefullywillthensee 1 "Welcome to Zend Framework 2" onyourscreen.ItmeansyousuccessfullyfinishedSprint1! 8http://vagrantup.com/v1/docs/getting-started/ssh.html 8http://www.apachefriends.org/xampp.html

Developers Dairy Sprint 2 -A custom module with add product functionality SourceCodedownloadThesourcecodeofSprint2canbedownloadedusingTag Sprint2 onGitHub.. .https://github.com/michael-romer/ZfDealsApp/tree/Sprint2 User Stories WhileSprint1wasfocusedonsettinguptheinitialcodebaseandthedevelopmentenvironment, Sprint2bringsthefirstfunctionalrequirementontothetable:Addingnewproductstothesys tem thatmaybesoldlaterataspecialdiscountprice.InScrum,requirementsareusuallygivenbyu sing atechniquecalled userstories : Insoftwaredevelopmentandproductmanagement,auserstoryisoneormore sentencesintheeverydayorbusinesslanguageoftheenduseroruserofasystemthat captureswhatauserdoesorneedstodoaspartofhisorherjobfunction. (Wikipedia) Usually,thesentencesfollowthepattern: Inorderto[receivebenefit]asa[role],Iwant[goal/desire] Therefore,thefirstrequirementreadsasfollows: Inordertoofferaproductwithaspecialdiscountpriceasamerchant,Iwanttoadd productdetailstothesystem. Thissoundsreasonable.Usually,inadditionthe userstorysentence onewillwanttostateso calledacceptancecriteriathatgointomoredetailontherequirements: OnemayaddauniqueID,descriptionandstockinformationperproduct. Onemayaddallproductdatausingawebform. Create a custom module

So,let sgo!First,IaddanewcustomZF2modulethatholdsallfunctionalityofZfDeals.Isetup thefollowingdirectoryandfilestructureinmodule:

Developers Dairy 1 ZfDeals/ 2 Module.php 3 config/ 4 module.config.php 5 src/ 6 ZfDeals/ 7 Controller/ 8 AdminController.php 9 view/ 10 zf-deals/ 11 admin/ 12 index.phtml 13 layout/ 14 admin.phtml ZFToolInsteadofsettingupthedirectoryandfilestructureallbyhand,youcoulduseZFTool. andletitcreatemostofthedirectoriesandfilesautomatically.However, ZFToolisnotbundledwiththeZF2libraryandmustbedownloadedseparately. .https://github.com/zendframework/ZFTool InModule.php Ionlyaddthemostbasiccodeforautoloadingthemodule sclassesandpointingthe framework sModuleManager tothemodule sconfigfile: 1 <?php 2 namespace ZfDeals; 3 4 class Module 5 { 6 public function getConfig() 7 { 8

return include __DIR__ . '/config/module.config.php'; 9 } 10 11 public function getAutoloaderConfig() 12 { 13 return array( 14 'Zend\Loader\StandardAutoloader' => array(

Developers Dairy 15 'namespaces' => array( 16 __NAMESPACE__ 17 => __DIR__ . '/src/' . __NAMESPACE__, 18 ), 19 ), 20 ); 21 } 22 } Listing26.1 MybarenewAdminController isbasedonAbstractActionController,allowingtoworkwith custom actions : 1 <?php 2 namespace ZfDeals\Controller; 3 4 use Zend\Mvc\Controller\AbstractActionController; 5 use Zend\View\Model\ViewModel; 6 7 class AdminController extends AbstractActionController 8 { 9 public function indexAction() 10 { 11 return new

ViewModel(); 12 } 13 } Listing26.2 Thefilemodule.config.php isstraightforward.ItholdsaliteralroutetotheZfDeals adminsection homepage 1 <?php 2 return array( 3 'router' => array( 4 'routes' => array( 5 'zf-deals\admin\home' => array( 6 'type' => 'Zend\Mvc\Router\Http\Literal', 7 'options' => array( 8 'route' => '/deals/admin', 9 'defaults' => array( 10 'controller'

Developers Dairy 201 11 => 'ZfDeals\Controller\Admin', 12 'action' 13 => 'index', 14 ), 15 ), 16 ), 17 ), 18 ), 19 // [..] 20 ) Listing26.3 aswellastheAdminController declaration 1 <?php 2 // [..] 3 'controllers' => array( 4 'invokables' => array( 5 'ZfDeals\Controller\Admin' 6 => 'ZfDeals\Controller\AdminController' 7 ), 8 ), 9 // [..] Listing26.4 andtheadmin layoutdeclaration.InclassModule Imakesurethatthislayoutisusedwheneverthe AdminController isdispatched:

1 <?php 2 // [..] 3 public function init(\Zend\ModuleManager\ModuleManager $moduleManager) 4 { 5 $sharedEvents = $moduleManager 6 ->getEventManager()->getSharedManager(); 7 $sharedEvents->attach( 8 'ZfDeals\Controller\AdminController', 9 'dispatch', 10 function($e) { 11 $controller = $e->getTarget(); 12 $controller->layout('zf-deals/layout/admin'); 13 },

Developers Dairy 202 14 100 15 ); 16 } 17 // [..] Listing26.5 Iregisteracallbackfunctionforthedispatch eventinmethodinit.Thedispatch eventisemitted bytheZfDeals\Controller\AdminController whendispatchedsothecallbackisexecuted.The reasontheSharedEventManager isusedhereissimple:Atthepointtime,whenIattachmyevent handler,theAdminController itselfwithitscomposedownEventManager hasnotbeeninstantiated yet.SoIwillneedtotogothroughtheSharedEventManager heretoattachmyhandler. Thecallbackfunctionitselfusesthelayout controllerplugintosetthelayouttotheonedefinedin module.config.php: 1 <?php 2 // [..] 3 'view_manager' => array( 4 'template_map' => array( 5 'zf-deals/layout/admin' => __DIR__ . '/../view/layout/admin.phtml', 6 ), 7 'template_path_stack' => array( 8 __DIR__ . '/../view', 9 ), 10

), 11 // [..] Listing26.6 Don tforgettoactivatethenewmodulebyaddingittothelistofmodulesinapplication.config. php: 1 <?php 2 return array( 3 'modules' => array( 4 'Application', 5 'ZfDeals' 6 ), 7 // [..] 8 ); Listing26.7 Afteraddinganemptyactionmethodcalledindex tothenewcontrolleraswellascreatingaview file,Icanopen/deals/admin inabrowserandseetheadminlayoutdesignonthescreen.

Developers Dairy Dealing with static assets Atthisstage,I maskingmyselfhowtodealwithstaticassetsmymodulewillcontain,likeimages suchastheZfDeals-logo,cssandjsfiles.Ifonewantstoserveassetstoaclient,ingeneral,t heyneed tobepubliclyavailable,e.g.byputtingthemsomewhereinthepublic-folderofthehostappli cation. Thismeans,ifIshipmyassetsbundledwiththeZfDealsmodule,theywon tbeavailablebydefault . So,whattodo?Unfortunately,ZF2doesnotbringanysupport out-of-the-box andoneneedsto becreativetosolvethisissue.Herearetheoptions: Idevelopacontrollerwithinmymodule,thatservesstaticassetsviaPHPbyusingfile_get_ contents() orsimilar.Thisprobablywillworkfine,however,itisn tverysmarttoadd such infrastructure codetoZfDealsandifIdon timplementamechanismofcaching,itwill surelybecomeaperformancebottleneckatsomestage. IuseanassetmanagerlibraryforZF2suchasAssetManager84andaddthismoduletothe listofdependenciesofZfDeals.However,thiswouldrequiremetoinstallanothermodule besidesZfDealsitselftomakethingsworkproperly. Isimplycopyallassetsovertothepublic directoryofthehostapplication.However,this means,whendistributingthemodule,Iwillneedtoinstructthedeveloperonhowtointegrate ZfDealinhisownapplicationbycopyingoverfilesmanually. Isimplycopytheassetsovertothepublic directoryofthehostapplication.Butinstead ofputtingthemdirectlyintothepublic directory,Iaddanotherdirectorynamedafterthe modulefirst.However,thismeans,whendistributingthemodule,Iwillneedtoinstructthe developeronhowtointegrateZfDealinhisownapplicationbycopyingoverfilesmanually. Ikeeptheassetswithinthemodule,butaddasymlinktothepublic directory.Again,this means,whendistributingthemodule,Iwillneedtoinstructthedeveloperonhowtointegrate ZfDealinhisownapplicationbysettingupasymlink. Iguessutilizingaproperassetmanagerwillbemyfirstchoiceinthelongrun,however,fornow ,I willlivewithcopyingoverfilestothehostapplication spublicdirectory. Web form for adding a product NowIaddafirstwebformtoZfDeals.Theformisusedtoaddnewproductstothesystem.Instead ofputtingproductrelatedfieldsdirectlyintotheform,Iencapsulatetheminafieldset.The fieldset

isaddedtotheformthen: 84https://github.com/RWOverdijk/AssetManager

Developers Dairy 1 <?php 2 namespace ZfDeals\Form; 3 4 use Zend\Form\Form; 5 6 class ProductAdd extends Form 7 { 8 public function __construct() 9 { 10 parent::__construct('login'); 11 $this->setAttribute('action', '/deals/admin/product/add'); 12 $this->setAttribute('method', 'post'); 13 14 $this->add(array( 15 'type' => 'ZfDeals\Form\ProductFieldset', 16 'options' => array( 17 'use_as_base_fieldset' => true 18 ) 19 )); 20 21 $this->add(array( 22 'name' =>

'submit', 23 'attributes' => array( 24 'type' => 'submit', 25 'value' => 'Hinzufgen' 26 ), 27 )); 28 } 29 } Listing26.8 TheformProductAdd iscomposedofan add buttonaswellastheProductFieldset,whichalso holdsthefilterandvalidatordefinitions:

Developers Dairy <?php namespace ZfDeals\Form; use Zend\Form\Fieldset; use Zend\InputFilter\InputFilterInterface; use Zend\InputFilter\InputFilterProviderInterface; class ProductFieldset extends Fieldset implements InputFilterProviderInterface { public function __construct() { parent::__construct('product'); $this->add(array( 'name' => 'id', 'attributes' => array( 'type' => 'text', ), 'options' => array( 'label' => 'Produkt-ID:', ) ));

$this->add(array( 'name' => 'name', 'attributes' => array( 'type' => 'text', ), 'options' => array( 'label' => 'Produktbezeichnung:', ) )); $this->add(array( 'name' => 'stock', 'attributes' => array( 'type' => 'number', ), 'options' => array( 'label' => '# Bestand:'

Developers Dairy 43 ), 44 )); 45 } 46 47 public function getInputFilterSpecification() 48 { 49 return array( 50 'id' => array ( 51 'required' => true, 52 'filters' => array( 53 array( 54 'name' => 'StringTrim' 55 ) 56 ), 57 'validators' => array( 58 array( 59 'name' => 'NotEmpty', 60 'options' => array( 61 'message' =>

62 "Bitte geben Sie die Produkt-ID an." 63 ) 64 ) 65 ) 66 ), 67 'name' => array ( 68 'required' => true, 69 'filters' => array( 70 array( 71 'name' => 'StringTrim' 72 ) 73 ), 74 'validators' => array( 75 array( 76 'name' => 'NotEmpty', 77 'options' => array( 78 'message' => 79 "Bitte geben Sie eine

Produktbezeichnung an." 80 ), 81 ) 82 ) 83 ), 84 'stock' => array (

Developers Dairy 85 'required' => true, 86 'filters' => array( 87 array( 88 'name' => 'StringTrim' 89 ) 90 ), 91 'validators' => array( 92 array( 93 'name' => 'NotEmpty', 94 'options' => array( 95 'message' => 96 "Bitte geben Sie die Lagerbestand an." 97 ) 98 ), 99 array( 100 'name' => 'Digits',

101 'options' => array( 102 'message' => 103 "Bitte geben Sie einen ganzzahligen Wert an." 104 ) 105 ), 106 array( 107 'name' => 'GreaterThan', 108 'options' => array( 109 'min' => 0, 110 'message' => 111 "Bitte geben Sie

Wert >= 0 an." 112 ) 113 ) 114 ) 115 ) 116 ); 117 } 118 } Listing26.9 Thisapproachallowstore-usetheproductfielddefinitionsinotherforms,e.g. aformthatis dedicatedtoeditinganexistingproduct. Display the form Anadditionalrouteandactiontakecareofdisplayingandprocessingtheform:

Developers Dairy 1 <?php 2 return array( 3 'router' => array( 4 'routes' => array( 'zf-deals\admin\home' => array( 6 'type' => 'Zend\Mvc\Router\Http\Literal', 7 'options' => array( 8 'route' => '/deals/admin', 9 'defaults' => array( 'controller' 11 => 'ZfDeals\Controller\Admin', 12 'action' 13 => 'index', 14 ), ), 16 ), 17 'zf-deals\admin\product\add' => array( 18 'type' => 'Zend\Mvc\Router\Http\Literal', 19 'options'

=> array( 'route' => '/deals/admin/product/add', 21 'defaults' => array( 22 'controller' 23 => 'ZfDeals\Controller\Admin', 24 'action' => 'add-product', 26 ) 27 ) 28 ) 29 ) ) 31 // [..] 32 ) Listing26.10 Theactionitselfreadsasfollows:

Developers Dairy 1 <?php 2 // [..] 3 public function addProductAction() 4 { 5 $form = new \ZfDeals\Form\ProductAdd(); 6 7 if ($this->getRequest()->isPost()) { 8 $form->setData($this->getRequest()->getPost()); 9 10 if ($form->isValid()) { 11 // todo 12 } else { 13 return new ViewModel( 14 array( 15 'form' => $form 16 ) 17 ); 18 } 19 } else { 20

return new ViewModel( 21 array( 22 'form' => $form 23 ) 24 ); 25 } 26 } 27 // [..] Listing26.11 InthecorrespondingviewIdisplaytheform: 1 <?php 2 $this->form->prepare(); 3 echo $this->form()->openTag($this->form); 4 echo $this->formRow($this->form->get('product')->get('id')); 5 echo $this->formRow($this->form->get('product')->get('name')); 6 echo $this->formRow($this->form->get('product')->get('stock')); 7 echo $this->formSubmit($this->form->get('submit')); 8 echo $this->form()->closeTag(); Listing26.12

Developers Dairy IfInowopen/deals/admin/product/add Icanalreadyseetheformbeingrendered.However,it doesn tlookthatprettyyet.Icanmakeitlookmorebeautifulbyadding TwitterBootstrap85 andinsteadofaddingallrequiredmarkupbyhand,IoptforaddinganotherZF2module: DluTwBootstrap86 .Asalways,IutilizeComposertoinstallandconfigurethemodulebyaddinga dependency: 1 "dlu/dlutwbootstrap": "dev-master" ThenIrun 1 $ php composer.phar update andthemoduleisbeinginstalled(donotforgettoactivateitinapplication.config.php!).T he modulemainlyregistersabunchofadditional ViewHelper fordisplayingtheformusingTwitter Bootstrap: 1 <?php 2 $this->form->prepare(); 3 echo $this->form()->openTag($this->form); 4 echo $this->formRowTwb($this->form->get('product')->get('id')); 5 echo $this->formRowTwb($this->form->get('product')->get('name')); 6 echo $this->formRowTwb($this->form->get('product')->get('stock')); 7 echo $this->formSubmitTwb($this->form->get('submit')); 8 echo $this->form()->closeTag(); Listing26.13

Andthishowitlooksrightnow: 85http://twitter.github.com/bootstrap/ 86https://bitbucket.org/dlu/dlutwbootstrap/overview

Developers Dairy

ZfDeals-Addproductform Unit-Tests for the web form BeforeIstartworkingongettingdataoutoftheformandintothedatabase,Iwillfirstaddsome unittestsforformProductAdd,justtobesure,it sconfiguredcorrectly.Icreateanewdirecto ry tests intheapplicationrootandaddthefilephpunit.xml toconfigurethedirectoriesholdingtest cases: 1 2 3 4 5 6 7 <phpunit bootstrap="./bootstrap.php"> <testsuites> <testsuite name="AllTests"> <directory>./ZfDealsTest/FormTest</directory> </testsuite> </testsuites> </phpunit> AsIneedtobootstraptheapplicationwithallofitsservicestoactuallyruntests,Iaddaprop er bootstrap.php filetothetests directoryaswell:

Developers Dairy 1 <?php 2 use Zend\Loader\StandardAutoloader; 3 4 chdir(dirname(__DIR__)); 5 6 include 'init_autoloader.php'; 7 8 $loader = new StandardAutoloader(); 9 $loader->registerNamespace('ZfDealsTest', __DIR__ . '/ZfDealsTest'); 10 $loader->register(); 11 12 Zend\Mvc\Application::init(include 'config/application.config.php'); Listing26.14 ItsexecutedautomaticallybyPHPUnit,whenrunningtests. Inbootstrap.php Iconfigure autoloadingoftestclassesstoredindirectoryZfDealsTest.ThetestfileProductAddTest goesinto itssubdirectoryFormTest: 1 <?php 2 namespace ZfDealsTest\FormTest; 3 4 use

ZfDeals\Form\ProductAdd; 5 6 class ProductAddTest extends \PHPUnit_Framework_TestCase 7{ 8 private $form; 9 private $data; 10 11 public function setUp() 12 { 13 $this->form = new ProductAdd(); 14 $this->data = array( 15 'product' => array( 16 'id' => '', 17 'name' => '',

18 'stock' => '' 19 ) 20 ); 21 } 22 23 public function testEmptyValues() 24 {

Developers Dairy 25 $form = $this->form; 26 $data = $this->data; 27 28 $this->assertFalse($form->setData($data)->isValid()); 29 30 $data['product']['id'] = 1; 31 $this->assertFalse($form->setData($data)->isValid()); 32 33 $data['product']['name'] = 1; 34 $this->assertFalse($form->setData($data)->isValid()); 35 36 $data['product']['stock'] = 1; 37 $this->assertTrue($form->setData($data)->isValid()); 38 } 39 40 public function testStockElement() 41 { 42 $form = $this->form; 43 $data = $this->data; 44 $data['product']['id'] = 1; 45 $data['product']['name'] =

1; 46 47 $data['product']['stock'] = -1; 48 $this->assertFalse($form->setData($data)->isValid()); 49 50 $data['product']['stock'] = "test"; 51 $this->assertFalse($form->setData($data)->isValid()); 52 53 $data['product']['stock'] = 12.3; 54 $this->assertFalse($form->setData($data)->isValid()); 55 56 $data['product']['stock'] = 12; 57 $this->assertTrue($form->setData($data)->isValid()); 58 } 59 } Listing26.15 Totesttheform,Ihandindifferentcombinationsoftestdataandvalidatetheform sbehavior.

Developers Dairy Set up the product entity Inowstartmodelingthesocalled BusinessDomain byaddingtheProduct entityclassrepresenting aproductinthesystem: 1 <?php 2 namespace ZfDeals\Entity; 3 4 class Product 5 { 6 protected $id; 7 protected $name; 8 protected $stock; 9 10 public function setName($name) 11 { 12 $this->name = $name; 13 } 14 15 public function getName() 16 { 17 return $this->name; 18 } 19 20 public

function setId($id) 21 { 22 $this->id = $id; 23 } 24 25 public function getId() 26 { 27 return $this->id; 28 } 29 30 public function setStock($stock) 31 { 32 $this->stock = $stock; 33 } 34 35 public function getStock() 36 { 37 return $this->stock;

Developers Dairy 38 } 39 } Listing26.16 AproductentityholdsitsID,adescriptionandstockinformation.Thefileisaddedtoadirect ory calledEntity inscr/ZfDeals withinmymodule. Product entity persistence Thedatabasemappermappingtheentityfieldstocolumnsinthedatabasetablereadsasfollows : 1 <?php 2 3 namespace ZfDeals\Mapper; 4 5 use ZfDeals\Entity\Product as ProductEntity; 6 use Zend\Stdlib\Hydrator\HydratorInterface; 7 use Zend\Db\TableGateway\TableGateway; 8 use Zend\Db\TableGateway\Feature\RowGatewayFeature; 9 use Zend\Db\Sql\Sql; 10 use Zend\Db\Sql\Insert; 11 12 class Product extends TableGateway 13 { 14 protected $tableName = 'product'; 15

protected $idCol = 'id'; 16 protected $entityPrototype = null; 17 protected $hydrator = null; 18 19 public function __construct($adapter) 20 { 21 parent::__construct($this->tableName, 22 $adapter, 23 new RowGatewayFeature($this->idCol) 24 ); 25 26 $this->entityPrototype = new ProductEntity(); 27 $this->hydrator = new \Zend\Stdlib\Hydrator\Reflection; 28 } 29 30 public function insert($entity)

Developers Dairy 216 31 { 32 return parent::insert($this->hydrator->extract($entity)); 33 } 34 } Listing26.17 Conventionoverconfiguration makesthemapperstraightforward,iftheentityfieldsmatchthe columnnamesinthedatabasetable.Thanksto\Zend\Stdlib\Hydrator\Reflection all mapping magic mainlyhappensautomaticallywhencallingtheinsert method. Nowlet saddthemissingdatabaseadaptertomodule.config.php.It sneededtoactuallyconnect tothedatabase: 1 <?php 2 // [..] 3 'service_manager' => array( 4 'factories' => array( 5 'Zend\Db\Adapter\Adapter' => function ($sm) { 6 $config = $sm->get('Config'); 7 $dbParams = $config['dbParams']; 8 9 return new Zend\Db\Adapter\Adapter(array( 10 'driver' => 'pdo', 11 'dsn' =>

12 'mysql:dbname='.$dbParams['database'] 13 .';host='.$dbParams['hostname'], 14 'database' => $dbParams['database'], 15 'username' => $dbParams['username'], 16 'password' => $dbParams['password'], 17 'hostname' => $dbParams['hostname'], 18 )); 19 } 20 ) 21 ) 22 // [..] Listing26.18 Iputthedatabaseconnectioncredentialsanddetailsindb.local.php indirectory/config/autoload. Idonotaddthisfiletothecoderepositoryasitcontainssensitivedata.However,Iaddanothe r filecalleddb.local.php.dist whichIcommitinstead.Itactsasatemplatefortheconfigfilethat needstobepresentonaboxrunningZfDeals:

Developers Dairy 217 1 <?php 2 return array( 3 'dbParams' => array( 4 'database' => '', 5 'username' => '', 6 'password' => '', 7 'hostname' => '', 8) 9 ); Listing26.19 ThiswayImakesurethatthestructureofthedatabaseconfigfileisunderstoodbydevelopers integratingZfDealsintotheirownapplications. InServiceManager ImakeZfDeals\Mapper\Product availableandinjectitsdependencytoZend\Db\Adapter\Adapter 1 <?php 2 // [..] 3 'service_manager' => array( 4 'factories' => array(

5 'ZfDeals\Mapper\Product' => function ($sm) { 6 return new \ZfDeals\Mapper\Product( 7 $sm->get('Zend\Db\Adapter\Adapter') 8 ); 9 }, 10 ) 11 ) 12 // [..] Listing26.20 Form processing IcannowaddpersistencecodetoaddProductAction().Ireaddatafromtheformandbybinding anewproductentityobjectfirst,callinggetData() givesbacktheobjectautomaticallypopulated withthedatagiven.ZfDeals\Mapper\Product isthenusedtosavetheentitytothedatabase:

Developers Dairy <?php // [..] public function addProductAction() { $form = new \ZfDeals\Form\ProductAdd(); if ($this->getRequest()->isPost()) { $form->setHydrator(new\Zend\Stdlib\Hydrator\Reflection()); $form->bind(new \ZfDeals\Entity\Product()); $form->setData($this->getRequest()->getPost()); if ($form->isValid()) { $newEntity = $form->getData(); $mapper = $this->getServiceLocator() ->get('ZfDeals\Mapper\Product'); $mapper->insert($newEntity); $form = new \ZfDeals\Form\ProductAdd(); return new ViewModel( array( 'form'

=> $form, 'success' => true ) ); } else { return new ViewModel( array( 'form' => $form ) ); } } else { return new ViewModel( array( 'form' => $form ) ); } } // [..]

Developers Dairy Listing26.21 Butallofthisonlyworksafterthedatabasetableissetup: 1 2 3 4 5 CREATE ); TABLE product( id varchar(255) NOT NULL, name varchar(255) NOT NULL, stock int(10) NOT NULL, PRIMARY KEY (id) Onecannowsubmittheformandanewdatabaseentryisadded. messageisdisplayed: Ifitallworkedout,asuccess 1 2 3 4 5 6 7 8 9 10 11 12 <?php if ($this->success) { ?> <div class="alert alert-success">Produkt hinzugefgt!</div> <?php } ?> <?php $this->form->prepare(); echo

$this->form()->openTag($this->form); echo $this->formRowTwb($this->form->get('product')->get('id')); echo $this->formRowTwb($this->form->get('product')->get('name')); echo $this->formRowTwb($this->form->get('product')->get('stock')); echo $this->formSubmitTwb($this->form->get('submit')); echo $this->form()->closeTag(); Listing26.22 Dependency Injection Sofar,sogood.However,thereisalotofcodethatcanbeimproved.Toooften,Iusethenew statementinmycodemakingitdirectlydependentonotherclasses.Thisbadpracticemakesmy codehardtotestandIwanttoavoiditwheneverpossiblebyapplying dependencyinjection .This willmaketestingeasieraswellasmakemycodemoreversatile.Let sdosomerefactoringright away. ThenewAdminControllerFactory isaddedtocreatetheAdminController.Thefactorytakescare ofinjectingtheZfDeals\Mapper\Product dependency:

Developers Dairy 1 <?php 2 namespace ZfDeals\Controller; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class AdminControllerFactory implements FactoryInterface 8 { 9 public function createService(ServiceLocatorInterface $serviceLocator) 10 { 11 $ctr = new AdminController(); 12 $form = new \ZfDeals\Form\ProductAdd(); 13 $form->setHydrator(new\Zend\Stdlib\Hydrator\Reflection()); 14 $form->bind(new \ZfDeals\Entity\Product()); 15 $ctr->setProductAddForm($form); 16 17 $mapper = $serviceLocator->getServiceLocator() 18 ->get('ZfDeals\Mapper\Product'); 19 20 $ctr->setProductMapper($mapper); 21 return

$ctr; 22 } 23 } Listing26.23 Inmodule.config.php Inowpointtothefactory: 1 <?php 2 // [..] 3 'controllers' => array( 4 'factories' => array( 5 'ZfDeals\Controller\Admin' 6 => 'ZfDeals\Controller\AdminControllerFactory' 7 ) 8 ) 9 // [..] Listing26.24 Unit tests for my controller Notyetperfect,butalotbetter.AtleastIcannowaddsomeunittestsformycontroller.Before the refactoring,Iwouldnothavebeenabletoproperlyunittestmycontrolleratall.

Developers Dairy Butwhatexactlytotestinacontroller?Inbestcase,acontrolleritselfdoesn tdoalotmagic.M ainly acontrollershouldinstructotherobjectstodotheheavylifting.WhatIshouldtesthereisth atthe controllerinstructstherightobjectsattherighttimetodotherightthings.Mainly,wehave the followingcasesourcontrollerhastodealwith: 1.TheformisrequestedusingGET:Thewebformmustbeshown. 2.TheformissubmittedusingPOST butvalidationfailed:Showtheformagainwitherror messages. 3.TheformissubmittedusingPOST andvalidatedsuccessfully:Anewentityhastobecreated basedonthedatagivenandstoredinthedatabaseusingthemapper. Andthisishowthetestscouldlooklike: 1 <?php 2 namespace ZfDeals\ControllerTest; 3 4 use ZfDeals\Controller\AdminController; 5 use Zend\Http\Request; 6 use Zend\Http\Response; 7 use Zend\Mvc\MvcEvent; 8 use Zend\Mvc\Router\RouteMatch; 9 10 class AdminControllerTest extends \PHPUnit_Framework_TestCase 11 { 12 private $controller; 13

private $request; 14 private $response; 15 private $routeMatch; 16 private $event; 17 18 public function setUp() 19 { 20 $this->controller = new AdminController(); 21 $this->request = new Request(); 22 $this->response = new Response(); 23 $this->routeMatch = new RouteMatch(array('controller' => 'admin')); 24 $this->routeMatch->setParam('action', 'add-product'); 25 $this->event = new MvcEvent(); 26 $this->event->setRouteMatch($this->routeMatch); 27 $this->controller->setEvent($this->event); 28 } 29

Developers Dairy 30 public 31 { 32 33 34 35 36 37 38 39 } 40 41 public 42 { 43 44 45 46 47 48 49 50 51 52 53 54 55 } 56 57 public 58 { 59 60 61 62 63 64 65 66 67 68 69 70 71 function testShowFormOnGetRequest()

$fakeForm = new \Zend\Form\Form('fakeForm'); $this->controller->setProductAddForm($fakeForm); $this->request->setMethod('get'); $response = $this->controller->dispatch($this->request); $viewModelValues = $response->getVariables(); $formReturned = $viewModelValues['form']; $this->assertEquals($formReturned->getName(), $fakeForm->getName()); function testShowFormOnValidationError() $fakeForm = $this->getMock('Zend\Form\Form', array('isValid')); $fakeForm->expects($this->once()) ->method('isValid') ->will($this->returnValue(false)); $this->controller->setProductAddForm($fakeForm); $this->request->setMethod('post'); $response = $this->controller->dispatch($this->request); $viewModelValues = $response->getVariables(); $formReturned = $viewModelValues['form']; $this->assertEquals($formReturned->getName(), $fakeForm->getName()); function testCallMapperOnFormValidationSuccess() $fakeForm = $this->getMock( 'Zend\Form\Form', array('isValid',

'getData') ); $fakeForm->expects($this->once()) ->method('isValid') ->will($this->returnValue(true)); $fakeForm->expects($this->once()) ->method('getData') ->will($this->returnValue(new \stdClass())); $fakeMapper = $this->getMock('ZfDeals\Mapper\Product',

Developers Dairy 72 array('insert'), 73 array(), 74 '', 75 false 76 ); 77 78 $fakeMapper->expects($this->once()) 79 ->method('insert') 80 ->will($this->returnValue(true)); 81 82 $this->controller->setProductAddForm($fakeForm); 83 $this->controller->setProductMapper($fakeMapper); 84 $this->request->setMethod('post'); 85 $response = $this->controller->dispatch($this->request); 86 } 87 } Listing26.25 Agoodstartingpoint,Iguess.Torunalltestswithasinglecommand,Iaddedthemtophpunit.xm l file: 1 <phpunit bootstrap="./bootstrap.php"> 2 <testsuites> 3 <testsuite name="AllTests"> 4 <directory>./ZfDealsTest/FormTest</directory> 5 <directory>./ZfDealsTest/ControllerTest</directory> 6 </testsuite> 7 </testsuites> 8 </phpunit> Avoid ambiguous

product IDs There sonethingthatcomestomymind:Whatactuallyhappens,ifIaddthesameproductID twice?IbreakthesystemasIconfiguredthecolumntobeuniqueinthedatabase.It stheprimary key.Wecanhandlethissituationbyaddingapropertry/catchstatement:

Developers Dairy 1 <?php 2 public function addProductAction() 3 { 4 $form = $this->productAddForm; 6 if ($this->getRequest()->isPost()) { 7 $form->setData($this->getRequest()->getPost()); 8 9 if ($form->isValid()) { $model = new ViewModel( 11 array( 12 'form' => $form 13 ) 14 ); 16 try { 17 $this->productMapper->insert($form->getData()); 18 $model->setVariable('success', true); 19 } catch (\Exception $e) { $model->setVariable('insertError', true); 21 } 22

23 return $model; 24 } else { return new ViewModel( 26 array( 27 'form' => $form 28 ) 29 ); } 31 } else { 32 return new ViewModel( 33 array( 34 'form' => $form ) 36 ); 37 } 38 } Listing26.26 Andtheviewfilenowlookslikethis:

Developers Dairy 1 <?php if ($this->success) { ?> 2 <div class="alert alert-success">Produkt hinzugefgt!</div> 3 <?php } ?> 4 5 <?php if ($this->insertError) { ?> 6 <div class="alert alert-error"> 7 Produkt konnte nicht hinzugefgt werden. 8 </div> 9 <?php } ?> 10 11 <?php 12 $this->form->prepare(); 13 echo $this->form()->openTag($this->form); 14 echo $this->formRowTwb($this->form->get('product')->get('id')); 15 echo $this->formRowTwb($this->form->get('product')->get('name')); 16 echo $this->formRowTwb($this->form->get('product')->get('stock')); 17

echo $this->formSubmitTwb($this->form->get('submit')); 18 echo $this->form()->closeTag(); Listing26.27 Let saddanothertesttoverifythetry/catchstatementsworksasdesired.Inthecourseofaddin g thetest,Idosometestcodecleanupandextractthecodetocreatefakeobjects: 1 <?php 2 namespace ZfDeals\ControllerTest; 3 4 use ZfDeals\Controller\AdminController; 5 use Zend\Http\Request; 6 use Zend\Http\Response; 7 use Zend\Mvc\MvcEvent; 8 use Zend\Mvc\Router\RouteMatch; 9 10 class AdminControllerTest extends \PHPUnit_Framework_TestCase 11 { 12 private $controller; 13 private $request; 14 private $response; 15 private $routeMatch; 16 private $event; 17 18 public function setUp() 19 {

Developers Dairy 20 $this->controller = new AdminController(); 21 $this->request = new Request(); 22 $this->response = new Response(); 23 $this->routeMatch = new RouteMatch(array('controller' => 'admin')); 24 $this->routeMatch->setParam('action', 'add-product'); 25 $this->event = new MvcEvent(); 26 $this->event->setRouteMatch($this->routeMatch); 27 $this->controller->setEvent($this->event); 28 } 29 30 public function testShowFormOnGetRequest() 31 { 32 $fakeForm = new \Zend\Form\Form('fakeForm'); 33 $this->controller->setProductAddForm($fakeForm); 34 $this->request->setMethod('get'); 35 $response = $this->controller->dispatch($this->request);

36 $viewModelValues = $response->getVariables(); 37 $formReturned = $viewModelValues['form']; 38 $this->assertEquals($formReturned->getName(), $fakeForm->getName()); 39 } 40 41 public function testShowFormOnValidationError() 42 { 43 $fakeForm = $this->getFakeForm(false); 44 $this->controller->setProductAddForm($fakeForm); 45 $this->request->setMethod('post'); 46 $response = $this->controller->dispatch($this->request); 47 $viewModelValues = $response->getVariables(); 48 $formReturned = $viewModelValues['form']; 49 $this->assertEquals($formReturned->getName(), $fakeForm->getName()); 50 } 51 52 public function testCallMapperOnFormValidationSuccessPersistenceSuccess() 53 { 54 $fakeForm = $this->getFakeForm(); 55 56 $fakeForm->expects($this->once()) 57

->method('getData') 58 ->will($this->returnValue(new \stdClass())); 59 60 $fakeMapper = $this->getFakeMapper(); 61

Developers Dairy 62 $fakeMapper->expects($this->once()) 63 ->method('insert') 64 ->will($this->returnValue(true)); 65 66 $this->controller->setProductAddForm($fakeForm); 67 $this->controller->setProductMapper($fakeMapper); 68 $this->request->setMethod('post'); 69 $response = $this->controller->dispatch($this->request); 70 $viewModelValues = $response->getVariables(); 71 $this->assertTrue(isset($viewModelValues['success'])); 72 } 73 74 public function testCallMapperOnFormValidationSuccessPersistenceError() 75 { 76 $fakeForm = $this->getFakeForm(); 77 78 $fakeForm->expects($this->once()) 79 ->method('getData') 80 ->will($this->returnValue(new \stdClass())); 81 82 $fakeMapper = $this->getFakeMapper(); 83 84 $fakeMapper->expects($this->once()) 85 ->method('insert') 86 ->will($this->throwException(new

\Exception)); 87 88 $this->controller->setProductAddForm($fakeForm); 89 $this->controller->setProductMapper($fakeMapper); 90 $this->request->setMethod('post'); 91 $response = $this->controller->dispatch($this->request); 92 $viewModelValues = $response->getVariables(); 93 $this->assertTrue(isset($viewModelValues['form'])); 94 $this->assertTrue(isset($viewModelValues['insertError'])); 95 } 96 97 public function getFakeForm($isValid = true) 98 { 99 $fakeForm = $this->getMock( 100 'Zend\Form\Form', array('isValid', 'getData') 101 ); 102 103 $fakeForm->expects($this->once())

Developers Dairy 104 ->method('isValid') 105 ->will($this->returnValue($isValid)); 106 107 return $fakeForm; 108 } 109 110 public function getFakeMapper() 111 { 112 $fakeMapper = $this->getMock('ZfDeals\Mapper\Product', 113 array('insert'), 114 array(), 115 '', 116 false 117 ); 118 119 return $fakeMapper; 120 } 121 } Listing26.28 Andthisishowitlookslikenow:

ZfDeals-Successfullyaddedanewproduct Congrats-thatwasitforSprint2!

Developers Dairy Sprint 3 -Add a deal, show available deals SourceCodeDownloadThesourcecodeofSprint3canbedownloadedusingTag Sprint3 onGitHub.. .https://github.com/michael-romer/ZfDealsApp/tree/Sprint3 ThefirstuserstoryofSprint3readsasfollows: Inordertoofferadealasamerchant,Iwantaddonetothesystem. Acceptancecriteria: Byusingawebform,anewdealwithpricingdetails,startdate,enddateandstockinformation canbeaddedbasedonanexistingproductwithinthesystem Theseconduserstoryforthissprintreadsasfollows: Inordertobuyaproductasacustomer,Iwanttoseeallavailabledealsataglance. Acceptancecriteria: Alldealsaredisplayedthatarecurrentlyavailable. Onlydealsaredisplayedthathavestock. ButbeforeIstartworkingonthenewrequirements,Iwillworkonsometechnicalimprovements. Coding Standard ZF2compliestothePSR-2CodingStandard87.Rightfromthebeginning,IwanttofollowthePSR2 rulesaswellsothatIcanbeforsurethatmycodewilllookfamiliartoothersthatneedsto understand,modifyorenhanceit.TosupportPSR-2adoption,onemayinstallPHP_Codesniffer 88. It spre-installedonmylocalbox,however,itmaybeinstalledquicklyusingPEAR8.: 87https://github.com/pmjones/fig-standards/blob/psr-1-style-guide/proposed/PSR-2 -advanced.md 88http://pear.php.net/package/PHP_CodeSniffer/download/ 8.http://pear.php.net/

Developers Dairy 1 $ pear install PHP_CodeSniffer-1.3.6 Now,whenrunning 1 $ phpcs -v --standard=psr2 ZfDeals/ indirectorymodules,PHP_CodesnifferautomaticallytestsmyZfDeals modulecodeonPSR-2 compliance.Unfortunately,withthecurrentcode,PHP_Codesnifferhasalottocriticise: 1 FILE: /vagrant/module/ZfDeals/Module.php 2 ---------------------------------------------------------------------3 FOUND 7 ERROR(S) AFFECTING 3 LINE(S) 4 ---------------------------------------------------------------------5 11 | ERROR | Opening parenthesis of a multi-line 6 function call must be the last content on the line 7 8 11 | ERROR | Only one

argument is allowed per line 9 in a multi-line function call 10 11 11 | ERROR | Only one argument is allowed per line 12 in a multi-line function call 13 14 11 | ERROR | Expected 1 space after FUNCTION keyword; 0 found 15 16 14 | ERROR | Only one argument is allowed per line 17 in a

multi-line function call 18 19 14 | ERROR | Closing parenthesis of a multi-line function 20 call must be on a line by itself 21 22 32 | ERROR | Expected 1 blank line at end of file; 0 found OnceallCSissuesarefixed,PHP_Codesnifferapprovesmycodeinsilenceandnooutputisgiven tothecommandline. FixCSissuesautomaticallyFabienPotencierrecentlyreleasedaveryhelpfultoolcalled PHPCS-Fixer toautomaticallyfixmostofthetypicalCSissues.It sworthtakingalook. Tomakemycodingstylechecksaseasyaspossible,Icreatealittlehelperscriptintests:

Developers Dairy 231 1 <?php 2 echo shell_exec('phpcs --standard=psr2 ../module/ZfDeals') . PHP_EOL; Listing26.29 IcannoweasilycheckmycodeonPSR-2compliancebyexecuting: 1 $ php checkstyle.php Database init script ZfDeals requiresaspecificdatabaseschematowork.AsocalledDDLscriptin/module/ZfDeals/data makesiteasytocreatetheschemaneeded: 1 CREATE TABLE product( 2 id varchar(255) NOT NULL, 3 name varchar(255) NOT NULL, 4 stock int(10) NOT NULL, PRIMARY KEY (id) 5 ); It sastartingpoint.Moredatadefinitionswillsurelybeaddedsoon. Deal entity BacktothisSprint suserstories.First,IcodetheDeal entityclass: 1 <?php 2 namespace ZfDeals\Entity; 3 4

class Deal 5 { 6 protected $id; 7 protected $price; 8 protected $startDate; 9 protected $endDate; 10 protected $product; 11 12 public function setEndDate($endDate) 13 { 14 $this->endDate = $endDate; 15 }

Developers Dairy 16 17 public function getEndDate() 18 { 19 return $this->endDate; 20 } 21 22 public function setStartDate($startDate) 23 { 24 $this->startDate = $startDate; 25 } 26 27 public function getStartDate() 28 { 29 return $this->startDate; 30 } 31 32 public function setId($id) 33 { 34 $this->id = $id; 35 } 36 37 public function getId() 38

{ 39 return $this->id; 40 } 41 42 public function setPrice($price) 43 { 44 $this->price = $price; 45 } 46 47 public function getPrice() 48 { 49 return $this->price; 50 } 51 52 public function setProduct($product) 53 { 54 $this->product = $product; 55 } 56 57 public function getProduct()

Developers Dairy 58 { 59 return $this->product; 60 } 61 } Listing26.30 Adealhasaprice,startdate,enddateandareferencetotheproductonsale.Thefollowingdata structureisneededforpersistenceofdealsandthereforeIaddittostructure.sql: 1 CREATE TABLE deal( 2 id int(10) NOT NULL AUTO_INCREMENT, 3 price float NOT NULL, 4 startDate date NOT NULL, 5 endDate date NOT NULL, 6 product varchar(255) NOT NULL, 7 PRIMARY KEY (id) 8 ); Add a new

deal Iaddasection Deals withentry AddDeal totheadmin s/deals/admin/deal/add.Asusual,tomakething swork,IsetupTheDealAdd formisusedtoaddanewdealtothesystem: 1 <?php 2 namespace ZfDeals\Form; 3 4 use Zend\Form\Form; 5 use Zend\ServiceManager\ServiceManager; 6 use Zend\ServiceManager\ServiceManagerAwareInterface; 7 8 class DealAdd extends Form navigationpanel. Itpointsto arouteinmodule.config.php. 9{ 10 public 11 { 12 13 14 15 16

17 function __construct() parent::__construct('dealAdd'); $this->setAttribute('action', '/deals/admin/deal/add'); $this->setAttribute('method', 'post'); $this->add( array(

Developers Dairy 18 'type' => 'ZfDeals\Form\DealFieldset', 19 'options' => array( 20 'use_as_base_fieldset' => true 21 ) 22 ) 23 ); 24 25 $this->add( 26 array( 27 'name' => 'submit', 28 'attributes' => array( 29 'type' => 'submit', 30 'value' => 'Hinzufgen' 31 ), 32 ) 33 ); 34 } 35 } Listing26.31 DealFieldset definitions: 1 <?php 2 namespace

ZfDeals\Form; 3 4 use Zend\Form\Fieldset; 5 use Zend\InputFilter\InputFilterInterface; 6 7 class DealFieldset extends Fieldset 8 { 9 public function __construct() 10 { 11 parent::__construct('deal'); 12 13 $this->add( 14 array( 15 'name' => 'product', 16 'type' => 'ZfDeals\Form\ProductSelectorFieldset', 17 ) 18 ); 19 20 $this->add(

Developers Dairy 21 array( 22 'name' => 'price', 23 'type' => 'Zend\Form\Element\Number', 24 'attributes' => array( 25 'step' => 'any' 26 ), 27 'options' => array( 28 'label' => 'Preis:', 29 ) 30 ) 31 ); 32 33 $this->add( 34 array( 35 'name' => 'startDate', 36 'type' => 'Zend\Form\Element\Date', 37 'options' => array( 38 'label' => 'Startdatum:' 39

), 40 ) 41 ); 42 43 $this->add( 44 array( 45 'name' => 'endDate', 46 'type' => 'Zend\Form\Element\Date', 47 'options' => array( 48 'label' => 'Enddatum:' 49 ), 50 ) 51 ); 52 } 53 } Listing26.32 DealFieldset containsProductSelectorFieldset thatservesasaproductchooserwhileaddinga newdeal:

Developers Dairy 1 <?php 2 namespace ZfDeals\Form; 3 4 use Zend\Form\Fieldset; 5 use Zend\InputFilter\InputFilterInterface; 6 7 class ProductSelectorFieldset extends Fieldset 8 { 9 public function __construct() 10 { 11 parent::__construct('productSelector'); 12 $this->setHydrator(new\Zend\Stdlib\Hydrator\Reflection()); 13 $this->setObject(new \ZfDeals\Entity\Product()); 14 15 $this->add( 16 array( 17 'name' => 'id', 18 'type' => 'Zend\Form\Element\Select', 19 'options' => array( 20 'label' => 'Produkt-ID:', 21 'value_options'

=> array( 22 '1' => 'Label 1', 23 '2' => 'Label 2', 24 ), 25 ) 26 ) 27 ); 28 } 29 } Listing26.33 Thelistisinitializedwithdummydataonly. Realproductsareaddedbythecontrollerbefore actuallyrenderingtheform. INTLextensionOpeningthenewURLmyresultina PHPFatalerror:Class NumberFormatter notfound .T hereasonisthatPHP sINTLextensionmightbemissingonthesystembutisneededbytheframework ,evenifyoudon tdirectlydealwithZF2 sI18Nfeatures.OnaLinuxsystem,onecaneasilyinstallt heINTLextensionexecutingapt-getinstallphp5-intl.Don tforgettorestartApache

Developers Dairy afterwards. FormprocessingisdoneinAdminController.IextendAdminControllerFactory inaway,that DealAdd formisinjectedaswell: 1 <?php 2 namespace ZfDeals\Controller; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class AdminControllerFactory implements FactoryInterface 8{ 9 public function createService(ServiceLocatorInterface $serviceLocator) 10 { 11 $ctr = new AdminController(); 12 $productAddForm = new \ZfDeals\Form\ProductAdd(); 13 $productAddForm->setHydrator(new\Zend\Stdlib\Hydrator\Reflection()); 14

$productAddForm->bind(new \ZfDeals\Entity\Product()); 15 $ctr->setProductAddForm($productAddForm); 16 17 $mapper = $serviceLocator->getServiceLocator() 18 ->get('ZfDeals\Mapper\Product'); 19 20 $ctr->setProductMapper($mapper); 21 $dealAddForm = new \ZfDeals\Form\DealAdd(); 22 $ctr->setDealAddForm($dealAddForm); 23 24 $dealAddForm 25 ->setHydrator(new\Zend\Stdlib\Hydrator\Reflection()); 26 27 $dealAddForm->bind(new \ZfDeals\Entity\Deal()); 28

29 $dealMapper = $serviceLocator->getServiceLocator() 30 ->get('ZfDeals\Mapper\Deal'); 31 32 $ctr->setDealMapper($dealMapper); 33 return $ctr; 34 } 35 }

Developers Dairy 238 Listing26.34 FormprocessingcodeislocatedinaddDealAction inAdminController. First,Imakesure ProductSelectorFieldset isinitializedwithrealproductdataretrievedfromthedatabase.DealMapper takescareofaddingdealstothedatabaseaswellasfindingactivedeals: 1 <?php 2 3 namespace ZfDeals\Mapper; 4 5 use ZfDeals\Entity\Deal as DealEntity; 6 use Zend\Stdlib\Hydrator\HydratorInterface; 7 use Zend\Db\TableGateway\TableGateway; 8 use Zend\Db\TableGateway\Feature\RowGatewayFeature; 9 use Zend\Db\Sql\Sql; 10 use Zend\Db\Sql\Insert; 11 12 class Deal extends TableGateway 13 { 14 protected $tableName = 'deal'; 15 protected $idCol = 'id'; 16 protected $entityPrototype = null;

17 protected $hydrator = null; 18 19 public function __construct($adapter) 20 { 21 parent::__construct( 22 $this->tableName, 23 $adapter, 24 new RowGatewayFeature($this->idCol) 25 ); 26 27 $this->entityPrototype = new DealEntity(); 28 $this->hydrator = new \Zend\Stdlib\Hydrator\Reflection; 29 } 30 31 public function insert($entity) 32 { 33 return parent::insert($this->hydrator->extract($entity)); 34 } 35 36 public function findActiveDeals() 37 {

Developers Dairy 38 $sql = new \Zend\Db\Sql\Sql($this->getAdapter()); 39 $select = $sql->select() 40 ->from($this->tableName) 41 ->join('product', 'deal.product=product.id') 42 ->where('DATE(startDate) <= DATE(NOW())') 43 ->where('DATE(endDate) >= DATE(NOW())') 44 ->where('stock > 0'); 45 46 $stmt = $sql->prepareStatementForSqlObject($select); 47 $results = $stmt->execute(); 48 49 return $this->hydrate($results); 50 } 51 52 public function hydrate($results) 53 { 54 $deals = new \Zend\Db\ResultSet\HydratingResultSet( 55 $this->hydrator, 56

$this->entityPrototype 57 ); 58 59 return $deals->initialize($results); 60 } 61 } Listing26.35 MyaddDealAction stillisstraightforward,however,thingsalreadystarttogetabitmessy: 1 <?php 2 // [..] 3 public function addDealAction() 4 { 5 $form = $this->dealAddForm; 6 7 $products = $this->productMapper->select(); 8 $fieldElements = array(); 9 10 foreach ($products as $product) { 11 $fieldElements[$product['id']] = $product['name']; 12 } 13 14 $form->get('deal')->get('product')

Developers Dairy 15 ->get('id')->setValueOptions($fieldElements); 16 17 if ($this->getRequest()->isPost()) { 18 $form->setData($this->getRequest()->getPost()); 19 20 if ($form->isValid()) { 21 $model = new ViewModel( 22 array( 23 'form' => $form 24 ) 25 ); 26 27 $newDeal = $form->getData(); 28 $newDeal->setProduct($newDeal->getProduct()->getId()); 29 30 try { 31 $this->dealMapper->insert($newDeal); 32 $model->setVariable('success', true); 33 } catch (\Exception $e) { 34 $model->setVariable('insertError', true); 35 }

36 37 return $model; 38 } else { 39 return new ViewModel( 40 array( 41 'form' => $form 42 ) 43 ); 44 } 45 } else { 46 return new ViewModel( 47 array( 48 'form' => $form 49 ) 50 ); 51 } 52 } 53 // [..] Listing26.36

Developers Dairy 241 Show active deals IcreateIndexController withitsfactoryIndexControllerFactory toshowactivedealstocustomers. IndexControllerFactory injectstheDeal mappertoretrieveactivedealsfromthedatabase: 1 <?php 2 namespace ZfDeals\Controller; 3 4 use Zend\Mvc\Controller\AbstractActionController; 5 use Zend\View\Model\ViewModel; 6 use Zend\Form\Annotation\AnnotationBuilder; 7 8 class IndexController extends AbstractActionController 9 { 10 private $dealMapper; 11 private $productMapper; 12 13 public function indexAction() 14 { 15 $deals = $this->dealMapper->findActiveDeals(); 16 $dealsView = array(); 17 18 foreach ($deals

as $deal) { 19 $deal->setProduct( 20 $this->productMapper->findOneById($deal->getProduct()) 21 ); 22 23 $dealsView[] = $deal; 24 } 25 26 return new ViewModel( 27 array( 28 'deals' => $dealsView 29 ) 30 ); 31 } 32 33 public function setDealMapper($dealMapper) 34 { 35 $this->dealMapper = $dealMapper; 36 } 37 38 public function getDealMapper()

Developers Dairy 39 { 40 return $this->dealMapper; 41 } 42 43 public function setProductMapper($productMapper) 44 { 45 $this->productMapper = $productMapper; 46 } 47 48 public function getProductMapper() 49 { 50 return $this->productMapper; 51 } 52 } Listing26.37 Theforeach isn tveryeleganthere,butasIdon texcepthighvolumeusagefornow,IguessIcan livewithitforthemomentandimproveitlater.However,Iknowit saperformancebottleneck. Iwilluseanotherlayouttemplateforshowingdealstocustomers.It sconfiguredinclassModul e withininit: 1 <?php 2 [..] 3 public function init(ModuleManager $moduleManager) 4 { 5 $sharedEvents = $moduleManager->getEventManager()->getSharedManager();

6 7 $sharedEvents->attach( 8 'ZfDeals\Controller\AdminController', 9 'dispatch', 10 function ($e) { 11 $controller = $e->getTarget(); 12 $controller->layout('zf-deals/layout/admin'); 13 }, 14 100 15 ); 16 17 $sharedEvents->attach( 18 'ZfDeals\Controller\IndexController', 19 'dispatch', 20 function ($e) {

Developers Dairy 21 $controller = $e->getTarget(); 22 $controller->layout('zf-deals/layout/site'); 23 }, 24 100 25 ); 26 } 27 [..] Listing26.38 Now/deals bringsupallactivedealsavailableinthesystem. A custom controller type for dealing with forms Iamnotreallyhappywiththecodeyet.First,let stakealookatAdminController:Itholdsboth addactions .Oneforproducts,onefordeals.Bothmethodslookalotlike copyandpaste asthey mainlydothesamething:handlingawebform.Andbothlooksomewhatcomplicatedandabit toonested.Thereasonisthatasingleactiondisplaystheform,doesdatavalidation,errorha ndling aswellasdatapersistence.Maybethat stoomuchresponsibilityforasingleactionanditshoul d berefactored.Thesameistrueforitstemplatefile.AnotherissueisthatIndexControllerFa ctory bydefaultscreatesandinjectsbothformseverytime,evenifonlyonemaybeusedatatime.This doesn tlookappropriate. Tofixallissuesmentionedatonedash,Itakeadvantageofthefact,thataControllerinZF2sim ply requirestohaveamethodonDispatch() tobefullyfunctional,creatingaResponse basedona Request given.ThisallowsmetocomeupwithaspecialtypeofControllerbuiltfordealingwith formsinitsveryownway,anabstractcontrollerclasscalledAbstractFormController: 1 <?php 2 namespace ZfDeals\Controller; 3 4 use Zend\Mvc\Controller\AbstractController;

5 use Zend\Mvc\MvcEvent; 6 use Zend\Form\Form as Form; 7 use Zend\View\Model\ViewModel; 8 9 abstract class AbstractFormController extends AbstractController 10 { 11 protected $form; 12 13 public function __construct(Form $form) 14 { 15 $this->form = $form;

Developers Dairy 16 } 17 18 public function onDispatch(MvcEvent $e) 19 { 20 if (method_exists($this, 'prepare')) { 21 $this->prepare(); 22 } 23 24 $routeMatch = $e->getRouteMatch(); 25 26 if ($this->getRequest()->isPost()) { 27 $this->form->setData($this->getRequest()->getPost()); 28 29 if ($this->form->isValid()) { 30 $routeMatch->setParam('action', 'process'); 31 $return = $this->process(); 32 } else { 33 $routeMatch->setParam('action', 'error'); 34 $return = $this->error(); 35 }

36 37 } else { 38 $routeMatch->setParam('action', 'show'); 39 $return = $this->show(); 40 } 41 42 $e->setResult($return); 43 return $return; 44 } 45 46 abstract protected function process(); 47 48 protected function show() 49 { 50 return new ViewModel( 51 array( 52 'form' => $this->form 53 ) 54 ); 55 } 56 57 protected function error()

Developers Dairy 245 58 59 60 61 { return new ViewModel( array( 'form' => $this->form 62 63 64 65 } ); ) 66 67 68 69 70 public { } function setForm($form) $this->form = $form; 71 72 73 74 75 } public { } function getForm() return $this->form; Listing26.39 Andthisishowitworks:AtroutingaAbstractFormController basedcontrollerismatchedtothe URLrequested.Itsdispatch() andthenitsonDispatch() methodiscalled.Insteadofcallinganactionmethod,whichusuallyhappenswhentheframework swell-knownAbstractActionController isused,anothermethodiscalledbasedonthestateofformprocessing.Letmeexplainthatfurt her. IftheurlrequestedisusingHTTP sGETmethod,it sobviousthatwesimplyhavetoinitiallydispla y

theformtotheuser.Therefore,show() iscalled.It salreadygivenbyAbstractFormController.A concretecontrollerbasedonAbstractFormController mayoverwritethismethodifithasspecial needsdisplayingtheform.AbstractFormController isdesignedinaway,thatoninstantiation,the forminquestionhastobeinjected.Thisway,thecontrollercansimplycalltheform sisValid() methodonaPOST request.Ifvalidationwassuccessful,process() isexecuted.Ifnot,method error() iscalledinstead.Asprocessingasuccessfullyvalidatedformusuallyisspecifictotheform inquestion,AbstractFormController onlyshipswithanabstractmethodprocess().Itneedsto beimplementedindividuallybytheapplicationdeveloper. Inshort,AbstractFormController helpstoprevent Spaghetticode. incontrollersdealingwith forms.YoumaywonderwhysetParam() stillisusedtosetvaluesforkeyaction.It ssimply toensurethattemplatelookuplaterisstillworking.Therefore,AbstractFormController needsa show.phtml,process.phtml anderror.phtml templatetoworkproperly. Now,withAbstractFormController inplace,eachformgetsitsowncontroller.ProductAddFormController lookslikethis: .http://en.wikipedia.org/wiki/Spaghetti_code

Developers Dairy <?php namespace ZfDeals\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Zend\Stdlib\Hydrator\Reflection; use ZfDeals\Entity\Product as ProductEntity; use ZfDeals\Form\ProductAdd as ProductAddForm; class ProductAddFormController extends AbstractFormController { private $productMapper; public function __construct(ProductAddForm $form) { parent::__construct($form); } public function prepare() { $this->form->setHydrator(new Reflection()); $this->form->bind(new ProductEntity());

} public function process() { $model = new ViewModel( array( 'form' => $this->form ) ); try { $this->productMapper->insert($this->form->getData()); $model->setVariable('success', true); } catch (\Exception $e) { $model->setVariable('insertError', true); } return $model; }

Developers Dairy 43 public function setProductMapper($productMapper) 44 { 45 $this->productMapper = $productMapper; 46 } 47 48 public function getProductMapper() 49 { 50 return $this->productMapper; 51 } 52 } Listing26.40 DealAddFormController likethis: 1 <?php 2 namespace ZfDeals\Controller; 3 4 use Zend\Mvc\Controller\AbstractActionController; 5 use Zend\View\Model\ViewModel; 6 use Zend\Stdlib\Hydrator\Reflection; 7 use ZfDeals\Entity\Deal as DealEntity; 8 9 class DealAddFormController extends

AbstractFormController 10 { 11 private $productMapper; 12 private $dealMapper; 13 14 public function prepare() 15 { 16 $this->form->setHydrator(new Reflection()); 17 $this->form->bind(new DealEntity()); 18 19 $products = $this->productMapper->select(); 20 $fieldElements = array(); 21 22 foreach ($products as $product) { 23 $fieldElements[$product['id']] = $product['name']; 24 } 25 26 $this->form->get('deal') 27 ->get('product') 28 ->get('id')->setValueOptions($fieldElements);

Developers Dairy 29 } 30 31 public 32 { 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 } 51 52 public 53 { 54 55 } 56 57 public 58 { 59 60 } 61 62 public 63 { 64 65 } 66 67 public 68 { 69

70 } function process() $model = new ViewModel( array( 'form' => $this->form ) ); $newDeal = $this->form->getData(); $newDeal->setProduct($newDeal->getProduct()->getId()); try { $this->dealMapper->insert($newDeal); $model->setVariable('success', true); } catch (\Exception $e) { $model->setVariable('insertError', true); } return $model; function setProductMapper($productMapper) $this->productMapper = $productMapper;

function getProductMapper() return $this->productMapper; function setDealMapper($dealMapper) $this->dealMapper = $dealMapper; function getDealMapper() return $this->dealMapper;

Developers Dairy 71 } Listing26.41 Methodprepare() allowsforinitializationcoderunbeforeformprocessing,ifimplementedbya controller. Unit tests for AbstractFormController NowIaddunittestsforAbstractFormController. ThisiswhereIwilltestallgeneralform processinglogicinasingleplace,savingmealotoftimeasIcannowditchmostoftheindividua l formtests: 1 <?php 2 namespace ZfDeals\ControllerTest; 3 4 use ZfDeals\Controller\AbstractFormController; 5 use Zend\Http\Request; 6 use Zend\Http\Response; 7 use Zend\Mvc\MvcEvent; 8 use Zend\Mvc\Router\RouteMatch; 9 use ZfDeals\Form\ProductAdd as ProductAddForm; 10 11 class AbstractFormControllerTest extends \PHPUnit_Framework_TestCase 12 { 13 private $controller; 14 private $request; 15

private $response; 16 private $routeMatch; 17 private $event; 18 19 public function setUp() 20 { 21 $fakeController = $this->getMockForAbstractClass( 22 'ZfDeals\Controller\AbstractFormController', 23 array(), 24 '', 25 false 26 ); 27 28 $this->controller = $fakeController; 29 $this->request = new Request();

Developers Dairy 30 $this->response = new Response(); 31 32 $this->routeMatch = new RouteMatch( 33 array('controller' => 'abstract-form') 34 ); 35 36 $this->event = new MvcEvent(); 37 $this->event->setRouteMatch($this->routeMatch); 38 $this->controller->setEvent($this->event); 39 } 40 41 public function testShowOnGetRequest() 42 { 43 $this->form = new \Zend\Form\Form('fakeForm'); 44 $this->controller->setForm($this->form); 45 $this->request->setMethod('get'); 46 $response = $this->controller->dispatch($this->request); 47 $viewModelValues = $response->getVariables(); 48 $formReturned =

$viewModelValues['form']; 49 50 $this->assertEquals( 51 $formReturned->getName(), $this->form->getName() 52 ); 53 } 54 55 public function testErrorOnValidationError() 56 { 57 $fakeForm = $this->getMock( 58 'Zend\Form\Form', array('isValid') 59 ); 60 61 $fakeForm->expects($this->once()) 62 ->method('isValid') 63 ->will($this->returnValue(false)); 64 65 $this->controller->setForm($fakeForm); 66 $this->request->setMethod('post'); 67 $response = $this->controller->dispatch($this->request); 68 $viewModelValues = $response->getVariables(); 69 $formReturned = $viewModelValues['form']; 70 $this->assertEquals($formReturned, $fakeForm); 71 }

Developers Dairy 251 72 73 public function testProcessOnValidationSuccess() 74 { 75 $fakeForm = $this->getMock( 76 'Zend\Form\Form', array('isValid') 77 ); 78 79 $fakeForm->expects($this->once()) 80 ->method('isValid') 81 ->will($this->returnValue(true)); 82 83 $this->controller->setForm($fakeForm); 84 $this->request->setMethod('post'); 85 86 $this->controller->expects($this->once()) 87 ->method('process') 88 ->will($this->returnValue(true)); 89 90 $response = $this->controller->dispatch($this->request); 91 } 92 } Listing26.42 Use closures as simple factories Inmycode,Ihavemultiplefull-blownfactoriesonlydoingtinywork.DealAddFormController Factory forinstanceonlyretrievessomeservicesfromServiceManager andinjectsthemoncontroller creation: 1 <?php

2 namespace ZfDeals\Controller; 3 4 use Zend\ServiceManager\FactoryInterface; 5 use Zend\ServiceManager\ServiceLocatorInterface; 6 7 class DealAddFormControllerFactory implements FactoryInterface 8 { 9 public function createService(ServiceLocatorInterface $serviceLocator) 10 { 11 $form = new \ZfDeals\Form\DealAdd(); 12 $ctr = new DealAddFormController($form);

Developers Dairy 13 14 $dealMapper = $serviceLocator 15 ->getServiceLocator()->get('ZfDeals\Mapper\Deal'); 16 17 $ctr->setDealMapper($dealMapper); 18 19 $productMapper = $serviceLocator->getServiceLocator() 20 ->get('ZfDeals\Mapper\Product'); 21 22 $ctr->setProductMapper($productMapper); 23 return $ctr; 24 } 25 } Listing26.43 Tosavelinesofcode(andthoughlowertheriskofbugsinmyapplication)Imigratethefactorie s tosimpleclosuresinmodule sconfigfile: 1 <?php 2 // [..] 3 'controllers' => array( 4 'invokables' => array( 5 'ZfDeals\Controller\Admin' 6 => 'ZfDeals\Controller\AdminController', 7 ), 8 'factories' => array( 9

'ZfDeals\Controller\DealAddForm' => function ($serviceLocator) { 10 $form = new ZfDeals\Form\DealAdd(); 11 $ctr = new ZfDeals\Controller\DealAddFormController($form); 12 13 $dealMapper = $serviceLocator 14 ->getServiceLocator()->get('ZfDeals\Mapper\Deal'); 15 16 $ctr->setDealMapper($dealMapper); 17 18 $productMapper = $serviceLocator->getServiceLocator() 19 ->get('ZfDeals\Mapper\Product'); 20 21 $ctr->setProductMapper($productMapper); 22 return $ctr; 23 }, 24 'ZfDeals\Controller\ProductAddForm' => function ($serviceLocator) {

Developers Dairy 25 $form = new \ZfDeals\Form\ProductAdd(); 26 $ctr = new ZfDeals\Controller\ProductAddFormController($form); 27 28 $productMapper = $serviceLocator->getServiceLocator() 29 ->get('ZfDeals\Mapper\Product'); 30 31 $ctr->setProductMapper($productMapper); 32 return $ctr; 33 }, 34 'ZfDeals\Controller\Index' => function ($serviceLocator) { 35 $ctr = new ZfDeals\Controller\IndexController(); 36 37 $productMapper = $serviceLocator->getServiceLocator() 38 ->get('ZfDeals\Mapper\Product'); 39 40 $dealMapper = $serviceLocator->getServiceLocator() 41 ->get('ZfDeals\Mapper\Deal'); 42 43 $ctr->setDealMapper($dealMapper); 44 $ctr->setProductMapper($productMapper); 45

return $ctr; 46 } 47 ), 48 ), Listing26.44 AndthatwasitforSprint3! Sprint 4 -Order form SourceCodeDownloadThesourcecodeofSprint4canbedownloadedusingTag Sprint4 onGitHub.. .https://github.com/michael-romer/ZfDealsApp/tree/Sprint4 Again,beforeworkingonthisSprint srequirements,Iusethebreakbetweenthelastandthecurr ent sprint,called slacktime ,tofurthergetmycodebaseinabettershape.Today,Ifirstwanttoknow

Developers Dairy whatmy codecoverage is.Itdescribesthedegreetowhichthesourcecodeofaprogramhasbeen tested.ThankstoPHPUnit,thisisaneasytask.Isimpleneedtoaddsectionlogging tophpunit.xml: 1 <phpunit bootstrap="./bootstrap.php"> 2 <testsuites> 3 <testsuite name="AllTests"> 4 <directory>./ZfDealsTest/FormTest</directory> 5 <directory>./ZfDealsTest/ControllerTest</directory> 6 </testsuite> 7 </testsuites> 8 <logging> 9 <log type="coverage-html" 10 target="./reports/coverage" 11 charset="UTF-8" 12 yui="true" 13 highlight="false" 14 lowUpperBound="35" 15 highLowerBound="70" /> 16 </logging> 17 </phpunit> Nowwhenexecuting 1 $ phpunit notonlyalltestareexecuted,butnowalso CodeCoverageReports inHTMLformatarewritten todisk.Theyshowthetotalvaluesofcoverageataglanceaswellasdetailedinformationonsin gle linesofcode: 1 ZfDeals: 55.16% 2 config: 0% 3

src: 67.51% 4 Controller: 90.41% 5 Entity: 25% 6 Form: 79.31% 7 Mapper: 0% 8 Module.php: 0% WhatIseeisobviouslynotideal.Ingeneral,acodecoverageof70-80%isdesirableandIseetha tin someareasIdon thaveanytestsatall.Iwillneedtoworkthatthatforsure.

Developers Dairy There soneotherthingbeforeIgettotheUserStoriesofSprint4:Iwanttomakesure,rightfromt he beginning,thatZfDeals userinterfacesupportsmultiplelanguagesandshipsatleastwithEng lish andGermantranslations.First,IremovealllanguagefilesshippedwithZendSkeletonApplic ation inmoduleApplication.Idon tneedthem.Ialsodropmycustomformvalidationerrormessages andusetheZF2defaulterrormessageswhicharealreadytranslatedintothemostcommonlangua ges. Therefore,IcopyZend_Validate.php fromvendor/zendframework/zendframework/resources/languages/todirectorylanguage ofZfDealsandconfiguretranslator inmodule.config.php topickupthis languagefile: 1 <?php 2 // [..] 3 'translator' => array( 4 'locale' => 'de_DE', 5 'translation_file_patterns' => array( 6 array( 7 'type' => 'PhpArray', 8 'base_dir' => __DIR__ . '/../language', 9 'pattern' => '%s.php', 10 ), 11 ) 12 )

13 // [..] Listing26.45 Inaddition,Iconfiguremyvalidatorstoapplytranslator totheir1 <?php 2 // [..] 3 public function onBootstrap($e) 4 { errormessagesbydefault: 5 \Zend\Validator\AbstractValidator::setDefaultTranslator( 6 $e->getApplication()->getServiceManager()->get('translator') 7 ); 8 9 $eventManager = $e->getApplication()->getEventManager(); 10 $moduleRouteListener = new ModuleRouteListener(); 11 $moduleRouteListener> attach($eventManager); 12 } 13 // [..] Listing26.46

Developers Dairy NowIoncerunthroughallviewtemplatesandformsofthemoduletomakesuretranslator is usedwhenevertextisdisplayed. TheUserStoryofthissprintreadsasfollows: Inordertobuyaproductwithaspecialdiscountpriceasacustomer,Iwanttofillthe orderform. Acceptancecriteria: Allactivedealsshownonthesitehavea Buy button. Clickingthe Buy buttonbringsthecustomertoawebformpromptingforcustomername andshippingaddress.Alldataismandatory. AlistofallordersreceivedisaddedtotheadminsectionofZfDeals. Thismoreorlessnowalreadylookslikebusinessasusual.Fortheorderform,Isetuparoute,a form,acontrollerandentityorder.Order keepsareferencetoaProduct andstoresalladditional orderdata.AnewmapperforOrder takescareoforderpersistence.ServiceCheckout isZfDeals s first BusinessService .Checkout takescareofaddinganewOrder tothesystemaswellaslowering thestockofaproductorderedbyone.ThereasonIputthiscodeintoadedicatedserviceisbecau se Itouchtwodifferententitiessoitdoesn tnaturallyfitintooneofthem.Inaddition,Idon twant toputthecheckoutlogicintoCheckoutFormController becauseImaybewanttore-usethislogic elsewhere,inanotherControllerorwebservice.Mainly,theCheckoutService consistsofamethod calledprocess(): 1 <?php 2 public function process($ordering) 3{ 4 try { 5 $this->orderMapper->insert($ordering); 6 $deal

= $this->dealMapper->findOneById($ordering->getDeal()); 7 $product = $this->productMapper->findOneById($deal->getProduct()); 8 9 $this->productMapper->update( 10 array('stock' => $product->getStock() -1), 11 array('productId' => $product->getProductId()) 12 ); 13 14 } catch (\Exception $e) { 15 throw new \DomainException('Order could not be processed.'); 16 } 17 18 return true;

19 }

Developers Dairy Listing26.47 Thecodeisstraight-forward.Itsurelydoesnotyethandleanytypeofexceptionthatmayoccur , butthat sfinefornow.Atleast,itdoeswhatitshould. Andthat sit! Sprint 5 -Make ZfDeals available as a ZF2 module TheUserStoryofSprint5readsasfollows: InordertohaveZfDealsfunctionalityinmyownapplicationasadeveloper,Iwantto addtheZfDealsusingComposertomyowncode. A new repository for ZfDeals module Firstofall,Isetupanewgitrepositorydedicatedtothemodule scode.Imoveallmodulecode fromthehostapplicationtothenewrepository.ThenIaddthenewrepository(modulecode)asa gitsubmoduletotheexistingone(hostapplicationcode): 1 $ git submodule add https://github.com/michael-romer/ZfDeals module/ZfDeals Thecommandmustbeexecutedintheapplicationrootdirectoryanditrequiresthattheexistin g

directoryZfDeals hasbeendeletedbefore. NowIhaveahandydevelopmentenvironmentinplace:InmyworkspaceIhaveboth,anapplication hostingmymoduleaswellasthemoduleitself.Asbotharemanagedintheirownrepository,Ican committothemindividually,basedonwhereIrungitcommandsontheshell.AlsoIhavemy module ssourcecodeseparatedfromtherestsoIcandistributeiteasily.Ingeneral,thisisago od waytodevelopZF2moduleswheneveryouwanttokeepamodule scodeseparated. Whilemovingthefilesfromonerepositorytotheother,Iincludethemodule sstaticassetsinpu blic aswellasthelanguagefileandunittests.Theyallbelongtothemodulecodeandshallbedistri buted alongwithit.Additionally,ImovethedatabaseadapteroutofZfDeals module.config.php and addittotheapplication sconfiginstead.ThismeansthatifZfDealsisusedinanapplication,i t requirestheapplicationtoprovideadatabaseadaptertobeusedbyZfDeals.Thesameistruefo r translator.ThisisacommonpatternforZF2modules:Themostbasic,system-wideservices,su ch asadatabaseadapter,shallbegivenbythehostapplicationandthenbesharedbetweenallmodu les installed. TomakeZfDealsavailablethroughComposer,Iaddacomposer.json filetothemodule:

Developers Dairy 1 { 2 "name": "zf2book/zf-deals", 3 "description": "This is the companion to the 4 book 'Webentwicklung mit Zend Framework 2'", 5 "type": "library", 6 "keywords": [ 7 "zfdeals" 8 ], 9 "homepage": "http://zendframework2.de", 10 "authors": [ 11 { 12 "name": "Michael Romer", 13 "email": "zf2buch@michael-romer.de", 14 "homepage": "http://zendframework2.de" 15 } 16 ], 17 "require": { 18

"php": ">=5.3.3", 19 "zendframework/zendframework": "2.*" 20 }, 21 "autoload": { 22 "psr-0": { 23 "ZfDeals": "src/" 24 }, 25 "classmap": [ 26 "./Module.php" 27 ] 28 } 29 } Insectionautoload allclassesofdirectorysrc areconfiguredtobeautoloadedaswellasthe module smainModule class. NowIaddthemoduleasapackagetoPackagist.usingtheidentifierzf2book/zf-deals.Aftertha t, ZfDealnowcaneasilybeinstalledusingComposer.LastbutnotleastIaddREADME.md containing theinstallationinstructions: .https://packagist.org/packages/zf2book/zf-deals

Developers Dairy Install ======= Main Install 1. Add the following statement to the requirements-block of your composer.json: "zf2book/zf-deals": "dev-master", "dlu/dlutwbootstrap": "dev-master" 2. Run a composer update to download the libraries needed. 3. Add "ZfDeals" and "DluTwBootstrap" to the list of active modules in `application.config.php` 4. Import the SQL schema located in `/vendor/zf2book/zf-deals/data/structure.sql` 5. Copy

`/vendor/zf2book/zf-deals/data/public/zf-deals` to the public folder of your application. Post Install 1. If you do not already have a valid Zend\Db\Adapter\Adapter in your service manager configuration, put the following in `/config/autoload/db.local.php`: <?php $dbParams = array( 'database' => 'changeme', 'username' => 'changeme', 'password' => 'changeme', 'hostname' => 'changeme', ); return array( 'service_manager' => array(

'factories' => array( 'Zend\Db\Adapter\Adapter' => function ($sm) use ($dbParams) {

Developers Dairy 43 return new Zend\Db\Adapter\Adapter(array( 44 'driver' => 'pdo', 45 'dsn' => 46 'mysql:dbname='.$dbParams['database'].';host='.$dbParams['hostname'], 47 'database' => $dbParams['database'], 48 'username' => $dbParams['username'], 49 'password' => $dbParams['password'], 50 'hostname' => $dbParams['hostname'], 51 )); 52 }, 53 ), 54 ), 55 ); 56 57 2. Navigate to http://yourproject/deals or http://yourproject/deals/admin Asonecansee,besidesinstallingthemoduleviaComposer,thereissomemorestufftobedoneby adeveloperusingZfDeals:

InadditiontoZfDeals moduleDluTwBootstrap mustbeaddedtoapplication.config.php. ThisisanotherZF2moduleweutilizefordisplayingourforms. Thedatabasestructuregivenwithstructure.sql mustbecreated. StaticassetsusedbyZfDealsmustbecopiedovertothepublic directoryofthehost application. Simplify the module configuration Filemodule.config.php ofZfDeals alreadylooksabitmessy.Itholdsallroutedefinitionsaswell astheonesforservicesandcontrollers.Tomakeitmorereadable,Imovesomedefinitionsouto f thefile.First,Imoveallservicedefinitionstoservices.config.php: 1 <?php 2 return array( 3 'factories' => array( 4 'ZfDeals\Mapper\Product' => function ($sm) { 5 return new \ZfDeals\Mapper\Product( 6 $sm->get('Zend\Db\Adapter\Adapter') 7 ); 8 }, 9 'ZfDeals\Mapper\Deal' => function ($sm) { 10 return new \ZfDeals\Mapper\Deal(

Developers Dairy 11 $sm->get('Zend\Db\Adapter\Adapter') 12 ); 13 }, 14 'ZfDeals\Mapper\Order' => function ($sm) { 15 return new \ZfDeals\Mapper\Order( 16 $sm->get('Zend\Db\Adapter\Adapter') 17 ); 18 }, 19 'ZfDeals\Validator\DealAvailable' => function ($sm) { 20 $validator = new \ZfDeals\Validator\DealActive(); 21 $validator->setDealMapper($sm->get('ZfDeals\Mapper\Deal')); 22 23 $validator->setProductMapper( 24 $sm->get('ZfDeals\Mapper\Product') 25 ); 26 27 return $validator; 28 }, 29 'ZfDeals\Service\Checkout' => function ($sm) { 30 $srv

= new \ZfDeals\Service\Checkout(); 31 32 $srv->setDealAvailable( 33 $sm->get('ZfDeals\Validator\DealAvailable') 34 ); 35 36 $srv->setProductMapper($sm->get('ZfDeals\Mapper\Product')); 37 $srv->setOrderMapper($sm->get('ZfDeals\Mapper\Order')); 38 $srv->setDealMapper($sm->get('ZfDeals\Mapper\Deal')); 39 return $srv; 40 }, 41 ), 42 ); Listing26.48 Allcontrollerdefinitionsgotocontrollers.config.php:

Developers Dairy <?php return array( 'invokables' => array( 'ZfDeals\Controller\Admin' => 'ZfDeals\Controller\AdminController', ), 'factories' => array( 'ZfDeals\Controller\CheckoutForm' => function ($serviceLocator) { $form = new \ZfDeals\Form\Checkout(); $ctr = new ZfDeals\Controller\CheckoutFormController($form); $productMapper = $serviceLocator ->getServiceLocator()->get('ZfDeals\Mapper\Product'); $ctr->setProductMapper($productMapper); $validator = $serviceLocator->getServiceLocator() ->get('ZfDeals\Validator\DealAvailable'); $ctr->setdealActiveValidator($validator); $checkoutService = $serviceLocator ->getServiceLocator()->get('ZfDeals\Service\Checkout'); $ctr->setCheckoutService($checkoutService); return

$ctr; }, 'ZfDeals\Controller\DealAddForm' => function ($serviceLocator) { $form = new ZfDeals\Form\DealAdd(); $ctr = new ZfDeals\Controller\DealAddFormController($form); $dealMapper = $serviceLocator ->getServiceLocator()->get('ZfDeals\Mapper\Deal'); $ctr->setDealMapper($dealMapper); $productMapper = $serviceLocator ->getServiceLocator()->get('ZfDeals\Mapper\Product'); $ctr->setProductMapper($productMapper); return $ctr; }, 'ZfDeals\Controller\ProductAddForm' => function ($serviceLocator) {

Developers Dairy 263 43 $form = new \ZfDeals\Form\ProductAdd(); 44 $ctr = new ZfDeals\Controller\ProductAddFormController($form); 45 46 $productMapper = $serviceLocator 47 ->getServiceLocator()->get('ZfDeals\Mapper\Product'); 48 49 $ctr->setProductMapper($productMapper); 50 return $ctr; 51 }, 52 'ZfDeals\Controller\Index' => function ($serviceLocator) { 53 $ctr = new ZfDeals\Controller\IndexController(); 54 55 $productMapper = $serviceLocator 56 ->getServiceLocator() 57 ->get('ZfDeals\Mapper\Product'); 58 59 $dealMapper = $serviceLocator-> 60 getServiceLocator()-> 61 get('ZfDeals\Mapper\Deal'); 62 63 $ctr->setDealMapper($dealMapper);

64 $ctr->setProductMapper($productMapper); 65 return $ctr; 66 }, 67 'ZfDeals\Controller\Order' => function ($serviceLocator) { 68 $ctr = new ZfDeals\Controller\OrderController(); 69 70 $ctr->setOrderMapper($serviceLocator 71 ->getServiceLocator()->get('ZfDeals\Mapper\Order')); 72 73 return $ctr; 74 }, 75 ), 76 ); Listing26.49 IaddthefollowingmethodstoclassModule toincludetheadditionalconfigurationfiles:

Developers Dairy 1 <?php 2 // [..] 3 public function getServiceConfig() 4 { 5 return include __DIR__ . '/config/services.config.php'; 6 } 7 8 public function getControllerConfig() 9 { 10 return include __DIR__ . '/config/controllers.config.php'; 11 } 12 // [..] Listing26.50 Filemodule.config.php itselfnowonlyholdstheroutedefinitionsandviewconfiguration. A better approach for displaying forms Icanalsotweakformrendering.Currently,inallthreeshow.phtml templates,Imainlyhavethe codepresent: 1 <?php 2 $this->form->prepare(); 3 echo $this->form()->openTag($this->form);

4 echo $this->formRowTwb($this->form->get('product')->get('id')); 5 echo $this->formRowTwb($this->form->get('product')->get('name')); 6 echo $this->formRowTwb($this->form->get('product')->get('stock')); 7 echo $this->formSubmitTwb($this->form->get('submit')); 8 echo $this->form()->closeTag(); Listing26.51 Also,ifIaddanotherfieldtomyform,Iwillalsoneedchangethetemplatetomakeanew fieldappear.Acustom ViewHelper canhelphere.Thefollowinglinesofcoderenderaform dynamically,basedontheformdefinition.Itmakesformrenderingaone-liner: 1 <?php 2 echo $this->renderForm($form); Listing26.52 RenderForm itselflookslikethis:

Developers Dairy <?php namespace ZfDeals\View\Helper; use Zend\View\Helper\AbstractHelper; class RenderForm extends AbstractHelper { public function __invoke($form) { $form->prepare(); $html = $this->view->form()->openTag($form) . PHP_EOL; $html .= $this->renderFieldsets($form->getFieldsets()); $html .= $this->renderElements($form->getElements()); $html .= $this->view->form()->closeTag($form) . PHP_EOL; return $html; } private function renderFieldsets($fieldsets) { $html = ''; foreach($fieldsets

as $fieldset) { if(count($fieldset->getFieldsets()) > 0) { $html .= $this->renderFieldsets( $fieldset->getFieldsets() ); } $html .= $this->renderElements( $fieldset->getElements() ); } return $html; } private function renderElements($elements) { $html = ''; foreach($elements as $element) {

Developers Dairy 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 } } private { } $html .= $this->renderElement($element); } return $html; function renderElement($element) if($element->getAttribute('type') == 'submit') { return $this->view->formSubmitTwb($element) } else { return $this->view->formRow($element) . PHP_EOL; } . PHP_EOL; Listing26.53 AnimportantaspectofRenderForm isthatithandlesfieldsetsbyusingrecursion.RenderForm needs tobedeclaredbeforeitcanbeusedinviews.Again,tokeepthemoduleconfigurationslim,its declarationgoesintoaseparatefilecalledviewhelper.config.php: 1 2 3 4 5 6

<?php return ); array( 'invokables' => array( 'renderForm' => ) 'ZfDeals\View\Helper\RenderForm' Listing26.54 MethodgetViewHelperConfig() inclassModule makesitavailabletothemodule: 1 2 3 4 5 6 7 <?php // [..] public function getViewHelperConfig() { return include __DIR__ . '/config/viewhelper.config.php'; } // [..] Listing26.55 NowZfDealsisreadytobeusedin3rdpartyapplications!

Developers Dairy What s next? Granted,ZfDealsisnotyetfeaturerich,butalreadyfullyfunctionalandsomewhathelpful.W ecan nowgofromthereandaddfeaturebyfeature.Inaddition,thereissomemorerefactoringIcould dooneday.Let stakealook! Zend\Di for Dependency Injection IwrotealotofcodetoapplyDependencyInjectiontomyapplication.Mostofthehandcoded factorieslookwaymorecomplicatedthantheyshould.Mainly,theyallsimplycreateaservice or controllerbyonlyretrievingotherservicesandinjectingthemusing settermethods .Allthisf eels likeIviolatetheDRYprincipal..DoIreallyneedtorepeatmyselfandwritededicatedfactorie sto onlyinjectdependencies?ImaythinkaboutusingZend\Di.Doingsocouldmoreorlesseliminat e allofmyhandcodedfactories. Doctrine 2 for data persistence Next,ahugepartofmycodedealswithreadingdatafromandwritingdatatothedatabase.A systemlikeDoctrine2ORMcannearlyeliminateallthiscustomcodemakingdatapersistencefu lly transparenttotheapplicationdeveloper.JustdealwithyourPHPobjectsandletDoctrine2ta ke careofallthepersistencework. Utilize ZF2 s event system Currently,IassumethatZfDealsisused asis .Itnearlyhasnooptionsforcustomization.But whatif,forinstance,onewantstosendanordernotificationtocustomerservice?TomakeZfDe als extendable,ImayintroduceZF2 seventsystemandtriggerpropereventswhentheyoccur.This wouldallowdeveloperstoattacheventbasedcustomcodetoextendormodifyZfDeals sprocessin g

logic. Improve static assets handling Copyingoverstaticassetsfromthemoduletothehostapplicationspublic folderiserror-prone andinconvenient.Imayintroduceaproper AssetManager likezf2-module-assets.or Assetic withitsZF2 gluecodemodule zf2-assetic-module.4allowingtoservestaticassetsfromwithint he modulesdirectly. .http://de.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself .https://github.com/albulescu/zf2-module-assets .4https://github.com/widmogrod/zf2-assetic-module

Developers Dairy SourceCodedownloadYoucanfindtheDeveloper sDiarysourcecode,thehostapplicationaswell asmodulecode,onGitHubinRepository ZfDealsApp .andRepository ZfDeals .. .https://github.com/michael-romer/ZfDealsApp .https://github.com/michael-romer/ZfDeals

You might also like