Top Banner
157

Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Aug 31, 2018

Download

Documents

hoangdieu
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical
Page 2: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

1.1

1.2

2.1

2.2

3.1

3.2

3.3

4.1

4.2

4.3

5.1

5.2

6.1

6.2

6.3

TableofContentsIntroduction

Abouttheauthors

Configurationzend-configforallyourconfigurationneeds

Manageyourapplicationwithzend-config-aggregator

DataManipulationConvertobjectstoarraysandbackwithzend-hydrator

ScrapeScreenswithzend-dom

Paginatingdatacollectionswithzend-paginator

LogandFeedsLoggingPHPapplications

DiscoverandReadRSSandAtomFeeds

CreateRSSandAtomFeeds

AuthenticationandAuthorizationManagepermissionswithzend-permissions-rbac

Managepermissionswithzend-permissions-acl

WebServicesImplementJSON-RPCwithzend-json-server

ImplementanXML-RPCserverwithzend-xmlrpc

ImplementaSOAPserverwithzend-soap

2

Page 3: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

7.1

7.2

7.3

7.4

7.5

8.1

8.2

9.1

SecurityContext-specificescapingwithzend-escaper

Filterinputusingzend-filter

Validateinputusingzend-validator

Validatedatausingzend-inputfilter

End-to-endencryptionwithZendFramework3

DeploymentandVirtualizationCreateZPKstheEasyWay

UsingLaravelHomesteadwithZendFrameworkProjects

CopyrightCopyrightnote

3

Page 4: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ZendFramework3CookbookDuringtheyear2017,MatthewWeierO'PhinneyandEnricoZimuelstartedaseriesofblogpostsontheofficalZendFrameworkblogcoveringitscomponents.

ZendFrameworkiscomposedby60+componentscoveringawiderangeoffunctionality.Whiletheframeworkhastypicallybeenmarketedasafull-stackMVCframework,theindividualcomponentsthemselvestypicallyworkindependentlyandcanbeusedstandaloneorwithinotherframeworks.Theblogpostswerewrittentohighlightthisfact,anddemonstratehowtogetstartedwithanumberofthemorepopularandusefulcomponents.

WehopethisbookwillhelpyougetstartedusingZendFrameworkcomponents,nomatterwhatprojectyouarewriting!

Enjoyyourreading,MatthewWeierO'PhinneyandEnricoZimuelRogueWaveSoftware,Inc.

Introduction

4

Page 5: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Abouttheauthors

MatthewWeierO'PhinneyisaPrincipalEngineeratRogueWaveSoftware,andprojectleadfortheZendFramework,Apigility,andExpressiveprojects.He’sresponsibleforarchitecture,planning,andcommunityengagementforeachproject,whichareusedbythousandsofdevelopersworldwide,andshippedinprojectsfrompersonalwebsitestomultinationalmediaconglomerates,andeverythinginbetween.Whennotinfrontofacomputer,you'llfindhimwithhisfamilyanddogsontheplainsofSouthDakota.

Formoreinformation:

https://mwop.net/https://www.roguewave.com/

EnricoZimuelhasbeenasoftwaredevelopersince1996.HeworksasaSeniorSoftwareEngineeratRogueWaveSoftwareasacoredeveloperoftheZendFramework,Apigility,andExpressiveprojects.HeisaformerResearcherProgrammerfortheInformaticsInstituteoftheUniversityofAmsterdam.Enricospeaksregularlyatconferencesandevents,includingTEDxandinternationalPHPconferences.Heisalsotheco-founderofthePHPUserGroupofTorino(Italy).

Formoreinformation:

https://www.zimuel.it/

Abouttheauthors

5

Page 6: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

https://www.roguewave.com/TEDxpresentation:https://www.youtube.com/watch?v=SienrLY40-wPHPUserGroupofTorino:http://torino.grusp.org/

Abouttheauthors

6

Page 7: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Zend-configforallyourconfigurationneedsbyMatthewWeierO'Phinney

Differentapplicationsandframeworkshavedifferentopinionsabouthowconfigurationshouldbecreated.SomepreferXML,othersYAML,somelikeJSON,otherslikeINI,andsomeevensticktotheJavaPropertiesformat;inZendFramework,wetendtopreferPHParrays,aseachoftheotherformatsessentiallygetcompiledtoPHParrayseventuallyanyways.

Atheart,though,weliketosupportdeveloperneeds,whatevertheymaybe,and,assuch,ourzend-configcomponent provideswaysofworkingwithavarietyofconfigurationformats.

Installationzend-configisinstallableviaComposer:

$composerrequirezendframework/zend-config

Thecomponenthastwodependencies:

zend-stdlib ,whichprovidessomecapabilitiesaroundconfigurationmerging.psr/container ,toallowreaderandwriterpluginsupportfortheconfigurationfactory.

Latestversion

Thisarticlecoversthemostrecentlyreleasedversionofzend-config,3.1.0,whichcontainsanumberoffeaturessuchasPSR-11supportthatwerenotpreviouslyavailable.IfyouareusingZendFrameworkMVClayer,youshouldbeabletosafelyprovidetheconstraint ̂ 2.6||^3.1,astheprimaryAPIsremainthesame.

RetrievingconfigurationOnceyou'veinstalledzend-config,youcanstartusingittoretrieveandaccessconfigurationfiles.ThesimplestwayistouseZend\Config\Factory,whichprovidestoolsforloadingconfigurationfromavarietyofformats,aswellascapabilitiesformerging.

1

23

zend-configforallyourconfigurationneeds

7

Page 8: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Ifyou'rejustpullinginasinglefile,useFactory::fromFile():

useZend\Config\Factory;

$config=Factory::fromFile($path);

Farmoreinterestingistousemultiplefiles,whichyoucandoviaFactory::fromFiles().Whenyoudo,theyaremergedintoasingleconfiguration,intheorderinwhichtheyareprovidedtothefactory.Thisisparticularlyinterestingusingglob():

useZend\Config\Factory;

$config=Factory::fromFiles(glob('config/autoload/*.*'));

Thismethodsupportsavarietyofformats:

PHPfilesreturningarrays(.phpextension)INIfiles(.iniextension)JSONfiles(.jsonextension)XMLfiles(usingPHP'sXMLReader;.xmlextension)YAMLfiles(usingext/yaml,installableviaPECL;.yamlextension)JavaPropertiesfiles(.javapropertiesextension)

Thismeansthatyoucanchoosetheconfigurationformatyouprefer,ormix-and-matchmultipleformats,ifyouneedtocombineconfigurationfrommultiplelibraries!

ConfigurationobjectsBydefault,Zend\Config\FactorywillreturnPHParraysforthemergedconfiguration.Somedependencyinjectioncontainersdonotsupportarraysasservices,however;moreover,youmaywanttopasssomesortofstructuredobjectinsteadofaplainarraywheninjectingdependencies.

Assuch,youcanpassasecond,optionalargumenttoeachoffromFile()andfromFiles(),abooleanflag.Whentrue,itwillreturnaZend\Config\Configinstance,whichimplementsCountable,Iterator,andArrayAccess,allowingittolookandactlikeanarray.

Whatisthebenefit?

First,itprovidespropertyoverloadingtoeachconfigurationkey:

zend-configforallyourconfigurationneeds

8

Page 9: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$debug=$config->debug??false;

Second,itoffersaconveniencemethod,get(),whichallowsyoutospecifyadefaultvaluetoreturnifthevalueisnotfound:

$debug=$config->get('debug',false);//Returnfalseifnotfound

Thisislargelyobviatedbythe??ternaryshortcutinmodernPHPversions,butveryusefulwhenmockinginyourtests.

Third,nestedsetsarealsoreturnedasConfiginstances,whichgivesyoutheabilitytousetheaboveget()methodonanesteditem:

if(isset($config->expressive)){

$config=$config->get('expressive');//sameAPI!

}

Fourth,youcanmarktheConfiginstanceasimmutable!Bydefault,itactsjustlikearrayconfiguration,whichis,ofcourse,mutable.However,thiscanbeproblematicwhenyouuseconfigurationasaservice,because,unlikeanarray,aConfiginstanceispassedbyreference,andchangestovalueswouldthenpropagatetoanyotherservicesthatdependontheconfiguration.

Ideally,youwouldn'tbechanginganyvaluesintheinstance,butZend\Config\Configcanenforcethatforyou:

$config->setReadOnly();//Nowimmutable!

Further,callingthiswillmarknestedConfiginstancesasread-onlyaswell,ensuringdataintegrityfortheentireconfigurationtree.

zend-configforallyourconfigurationneeds

9

Page 10: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Read-onlybydefault!

Onethingtonote:bydefault,Configinstancesareread-only!Theconstructoracceptsanoptional,secondargument,aflagindicatingwhetherornottheinstanceallowsmodifications,andthevalueisfalsebydefault.WhenyouusetheFactorytocreateaConfiginstance,itneverenablesthatflag,meaningthatifyoureturnaConfiginstance,itwillberead-only.

IfyouwantamutableinstancefromaFactory,usethefollowingconstruct:

useZend\Config\Config;

useZend\Config\Factory;

$config=newConfig(Factory::fromFiles($files),true);

IncludingotherconfigurationMostoftheconfigurationreaderpluginsalsosupport"includes":directiveswithinaconfigurationfilethatwillincludeconfigurationfromanotherfile.(JavaPropertiesistheonlyconfigurationformatwesupportthatdoesnothavethisfunctionalityincluded.)

Forinstance:

INIfilescanusethekey@includetoincludeanotherfilerelativetothecurrentone;valuesaremergedatthesamelevel:

webhost='www.example.com'

@include='database.ini'

ForXMLfiles,youcanuseXInclude:

<?xmlversion="1.0"encoding="utf-8">

<configxmlns:xi="http://www.w3.org/2001/XInclude">

<webhost>www.example.com</webhost>

<xi:includehref="database.xml"/>

</config>

JSONfilescanusean@includekey:

{

"webhost":"www.example.com",

"@include":"database.json"

}

zend-configforallyourconfigurationneeds

10

Page 11: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

YAMLalsousesthe@includenotation:

webhost:www.example.com

@include:database.yaml

ChooseyourownYAMLOut-of-the-boxwesupporttheYAMLPECLextensionforourYAMLsupport.However,wehavemadeitpossibletousealternateparsers,suchasSpycortheSymfonyYAMLcomponent,bypassingacallbacktothereader'sconstructor:

useSymfony\Component\Yaml\YamlasSymfonyYaml;

useZend\Config\Reader\YamlasYamlConfig;

$reader=newYamlConfig([SymfonfyYaml::class,'parse']);

$config=$reader->fromFile('config.yaml');

Ofcourse,ifyou'regoingtodothat,youcouldjustusetheoriginallibrary,right?ButwhatifyouwanttomixYAMLandotherconfigurationwiththeFactoryclass?

Therearetwowaystoregisternewplugins.Oneistocreateaninstanceandregisteritwiththefactory:

useSymfony\Component\Yaml\YamlasSymfonyYaml;

useZend\Config\Factory;

useZend\Config\Reader\YamlasYamlConfig;

Factory::registerReader('yaml',newYamlConfig([SymfonyYaml::class,'parse']));

Alternately,youcanprovideanalternatereaderpluginmanager.YoucandothatbyextendingZend\Config\StandaloneReaderPluginManager,whichisabarebonesPSR-11containerforuseasapluginmanager:

zend-configforallyourconfigurationneeds

11

Page 12: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceAcme;

useSymfony\Component\Yaml\YamlasSymfonyYaml;

useZend\Config\Reader\YamlasYamlConfig;

useZend\Config\StandaloneReaderPluginManager;

classReaderPluginManagerextendsStandaloneReaderPluginManager

{

/**

*@inheritDoc

*/

publicfunctionhas($plugin)

{

if(YamlConfig::class===$plugin

||'yaml'===strtolower($plugin)

){

returntrue;

}

returnparent::has($plugin);

}

/**

*@inheritDoc

*/

publicfunctionget($plugin)

{

if(YamlConfig::class!==$plugin

&&'yaml'!==strtolower($plugin)

){

returnparent::get($plugin);

}

returnnewYamlConfig([SymfonyYaml::class,'parse']);

}

}

ThenregisterthiswiththeFactory:

useAcme\ReaderPluginManager;

useZend\Config\Factory;

Factory::setReaderPluginManager(newReaderPluginManager());

Processingconfigurationzend-configalsoallowsyoutoprocessaZend\Config\Configinstanceand/oranindividualvalue.Processorsperformoperationssuchas:

zend-configforallyourconfigurationneeds

12

Page 13: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

substitutingconstantvalueswithinstringsfilteringconfigurationdatareplacingtokenswithinconfigurationtranslatingconfigurationvalues

Whywouldyouwanttodoanyoftheseoperations?

Considerthis:deserializationofformatsotherthanPHPcannottakeintoaccountPHPconstantvaluesorclassnames!

WhilethismayworkinPHP:

return[

Acme\Component::CONFIG_KEY=>[

'host'=>Acme\Component::CONFIG_HOST,

'dependencies'=>[

'factories'=>[

Acme\Middleware\Authorization::class=>Acme\Middleware\AuthorizationF

actory::class,

],

],

],

];

ThefollowingJSONconfigurationwouldnot:

{

"Acme\\Component::CONFIG_KEY":{

"host":"Acme\\Component::CONFIG_HOST"

"dependencies":{

"factories":{

"Acme\\Middleware\\Authorization::class":"Acme\\Middleware\\Authoriza

tionFactory::class"

}

}

}

}

EntertheConstantprocessor!

Thisprocessorlooksforstringsthatmatchconstantnames,andreplacesthemwiththeirvalues.Processorsgenerallyonlyworkontheconfigurationvalues,buttheConstantprocessorallowsyoutoopt-intoprocessingthekeysaswell.

SinceprocessingmodifiestheConfiginstance,youwillneedtomanuallycreateaninstance,andthenprocessit.Let'slookatthat:

zend-configforallyourconfigurationneeds

13

Page 14: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useAcme\Component;

useZend\Config\Config;

useZend\Config\Factory;

useZend\Config\Processor;

$config=newConfig(Factory::fromFile('config.json'),true);

$processor=newProcessor\Constant();

$processor->enableKeyProcessing();

$processor->process($config);

$config->setReadOnly();

var_export($config->{Component::CONFIG_KEY}->dependencies->factories);

//['Acme\Middleware\Authorization'=>'Acme\Middleware\AuthorizationFactory']

Thisisareallypowerfulfeature,asitallowsyoutoaddmoreverificationsandvalidationstoyourconfigurationfiles,regardlessoftheformatyouuse.

Inversion3.1.0forward

Theabilitytoworkwithclassconstantsandprocesskeyswasaddedstartingwiththe3.1.0versionofzend-config.

Configallthethings!Thispostcoverstheparsingfeaturesofzend-config,butdoesnoteventouchonanothermajorcapability:theabilitytowriteconfiguration!We'llleavethattoanotherpost.

Intermsofconfigurationparsing,zend-configissimple,yetpowerful.Theabilitytoprocessanumberofcommonconfigurationformats,utilizeconfigurationincludes,andprocesskeysandvaluesmeansyoucanhighlycustomizeyourconfigurationprocesstosuityourneedsorintegratedifferentconfigurationsources.

Getmoreinformationfromthezend-configdocumentation .

Footnotes

.https://docs.zendframework.com/zend-config/↩

.https://docs.zendframework.com/zend-stdlib/↩

.https://github.com/php-fig/container↩

.https://docs.zendframework.com/zend-config/↩

4

1

2

3

4

zend-configforallyourconfigurationneeds

14

Page 15: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

zend-configforallyourconfigurationneeds

15

Page 16: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Manageyourapplicationwithzend-config-aggregatorbyMatthewWeierO'Phinney

WiththeriseofPHPmiddleware,manydevelopersarecreatingcustomapplicationarchitectures,andrunningintoanissuemanyframeworksalreadysolve:howtoallowruntimeconfigurationoftheapplication.

configurationisoftennecessary,evenincustomapplications:

Someconfiguration,suchasAPIkeys,mayvarybetweenenvironments.Youmaywanttosubstituteservicesbetweendevelopmentandproduction.Somecodemaybedevelopedbyotherteams,andpulledintoyourapplicationseparately(perhapsviaComposer ),andrequireconfiguration.Youmaybewritingcodeinyourapplicationthatyouwilllaterwanttosharewithanotherteam,andrecognizeitshouldprovideservicewiringinformationorallowfordynamicconfigurationitself.

Facedwiththisreality,youthenhaveanewproblem:howcanyouconfigureyourapplication,aswellasaggregateconfigurationfromothersources?

AspartoftheExpressiveinitiative,wenowofferastandalonesolutionforyou:zend-config-aggregator .

InstallationFirst,youwillneedtoinstallzend-config-aggregator:

$composerrequirezendframework/zend-config-aggregator

Onefeatureofzend-config-aggregatoristheabilitytoconsumemultipleconfigurationformatsviazend-config .Ifyouwishtousethatfeature,youwillalsoneedtoinstallthatpackage:

$composerrequirezendframework/zend-config

Finally,ifyouareusingtheabove,andwanttoparseYAMLfiles,youwillneedtoinstalltheYAMLPECLextension .

1

2

3

4

Manageyourapplicationwithzend-config-aggregator

16

Page 17: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Configurationproviderszend-config-aggregatorallowsyoutoaggregateconfigurationfromconfigurationproviders.AconfigurationproviderisanyPHPcallablethatwillreturnanassociativearrayofconfiguration.

Bydefault,thecomponentprovidesthefollowingprovidersoutofthebox:

Zend\ConfigAggregator\ArrayProvider,whichacceptsanarrayofconfigurationandsimplyreturnsit.Thisisprimarilyusefulforprovidingglobaldefaultsforyourapplication.Zend\ConfigAggregator\PhpFileProvider,whichacceptsaglobpatterndescribingPHPfilesthateachreturnanassociativearray.Wheninvoked,itwillloopthrougheachfile,andmergetheresultswithwhatithaspreviouslystored.Zend\ConfigAggregator\ZendConfigProvider,whichactssimilarlytothePhpFileProvider,butwhichcanaggregateanyformatzend-configsupports,includingINI,XML,JSON,andYAML.

Moreinterestingly,however,isthefactthatyoucanwriteprovidersassimpleinvokableobjects:

namespaceAcme;

classConfigProvider

{

publicfunction__invoke()

{

return[

//associativearrayofconfiguration

];

}

}

Thisfeatureallowsyoutowriteconfigurationforspecificapplicationfeatures,andthenseedyourapplicationwithit.Inotherwords,thisfeaturecanbeusedasthefoundationforamodulararchitecture ,whichisexactlywhatwedidwithExpressive!5

Manageyourapplicationwithzend-config-aggregator

17

Page 18: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Generators

YoumayalsouseinvokableclassesorPHPcallablesthatdefinegeneratorsasconfigurationproviders!Asanexample,thePhpFileProvidercouldpotentiallyberewrittenasfollows:

useZend\Stdlib\Glob;

function(){

foreach(Glob::glob('config/*.php',Glob::GLOB_BRACE)as$file){

yieldinclude$file;

}

}

AggregatingconfigurationNowthatyouhaveconfigurationproviders,youcanaggregatethem.

Forthepurposesofthisexample,we'llassumethefollowing:

Wewillhaveasingleconfigurationfile,config.php,attherootofourapplicationwhichwillaggregateallotherconfiguration.Wehaveanumberofconfigurationfilesunderconfig/,includingYAML,JSON,andPHPfiles.Wehaveathird-party"module"thatexposestheclassUmbrella\ConfigProvider.Wehavedevelopedourown"module"forre-distributionthatexposestheclassBlanket\ConfigProvider.

Typically,youwillwantaggregateconfigurationsuchthatthird-partyconfigurationisloadedfirst,withapplication-specificconfigurationmergedlast,inordertooverridesettings.

Let'saggregateandreturnourconfiguration.

//inconfig.php:

useZend\ConfigAggregator\ConfigAggregator;

useZend\ConfigAggregator\ZendConfigProvider;

$aggregator=newConfigAggregator([

\Umbrella\ConfigProvider::class,

\Blanket\ConfigProvider::class,

newZendConfigProvider('config/*.{json,yaml,php}'),

]);

return$aggregator->getMergedConfig();

Manageyourapplicationwithzend-config-aggregator

18

Page 19: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Thisfileaggregatesthethird-partyconfigurationprovider,theoneweexposeinourownapplication,andthenaggregatesavarietyofdifferentconfigurationfilesinorderto,intheend,returnanassociativearrayrepresentingthemergedconfiguration!

Validconfigprofiderentries

You'llnotethattheConfigAggregatorexpectsanarrayofprovidersasthefirstargumenttotheconstructor.Thisarraymayconsistofanyofthefollowing:

AnyPHPcallable(functions,invokableobjects,closures,etc.)returninganarray.Aclassnameofaclassthatdefines__invoke(),andwhichrequiresnoconstructorarguments.

Thislatterisuseful,asithelpsreduceoperationaloverheadonceyouintroducecaching,whichwediscussbelow.Theaboveexampledemonstratesthisusage.

zend-configandPHPconfiguration

TheaboveexampleusesonlytheZendConfigProvider,andnotthePhpFileProvider.Thisisduetothefactthatzend-configcanalsoconsumePHPconfiguration.

IfyouareonlyusingPHP-basedconfigurationfiles,youcanusethePhpFileProviderinstead,asitdoesnotrequireadditionallyinstallingthezendframework/zend-configpackage.

Globbingandprecedence

Globbingworksasitdoesonmost*nixsystems.Assuch,youneedtopayparticularattentiontowhenyouusepatternsthatdefinealternatives,suchasthe{json,yaml,php}patternabove.Insuchcases,allJSONfileswillbeaggregated,followedbyYAMLfiles,andfinallyPHPfiles.Ifyouneedthemtoaggregateinadifferentorder,youwillneedtochangethepattern.

CachingYoulikelydonotwanttoaggregateconfigurationoneachandeveryapplicationrequest,particularlyifdoingsowouldresultinmanyfilesystemhits.Fortunately,zend-config-aggregatoralsohasbuilt-incachingfeatures.

Toenablethesefeatures,youwillneedtodotwothings:

First,youneedtoprovideasecondargumenttotheConfigAggregatorconstructor,

Manageyourapplicationwithzend-config-aggregator

19

Page 20: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

specifyingthepathtothecachefiletocreateand/oruse.Second,youneedtoenablecachinginyourconfiguration,byspecifyingabooleantruevalueforthekeyConfigAggregator::ENABLE_CACHE.

Onecommonstrategyistoenablecachingbydefault,andthendisableitviaenvironment-specificconfiguration.

We'llupdatetheaboveexamplenowtoenablecachingtothefilecache/config.php:

useZend\ConfigAggregator\ArrayProvider;

useZend\ConfigAggregator\ConfigAggregator;

useZend\ConfigAggregator\PhpFileProvider;

useZend\ConfigAggregator\ZendConfigProvider;

$aggregator=newConfigAggregator(

[

newArrayProvider([ConfigAggregator::ENABLE_CACHE=>true]),

\Umbrella\ConfigProvider::class,

\Blanket\ConfigProvider::class,

newZendConfigProvider('config/{,*.}global.{json,yaml,php}'),

newPhpFileProvider('config/{,*.}local.php'),

],

'cache/config.php'

);

return$aggregator->getMergedConfig();

Theaboveaddsaninitialsettingthatenablesthecache,andtellsittocacheittocache/config.php.

NoticealsothatthisexamplechangestheZendConfigProvider,andaddsaPhpFileProviderentry.Let'sexaminethese.

TheZendConfigProviderglobpatternnowlooksforfilesnamedglobalwithoneoftheacceptedextensions,orthosenamed*.globalwithoneoftheacceptedextensions.Thisallowsustosegregateconfigurationthatshouldalwaysbepresentfromenvironment-specificconfiguration.

WethenaddaPhpFileProviderthataggregateslocal.phpand/or*.local.phpfilesspecifically.Aninterestingside-noteabouttheshippedprovidersisthatifnomatchingfilesarefound,theproviderwillreturnanemptyarray;thismeansthatwecanhavethisadditionalproviderthatislookingforseparateconfigurationsforthe"local"environment!Becausethisproviderisaggregatedlast,thesettingsitexposeswilloverrideanyothers.

Assuch,ifwewanttodisablecaching,wecancreateafilesuchasconfig/local.phpwiththefollowingcontents:

Manageyourapplicationwithzend-config-aggregator

20

Page 21: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

<?php

useZend\ConfigAggregator\ConfigAggregator;

return[ConfigAggregator::ENABLE_CACHE=>false];

andtheapplicationwillnolongercacheaggregatedconfiguration!

Clearthecache!

Thesettingoutlinedaboveisusedtodeterminewhethertheconfigurationcachefileshouldbecreatedifitdoesnotalreadyexist.zend-config-aggregator,whenprovidedthelocationofaconfigurationcachefile,willloaddirectlyfromitifthefileispresent.

Assuch,ifyoumaketheaboveconfigurationchange,youwillfirstneedtoremoveanycachedconfiguration:

$rmcache/config.php

ThiscanevenbemadeintoaComposerscript:

"scripts":{

"clear-config-cache":"rmcache/config.php"

}

Allowingyoutodothis:

$composerclear-config-cache

Whichallowsyoutochangethelocationofthecachefilewithoutneedingtore-learnthelocationeverytimeyouneedtoclearthecache.

Auto-enablingthird-partyprovidersBeingabletoaggregateprovidersfromthird-partiesisprettystellar;itmeansthatyoucanbeassuredthatconfigurationthethird-partycodeexpectsisgenerallypresent—withtheexceptionofvaluesthatmustbeprovidedbytheconsumer,thatis!

However,there'soneminorproblem:youneedtoremembertoregistertheseconfigurationproviderswithyourapplication,bymanuallyeditingyourconfig.phpfileandaddingtheappropriateentries.

6

Manageyourapplicationwithzend-config-aggregator

21

Page 22: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ZendFrameworksolvesthisviathezf-component-installerComposerplugin .IfyourpackageisinstallableviaComposer,youcanaddanentrytoyourpackagedefinitionasfollows:

"extra":{

"zf":{

"config-provider":[

"Umbrella\\ConfigProvider"

]

}

}

Iftheend-user:

Hasrequiredzendframework/zend-component-installerintheirapplication(aseitheraproductionordevelopmentdependency),ANDhastheconfigaggregationscriptinconfig/config.php

thenthepluginwillpromptyou,askingifyouwouldliketoaddeachoftheconfig-providerentriesfoundintheinstalledpackageintotheconfigurationscript.

Assuch,forourexampletowork,wewouldneedtomoveourconfigurationscripttoconfig/config.php,andlikelymoveourotherconfigurationfilesintoasub-directory:

cache/

config.php

config/

config.php

autoload/

blanket.global.yaml

global.php

umbrella.global.json

ThisapproachisessentiallythattakenbyExpressive.

Whenthosechangesaremade,anypackageyouaddtoyourapplicationthatexposesconfigurationproviderswillpromptyoutoaddthemtoyourconfigurationaggregation,and,ifyouconfirm,willaddthemtothetopofthescript!

FinalnotesFirst,wewouldliketothankMateuszTymek ,whoseprototype'expressive-config-manager'projectbecamezend-config-aggregator.Thisisastellarexampleofacommunityprojectgettingadoptedintotheframework!

6

7

Manageyourapplicationwithzend-config-aggregator

22

Page 23: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Second,thisapproachhassomeaffinitytoaproposalfromthefolkswhobroughtusPSR-11,whichdefinestheContainerInterfaceusedwithinExpressiveforallowingusageofdifferentdependencyinjectioncontainers.Thatsamegroupisnowworkingonaserviceprovider proposalthatwouldstandardizehowstandalonelibrariesexposeservicestocontainers;werecommendlookingatthatprojectaswell.

Wehopethatthisposthelpsspawnideasforconfiguringyournextproject!

Footnotes

.https://getcomposer.org↩

.https://github.com/zendframework/zend-config-aggregator↩

.https://docs.zendframework.com/zend-config/↩

.http://www.php.net/manual/en/book.yaml.php↩

.https://docs.zendframework.com/zend-expressive/features/modular-applications/↩

.https://docs.zendframework.com/zend-component-installer/↩

.http://mateusztymek.pl/↩

.https://github.com/container-interop/service-provider↩

8

1

2

3

4

5

6

7

8

Manageyourapplicationwithzend-config-aggregator

23

Page 24: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Convertobjectstoarraysandbackwithzend-hydratorbyMatthewWeierO'Phinney

APIsarealltheragethesedays,andatremendousnumberofthemarebeingwritteninPHP.WhenAPIswerefirstgainingpopularity,thisseemedlikeamatchmadeinheaven:querythedatabase,passtheresultstojson_encode(),andvoilà!APIpayload!Inreverse,it'sjson_decode(),passthedatatothedatabase,anddone!

ModerndayprofessionalPHP,however,isskewingtowardsusageofvalueobjectsandentities,butwe'restillcreatingAPIs.HowcanwetaketheseobjectsandcreateourAPIresponsepayloads?Howcanwetakeincomingdataandtransformitintothedomainobjectsweneed?

ZendFramework'sanswertothatquestioniszend-hydrator.Hydratorscanextractanassociativearrayofdatafromanobject,andhydrateanobjectfromanassociativearrayofdata.

InstallationAswithourothercomponents,youcaninstallzend-hydratorbyitself:

$composerrequirezendframework/zend-hydrator

Out-of-the-box,itonlyrequireszend-stdlib,whichisusedinternallyfortransformingiteratorstoassociativearrays.However,thereareanumberofotherinteresting,ifoptional,featuresthatrequireothercomponents:

Youcancreateanaggregatehydratorwhereeachhydratorisresponsibleforasubsetofdata.Thisrequireszend-eventmanager.Youcanfilter/normalizethekeys/propertiesofdatausingnamingstrategies;theserequirezend-filter.Youcanmapobjecttypestohydrators,anddelegatehydrationofarbitraryobjectsusingtheDelegatingHydrator.ThisfeatureutilizestheprovidedHydratorPluginManager,whichrequireszend-servicemanager.

Inourexamplesbelow,we'llbedemonstratingnamingstrategiesandthedelegatinghydrator,sowewillinstallthedependenciesthoseneed:

Convertobjectstoarraysandbackwithzend-hydrator

24

Page 25: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$composerrequirezendframework/zend-filterzendframework/zend-servicemanager

ObjectstoarraysandbackagainLet'stakethefollowingclassdefinition:

namespaceAcme;

classBook

{

private$id;

private$title;

private$author;

publicfunction__construct(int$id,string$title,string$author)

{

$this->id=$id;

$this->title=$title;

$this->author=$author;

}

}

Whatwehaveisavalueobject,withnowaytopubliclygrabanygivendatum.WenowwanttorepresentitinourAPI.Howdowedothat?

Theanswerisviareflection,andzend-hydratorprovidesasolutionforthat:

useAcme\Book;

useZend\Hydrator\ReflectionasReflectionHydrator;

$book=newBook(42,'Hitchhiker\'sGuidetotheGalaxy','DouglasAdams');

$hydrator=newReflectionHydrator();

$data=$hydrator->extract($book);

WenowhaveanarrayrepresentationofourBookinstance!

Let'ssaythatsomebodyhasjustsubmittedabookviaawebformoranAPI.Wehavethevalues,butwanttocreateaBookoutofthem.

Convertobjectstoarraysandbackwithzend-hydrator

25

Page 26: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useAcme\Book;

useReflectionClass;

useZend\Hydrator\ReflectionasReflectionHydrator;

$hydrator=newReflectionHydrator();

$book=$hydrator->hydrate(

$incomingData,

(newReflectionClass(Book::class))->newInstanceWithoutConstructor()

);

AndnowwehaveaBookinstance!

ThenewInstanceWithoutConstructor()constructisnecessaryinthiscasebecauseourclasshasrequiredconstructorarguments.Anotherpossibilityistoprovideanalreadypopulatedinstance,andhopethatthesubmitteddatawilloverwritealldataintheclass.Alternately,youcancreateclassesthathaveoptionalconstructorarguments.

Mostofthetime,itcanbeassimpleasthis:createanappropriatehydratorinstance,anduseeitherextract()togetanarrayrepresentationoftheobject,orhydrate()tocreateaninstancefromanarrayofdata.

Weprovideanumberofstandardimplementations:

Zend\Hydrator\ArraySerializableworkswithArrayObjectimplementations.ItwillalsohydrateanyobjectimplementingeitherthemethodexchangeArray()orpopulate(),andextractfromanyobjectimplementinggetArrayCopy().Zend\Hydrator\ClassMethodswillusesetterandgettermethodstopopulateandextractobjects.Italsounderstandshas*()andis*()methodsasgetters.Zend\Hydrator\ObjectPropertywillusepublicinstanceproperties.Zend\Hydrator\Reflectioncanextractandpopulateinstancepropertiesofanyvisibility.

FilteringvaluesSinceacommonrationaleforextractingdatafromobjectsistocreatepayloadsforAPIs,youmayfindthereisdatainyourobjectyoudonotwanttorepresent.

zend-hydratorprovidesaZend\Hydrator\Filter\FilterInterfaceforaccomplishingthis.Filtersimplementthefollowing:

Convertobjectstoarraysandbackwithzend-hydrator

26

Page 27: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceZend\Hydrator\Filter;

interfaceFilterInterface

{

/**

*@paramstring$property

*@returnbool

publicfunctionfilter($property);

}

Ifafilterreturnsabooleantrue,thevalueiskept;otherwise,itisomitted.

AFilterCompositeimplementationallowsattachingmultiplefilters;eachpropertyisthencheckedagainsteachfilter.(ThisclassalsoallowsattachingstandardPHPcallablesforfilters,insteadofFilterInterfaceimplementations.)AFilterEnabledInterfaceallowsahydratortoindicateitcomposesfilters.Tyingittogether,allshippedhydratorsinheritfromacommonbasethatimplementsFilterEnabledInterfacebycomposingaFilterComposite,whichmeansthatyoucanusefiltersimmediatelyinastandardfashion.

Asanexample,let'ssaywehaveaUserclassthathasapasswordproperty;weclearlydonotwanttoreturnthepasswordinourpayload,evenifitisproperlyhashed!Filterstotherescue!

useZend\Hydrator\ObjectPropertyasObjectPropertyHydrator;

$hydrator=newObjectPropertyHydrator();

$hydrator->addFilter('password',function($property){

return$property!=='password';

});

$data=$hydrator->extract($user);

Somehydratorsactuallyusefiltersinternallyinordertodotheirwork.Asanexample,theClassMethodshydratorcomposesthefollowingbydefault:

IsFilter,toidentifymethodsbeginningwithis,suchasisTransaction().HasFilter,toidentifymethodsbeginningwithhas,suchashasAuthor().GetFilter,toidentifymethodsbeginningwithget,suchasgetTitle().OptionalParametersFilter,toensureanygivenmatchedmethodcanbeexecutedwithoutrequiringanyarguments.

Thislatterpointbringsupaninterestingfeature:sincehydrationrunseachpotentialpropertynamethrougheachfilter,youmayneedtosetuprules.Forexample,withtheClassMethodshydrator,agivenmethodnameisvalidifthefollowingconditionismet:

Convertobjectstoarraysandbackwithzend-hydrator

27

Page 28: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

(matches"is"||matches"has"||matches"get")&&matches"optionalparameters"

Assuch,whencallingaddFilter(),youcanspecifyanoptionalthirdargument:aflagindicatingwhethertoORorANDthegivenfilter(usingthevaluesFilterComposite::CONDITION_ORorFilterComposite::FILTER_AND);thedefaultistoORthenewfilter.

Filteringisverypowerfulandflexible.Ifyourememberonlytwothingsaboutfilters:

Theyonlyoperateduringextraction.Theycanonlybeusedtodeterminewhatvaluestokeepintheextracteddataset.

StrategiesWhatifyouwantedtoalterthevaluesreturnedduringextractionorhydration?zend-hydratorprovidesthesefeaturesviastrategies.

Astrategyprovidesfunctionalitybothforextractingandhydratingavalue,andsimplytransformsit;thinkofstrategiesasnormalizationfilters.EachimplementsZend\Hydrator\Strategy\StrategyInterface:

namespaceZend\Hydrator\Strategy;

interfaceStrategyInterface

{

publicfunctionextract($value;)

publicfunctionhydrate($value;)

}

Likefilters,aStrategyEnabledInterfaceallowsahydratortoindicateitacceptsstrategies,andtheAbstractHydratorimplementsthisinterface,allowingyoutousestrategiesoutoftheboxwiththeshippedhydrators.

UsingourpreviousUserexample,wecould,insteadofomittingthepasswordvalue,insteadreturnastatic********value;astrategycouldallowustodothat.Datasubmittedwouldbeinsteadhashedusingpassword_hash():

Convertobjectstoarraysandbackwithzend-hydrator

28

Page 29: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceAcme;

useZend\Hydrator\Strategy\StrategyInterface;

classPasswordStrategyimplementsStrategyInterface

{

publicfunctionextract($value)

{

return'********';

}

publicfunctionhydrate($value)

{

returnpassword_hash($value);

}

}

Wewouldthenextractourdataasfollows:

useAcme\PasswordStrategy;

useZend\Hydrator\ObjectPropertyasObjectPropertyHydrator;

$hydrator=newObjectPropertyHydrator();

$hydrator->addStrategy('password',newPasswordStrategy());

$data=$hydrator->extract($user);

zend-hydratorshipswithanumberofreallyusefulstrategiesforcommondata:

BooleanStrategywillconvertbooleansintoothervalues(suchas0and1,orthestringstrueandfalse)andviceversa,accordingtoamapyouprovidetotheconstructor.ClosureStrategyallowsyoutoprovidecallbacksforeachofextractionandhydration,allowingyoutoforegotheneedtocreateacustomstrategyimplementation.DateTimeFormatterStrategywillconvertbetweenstringsandDateTimeinstances.ExplodeStrategyisawrapperaroundimplodeandexplode(),andexpectsadelimitertoitsconstructor.StrategyChainallowsyoutocomposemultiplestrategies;thereturnvalueofeachispassedasthevaluetothenext,providingafilterchain.

FilteringpropertynamesWecannowfilterpropertiestoomitfromourrepresentations,aswellasfilterornormalizethevaluesweultimatelywanttorepresent.Whataboutthepropertynames,though?

Convertobjectstoarraysandbackwithzend-hydrator

29

Page 30: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

InPHP,weoftenusecamelCasetorepresentproperties,butsnake_caseistypicallymoreacceptedforAPIs.Additionally,whataboutwhenweusegettersforourvalues?Welikelydon'twanttousetheactualmethodnameasthepropertyname!

Forthisreason,zend-hydratorprovidesnamingstrategies.Theseworkjustlikestrategies,butinsteadofworkingonthevalue,theyworkonthepropertyname.Likebothfiltersandstrategies,aninterface,NamingStrategyEnabledInterface,allowsahydratortoindicatecanacceptanamingstrategy,andtheAbstractHydratorimplementsthatinterface,toallowoutoftheboxusageofnamingstrategiesontheshippedhydrators.

Asanexample,let'sconsiderthefollowingclass:

namespaceAcme;

classTransaction

{

public$isPublished;

public$publishedOn;

public$updatedOn;

}

Let'snowextractaninstanceofthatclass:

useAcme\Transaction;

useZend\Hydrator\NamingStrategy\UnderscoreNamingStrategy;

useZend\Hydrator\ObjectPropertyasObjectPropertyHydrator;

$hydrator=newObjectPropertyHydrator();

$hydrator->setNamingStrategy(newUnderscoreNamingStrategy());

$data=$hydrator->extract($transaction);

Theextracteddatawillnowhavethekeysis_published,published_on,andupdated_on!

ThisisusefulifyouknowallyourpropertieswillbecamelCased,butwhatifyouhaveotherneeds?Forinstance,whatifyouwanttorenameisPublishedtopublishedinstead?

ACompositeNamingStrategyclassallowsyoutodoexactlythat.Itacceptsanassociativearrayofobjectpropertynamesmappedtothenamingstrategytousewithit.So,asanexample:

Convertobjectstoarraysandbackwithzend-hydrator

30

Page 31: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useAcme\Transaction;

useZend\Hydrator\NamingStrategy\CompositeNamingStrategy;

useZend\Hydrator\NamingStrategy\MapNamingStrategy;

useZend\Hydrator\NamingStrategy\UnderscoreNamingStrategy;

useZend\Hydrator\ObjectPropertyasObjectPropertyHydrator;

$underscoreNamingStrategy=newUnderscoreNamingStrategy();

$namingStrategy=newCompositeNamingStrategy([

'isPublished'=>newMapNamingStrategy(['published'=>'isPublished']),

'publishedOn'=>$underscoreNamingStrategy,

'updatedOn'=>$underscoreNamingStrategy,

]);

$hydrator=newObjectPropertyHydrator();

$hydrator->setNamingStrategy($namingStrategy);

$data=$hydrator->extract($transaction);

Ourdatawillnowhavethekeyspublished,published_on,andupdated_on!

Unfortunately,ifwetryandhydrateusingourCompositeNamingStrategy,we'llrunintoissues;theCompositeNamingStrategydoesnotknowhowtomapthenormalized,extractedpropertynamestothosetheobjectacceptsbecauseitmapsapropertynametothenamingstrategy.So,tofixthat,weneedtoaddthereversekeys:

$mapNamingStrategy=newMapNamingStrategy(['published'=>'isPublished']);

$underscoreNamingStrategy=newUnderscoreNamingStrategy();

$namingStrategy=newCompositeNamingStrategy([

//Extraction:

'isPublished'=>$mapNamingStrategy,

'publishedOn'=>$underscoreNamingStrategy,

'updatedOn'=>$underscoreNamingStrategy,

//Hydration:

'published'=>$mapNamingStrategy,

'published_on'=>$underscoreNamingStrategy,

'updated_on'=>$underscoreNamingStrategy,

]);

DelegationSometimeswewanttocomposeasinglehydrator,butdon'tknowuntilruntimewhatobjectswe'llbeextractingorhydrating.Agreatexampleofthisiswhenusingzend-db'sHydratingResultSet,wherethehydratormayvarybasedonthetablefromwhichwepull

Convertobjectstoarraysandbackwithzend-hydrator

31

Page 32: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

values.Othertimes,wemaywanttousethesamebasichydratortype,butcomposedifferentfilters,strategies,ornamingstrategiesbasedontheobjectwewishtohydrateorextract.

Toaccommodatethesescenarios,wehavetwofeatures.ThefirstisZend\Hydrator\HydratorPluginManager.ThisisaspecializedZend\ServiceManager\AbstractPluginManagerforretrievingdifferenthydratorinstances.Whenusedinzend-mvcorExpressiveapplications,itcanbeconfiguredviathehydratorsconfigurationkey,whichusesthesemanticsforzend-servicemanager,andmapstheservicetoHydratorManager.

Asanexample,wecouldhavethefollowingconfiguration:

return[

'hydrators'=>[

'factories'=>[

'Acme\BookHydrator'=>\Acme\BookHydratorFactory::class,

'Acme\AuthorHydrator'=>\Acme\AuthorHydratorFactory::class,

],

],

];

ManuallyconfiguringtheHydratorPluginManager

YoucanalsousetheHydratorPluginManagerprogrammatically:

$hydrators=newHydratorPluginManager();

$hydrators->setFactory('Acme\BookHydrator',\Acme\BookHydratorFactory::class);

$hydrators->setFactory('Acme\AuthorHydrator',\Acme\AuthorHydratorFactory::class)

;

Thefactoriesmightcreatestandardhydratorinstances,butconfigurethemdifferently:

Convertobjectstoarraysandbackwithzend-hydrator

32

Page 33: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceAcme;

usePsr\Container\ContainerInterface;

useZend\Hydrator\ObjectProperty;

useZend\Hydrator\NamingStrategy\CompositeNamingStrategy;

useZend\Hydrator\NamingStrategy\UnderscoreNamingStrategy;

useZend\Hydrator\Strategy\DateTimeFormatterStrategy;

classBookHydratorFactory

{

publicfunction__invoke(ContainerInterface$container)

{

$hydrator=newObjectProperty();

$hydrator->addFilter('isbn',function($property){

return$property!=='isbn';

});

$hydrator->setNamingStrategy(newCompositeNamingStrategy([

'publishedOn'=>newUnderscoreNamingStrategy(),

]));

$hydrator->setStrategy(newCompositeNamingStrategy([

'published_on'=>newDateTimeFormatterStrategy(),

]));

return$hydrator;

}

}

classAuthorHydratorFactory

{

publicfunction__invoke(ContainerInterface$container)

{

$hydrator=newObjectProperty();

$hydrator->setNamingStrategy(newUnderscoreNamingStrategy());

return$hydrator;

}

}

YoucouldthencomposetheHydratorManagerserviceinyourownclass,andpullthesehydratorsinordertoextractorhydrateinstances:

$bookData=$hydrators->get('Acme\BookHydrator')->extract($book);

$authorData=$hydrators->get('Acme\AuthorHydrator')->extract($author);

TheDelegatingHydratorworksbycomposingaHydratorPluginManagerinstance,buthasanadditionalsemantic:itusestheclassnameoftheobjectitisextracting,ortheobjecttypetohydrate,astheservicenametopullfromtheHydratorPluginManager.Assuch,wewouldchangeourconfigurationofthehydratorsasfollows:

Convertobjectstoarraysandbackwithzend-hydrator

33

Page 34: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

return[

'hydrators'=>[

'factories'=>[

\Acme\Book::class=>\Acme\BookHydratorFactory::class,

\Acme\Author::class=>\Acme\AuthorHydratorFactory::class,

],

],

];

Additionally,weneedtotellourapplicationabouttheDelegatingHydrator:

//zend-mvcapplications:

return[

'service_manager'=>[

'factories'=>[

\Zend\Hydrator\DelegatingHydrator::class=>\Zend\Hydrator\DelegatingHydra

torFactory::class

]

],

];

//Expressiveapplications

return[

'dependencies'=>[

'factories'=>[

\Zend\Hydrator\DelegatingHydrator::class=>\Zend\Hydrator\DelegatingHydra

torFactory::class

]

],

];

ManuallycreatingtheDelegatingHydrator

YoucaninstantiatetheDelegatingHydratormanually;whenyoudo,youpassitthe`HydratorPluginManagerinstance.

useZend\Hydrator\DelegatingHydrator;

useZend\Hydrator\HydratorPluginManager;

$hydrators=newHydratorPluginManager();

//...configurethepluginmanager...

$hydrator=newDelegatingHydrator($hydrators);

Technicallyspeaking,theDelegatingHydratorcanacceptanyPSR-11 containertoitsconstructor.

1

Convertobjectstoarraysandbackwithzend-hydrator

34

Page 35: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Fromthere,wecaninjecttheDelegatingHydratorintoanyofourownclasses,anduseittoextractorhydrateobjects:

$bookData=$hydrator->extract($book);

$authorData=$hydrator->extract($author);

Thisfeaturecanbequitepowerful,asitallowsyoutocreatethehydrationandextraction"recipes"forallofyourobjectswithintheirownfactories,ensuringthatanywhereyouneedthem,theyoperateexactlythesame.Italsomeansthatfortestingpurposes,youcansimplymocktheHydratorInterface(oritsparents,ExtractionInterfaceandHydrationInterface)insteadofcomposingaconcreteinstance.

OtherfeaturesWhilewe'vetriedtocoverthemajorityofthefunctionalityzend-hydratorprovidesinthisarticle,ithasanumberofotherusefulfeatures:

TheAggregateHydratorallowsyoutohandlecomplexobjectsthatimplementmultiplecommoninterfacesand/orhavenestedinstancescomposed;itevenexposeseventsyoucanlistentoduringeachofextractionandhydration.Youcanreadmoreaboutitinthedocumentation .YoucanwriteobjectsthatprovideandexposetheirownfiltersbyimplementingtheZend\Hydrator\Filter\FilterProviderInterface.YoucanhydrateorextractarraysofobjectsbyimplementingZend\Hydrator\Iterator\HydratingIteratorInterface.

Thecomponentcanbeseeninuseinanumberofplaces:zend-dbprovidesaHydratingResultSetthatleveragetheHydratorPluginManagerinordertohydrateobjectspulledfromadatabase.ApigilityusesthefeaturetoextractdataforHypertextApplicationLanguage(HAL)payloads.We'veevenseendeveloperscreatingcustomORMsfortheirapplicationusingthefeature!

Whatcanzend-hydratorhelpyoudotoday?

Footnotes

.https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md↩

.https://docs.zendframework.com/zend-hydrator/aggregate/↩

2

1

2

Convertobjectstoarraysandbackwithzend-hydrator

35

Page 36: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Convertobjectstoarraysandbackwithzend-hydrator

36

Page 37: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ScrapeScreenswithzend-dombyMatthewWeierO'Phinney

Eveninthisday-and-ageofreadilyavailableAPIsandRSS/Atomfeeds,manysitesoffernoneofthem.Howdoyougetatthedatainthosecases?Throughtheancientinternetartofscreenscraping.

Theproblemthenbecomes:howdoyougetatthedatayouneedinapileofHTMLsoup?YoucoulduseregularexpressionsoranyofthevariousstringfunctionsinPHP.Alloftheseareeasilysubjecttoerror,though,andoftenrequiresomeconvolutedcodetogetatthedataofinterest.

Alternately,youcouldtreattheHTMLasXML,andusetheDOMextension ,whichistypicallybuilt-intoPHP.Doingso,however,requiresmorethanapassingfamiliaritywithXPath ,whichissomethingofablackart.

IfyouuseJavaScriptlibrariesorwriteCSSfairlyoften,youmaybefamiliarwithCSSselectors,whichallowyoutotargeteitherspecificnodesorgroupsofnodeswithinanHTMLdocument.Thesearegenerallyratherintuitive:

jQuery('section.slideh2').each(function(node){

alert(node.textContent);

});

WhatifyoucoulddothatwithPHP?

Introducingzend-domzend-dom providesCSSselectorcapabilitiesforPHP,viatheZend\Dom\Queryclass,including:

elementtypes(h2,span,etc.)classattributes(.error,.next,etc.)elementidentifiers(#nav,#main,etc.)arbitraryelementattributes(div[onclick="foo"]),includingwordmatches(div[role~="navigation"])andsubstringmatches(div[role*="complement"])descendents(div.foospan)

WhileitdoesnotimplementthefullspectrumofCSSselectors,itdoesprovideenoughtogenerallyallowyoutogetattheinformationyouneedwithinapage.

1

2

3

ScrapeScreenswithzend-dom

37

Page 38: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Example:retrievinganavigationlistAsanexample,let'sfetchthenavigationlistfromtheZend\Dom\Querydocumentationpageitself:

useZend\Dom\Query;

$html=file_get_contents('https://docs.zendframework.com/zend-dom/query/');

$query=newQuery($html);

$results=$query->execute('ul.bs-sidenavlia');

printf("Received%dresults:\n",count($results));

foreach($resultsas$result){

printf("-[%s](%s)\n",$result->getAttribute('href'),$result->textContent);

}

Theabovequeriesforul.bs-sidenavlia—inotherwords,alllinkswithinlistitemsofthesidenavunorderedlist.

Whenyouexecute()aquery,youarereturnedaZend\Dom\NodeListinstance,whichdecoratesaDOMNodeList inordertoprovidefeaturessuchasCountable,andaccesstotheoriginalqueryanddocument.Intheexampleabove,wecount()theresults,andthenloopoverthem.

EachiteminthelistisaDOMNode ,givingyouaccesstoanyattributes,thetextcontent,andanychildelements.Inourcase,weaccessthehrefattribute(thelinktarget),andreportthetextcontent(thelinktext).

Theresultsare:

Received3results:

-[#querying-html-and-xml-documents](QueryingHTMLandXMLDocuments)

-[#theory-of-operation](TheoryofOperation)

-[#methods-available](MethodsAvailable)

OtherusesAnotherusecaseistesting.WhenyouhaveclassesthatreturnHTML,orifyouwanttoexecuterequestsandtestthegeneratedoutput,youoftendon'twanttotestexactcontents,butratherlookforspecificdataorfragmentswithinthedocument.

4

5

6 7

ScrapeScreenswithzend-dom

38

Page 39: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Weprovidethesecapabilitiesforzend-mvc applicationsviathezend-testcomponent ,whichprovidesanumberofCSSselectorassertions foruseinqueryingthecontentreturnedinyourMVCresponses.Havingthesecapabilitiesallowstestingfordynamiccontentaswellasstaticcontent,providinganumberofvectorsforensuringapplicationquality.

Startscraping!Wehopeyoucanappreciatethepowerfulcapabilitiesofthiscomponent!Wehaveusedthisfunctionalityinavarietyofways,fromtestingapplicationstocreatingfeedsbasedoncontentdifferencesinwebpages,tofindingandretrievingimageURIsfrompages.

Getmoreinformationfromthezend-domdocumentation .

Footnotes

.http://php.net/dom↩

.https://en.wikipedia.org/wiki/XPath↩

.https://docs.zendframework.com/zend-dom/↩

.http://php.net/class.domnodelist↩

.http://php.net/class.domnode↩

.https://docs.zendframework.com/zend-mvc/↩

.https://docs.zendframework.com/zend-test/↩

.https://docs.zendframework.com/zend-test/assertions/#css-selector-assertions↩

.https://docs.zendframework.com/zend-dom/↩

6 78

9

1

2

3

4

5

6

7

8

9

ScrapeScreenswithzend-dom

39

Page 40: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Paginatingdatacollectionswithzend-paginatorbyEnricoZimuel

zend-paginator isaflexiblecomponentforpaginatingcollectionsofdataandpresentingthatdatatousers.

Pagination isastandardUIsolutiontomanagethevisualizationoflistsofitems,likealistofpostsinablogoralistofproductsinanonlinestore.

zend-paginatorisverypopularamongZendFrameworkdevelopers,andit'softenusedwithzend-view ,thankstothepaginationcontrolviewhelperzend-viewprovides.

Itcanbeusedalsowithothertemplateengines.Inthisarticle,IwilldemonstratehowtouseitwithPlates .

Usageofzend-paginatorThecomponentcanbeinstalledviaComposer:

$composerrequirezendframework/zend-paginator

Toconsumethepaginatorcomponent,weneedacollectionofitems.zend-paginatorshipswithseveraldifferentadaptersforcommoncollectiontypes:

ArrayAdapter,whichworkswithPHParrays;Callback,whichallowsprovidingcallbacksforobtainingcountsofitemsandlistsofitems;DbSelect,toworkwithaSQLcollection(usingzend-db );DbTableGateway,toworkwithaTableDataGateway(usingtheTableGatewayfeaturefromzend-db.Iterator,toworkwithanyIterator instance.

Ifyourcollectiondoesnotfitoneoftheseadapters,youcancreateacustomadapter.Todoso,youwillneedtoimplementZend\Paginator\Adapter\AdapterInterface,whichdefinestwomethods:

count():int

getItems(int$offset,int$itemCountPerPage):array

1

2

3

4

5

6

Paginatingdatacollectionswithzend-paginator

40

Page 41: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Eachadapterneedstoreturnthetotalnumberofitemsinthecollection,implementingthecount()method,andaportion(apage)ofitemsstartingfrom$offsetpositionwithasizeof$itemCountPerPageperpage.

Withthesetwomethods,wecanusezend-paginatorwithanytypeofcollection.

Forinstance,imagineweneedtopaginateacollectionofblogpostsandwehaveaPostsclassthatmanagesalltheposts.Wecanimplementanadapterlikethis:

require'vendor/autoload.php';

useZend\Paginator\Adapter\AdapterInterface;

useZend\Paginator\Paginator;

useZend\Paginator\ScrollingStyle\Sliding;

classPostsimplementsAdapterInterface

{

private$posts=[];

publicfunction__construct()

{

//Readpostsfromfile/database/whatever

}

publicfunctioncount()

{

returncount($this->posts);

}

publicfunctiongetItems($offset,$itemCountPerPage)

{

returnarray_slice($this->posts,$offset,$itemCountPerPage);

}

}

$posts=newPosts();

$paginator=newPaginator($posts);

Paginator::setDefaultScrollingStyle(newSliding());

$paginator->setCurrentPageNumber(1);

$paginator->setDefaultItemCountPerPage(8);

foreach($paginatoras$post){

//Iterateoneachpost

}

$pages=$paginator->getPages();

var_dump($pages);

Paginatingdatacollectionswithzend-paginator

41

Page 42: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Inthisexample,wecreatedazend-paginatoradapterusingacustomPostsclass.Thisclassstoresthecollectionofpostsusingaprivatearray($posts).ThisadapteristhenpassedtoaninstanceofPaginator.

WhencreatingaPaginator,weneedtoconfigureitsbehavior.Thefirstsettingisthescrollingstyle.Intheexampleabove,weusedtheSliding style,aYahoo!-likescrollingstylethatpositionsthecurrentpagenumberascloseaspossibletothecenterofthepagerange.

Note:theSlidingscrollingstyleisthedefaultstyleusedbyzend-paginator.WeneedtosetitexplicitlyusingPaginator::setDefaultScrollingStyle()onlyifwedonotusezend-servicemanager asapluginmanager.Otherwise,thescrollingstyleisloadedbydefaultfromthepluginmanager.

Theothertwoconfigurationvaluesarethecurrentpagenumberandthenumberofitemsperpage.Intheexampleabove,westartedfrompage1,andwecount8itemsperpage.

Wecantheniterateonthe$paginatorobjecttoretrievethepostofthecurrentpageinthecollection.

Attheend,wecanretrievetheinformationregardingthepreviouspage,thenextpage,thetotalitemsinthecollection,andmore.TogetthesevaluesweneedtocallthegetPages()method.Wewillobtainanobjectlikethis:

7

8

Paginatingdatacollectionswithzend-paginator

42

Page 43: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

object(stdClass)#81(13){

["pageCount"]=>

int(3)

["itemCountPerPage"]=>

int(8)

["first"]=>

int(1)

["current"]=>

int(1)

["last"]=>

int(3)

["next"]=>

int(2)

["pagesInRange"]=>

array(3){

[1]=>

int(1)

[2]=>

int(2)

[3]=>

int(3)

}

["firstPageInRange"]=>

int(1)

["lastPageInRange"]=>

int(3)

["currentItemCount"]=>

int(8)

["totalItemCount"]=>

int(19)

["firstItemNumber"]=>

int(1)

["lastItemNumber"]=>

int(8)

}

Usingthisinformation,wecaneasilybuildanHTMLfootertonavigateacrossthecollection.

Note:usingzend-view,wecanconsumethepaginationControl() helper,whichemitsanHTMLpaginationbar.

AnexampleusingPlatesPlates implementstemplatesusingnativePHP;itisfastandeasytouse,withoutanyadditionalmetalanguage;itisjustPHP.

Inourexample,wewillcreateaPlatestemplatetopaginateacollectionofdatausingzend-paginator.WewilluseBootstrap astheUIframework.

9

10

11

Paginatingdatacollectionswithzend-paginator

43

Page 44: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Forpurposesofthisexample,blogpostswillbeaccessibleviathefollowingURL:

/blog[/page/{page:\d+}]

where[/page/{page:\d+}]representstheoptionalpagenumber(usingtheregexp\d+tovalidateonlydigits).Ifweopenthe/blogURLwewillgetthefirstpageofthecollection.Toreturnthesecondpageweneedtoconnectto/blog/page/2,thirdpageto/blog/page/3,andsoon.

Forinstance,wecanmanagethepageparameterusingaPSR-7middlewareclassconsumingthepreviousPostsadapter,thatworksasfollow:

Paginatingdatacollectionswithzend-paginator

44

Page 45: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

usePsr\Http\Message\ResponseInterface;

usePsr\Http\Message\ServerRequestInterface;

useLeague\Plates\Engine;

useZend\Paginator\Paginator;

useZend\Paginator\ScrollingStyle\Sliding;

usePosts;

classPaginatorMiddleware

{

/**@varPosts*/

protected$posts;

/**@varEngine*/

protected$template;

publicfunction__construct(Posts$post,Engine$template=null)

{

$this->posts=$post;

$this->template=$template;

}

publicfunction__invoke(

ServerRequestInterface$request,

ResponseInterface$response,callable$next=null

){

$paginator=newPaginator($this->posts);

$page=$request->getAttribute('page',1);

Paginator::setDefaultScrollingStyle(newSliding());

$paginator->setCurrentPageNumber($page);

$paginator->setDefaultItemCountPerPage(8);

$pages=$paginator->getPages();

$response->getBody()->write(

$this->template->render('posts',[

'paginator'=>$paginator,

'pages'=>$pages,

])

);

return$response;

}

}

Weusedaposts.phptemplate,passingthepaginator($paginator)andthepages($pages)instances.Thattemplatecouldthenlooklikethefollowing:

Paginatingdatacollectionswithzend-paginator

45

Page 46: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

<?php$this->layout('template',['title'=>'BlogPosts'])?>

<divclass="container">

<h1>BlogPosts</h1>

<?phpforeach($paginatoras$post):?>

<divclass="row">

<?php//printstheposttitle,date,author,...?>

</div>

<?phpendforeach?>

<?php$this->insert('page-navigation',['pages'=>$pages])?>

</div>

Thepage-navigation.phptemplatecontainstheHTMLcodeforthepagenavigationcontrol,withbuttonlikeprevious,next,andpagenumbers.

<navaria-label="Pagenavigation">

<ulclass="pagination">

<?phpif(!isset($pages->previous)):?>

<liclass="disabled"><ahref="#"aria-label="Previous"><spanaria-hidden="true">

&laquo;</span></a></li>

<?phpelse:?>

<li><ahref="/blog/page/<?=$pages->previous?>"aria-label="Previous"><spanari

a-hidden="true">&laquo;</span></a></li>

<?phpendif?>

<?phpforeach($pages->pagesInRangeas$num):?>

<?phpif($num===$pages->current):?>

<liclass="active"><ahref="/blog/page/<?=$num?>"><?=$num?><spanclass="s

r-only">(current)</span></a></li>

<?phpelse:?>

<li><ahref="/blog/page/<?=$num?>"><?=$num?></a></li>

<?phpendif?>

<?phpendforeach?>

<?phpif(!isset($pages->next)):?>

<liclass="disabled"><ahref="#"aria-label="Next"><spanaria-hidden="true">&raq

uo;</span></a></li>

<?phpelse:?>

<li><ahref="/blog/page/<?=$pages->next?>"aria-label="Next"><spanaria-hidden

="true">&raquo;</span></a></li>

<?phpendif?>

</ul>

</nav>

Summary

Paginatingdatacollectionswithzend-paginator

46

Page 47: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Thezend-paginatorcomponentofZendFrameworkisapowerfulandeasytousepackagethatprovidespaginationofdata.ItcanbeusedasstandalonecomponentinmanyPHPprojectsusingdifferentframeworksandtemplateengines.Inthisarticle,Idemonstratedhowtouseitingeneralpurposeapplications.Moreover,IshowedanexampleusingPlatesandBootstrap,inaPSR-7middlewarescenario.

Visitthezend-paginatordocumentation tofindoutwhatelseyoumightbeabletodowiththiscomponent!

Footnotes

.https://docs.zendframework.com/zend-paginator/↩

.https://en.wikipedia.org/wiki/Pagination↩

.https://docs.zendframework.co/zend-view/↩

.http://platesphp.com/↩

.https://docs.zendframework.com/zend-db/↩

.http://php.net/iterator↩

.https://github.com/zendframework/zend-paginator/blob/master/src/ScrollingStyle/Sliding.php↩

.https://docs.zendframework.com/zend-servicemanager/↩

.https://docs.zendframework.com/zend-paginator/usage/#rendering-pages-with-view-scripts↩

.http://platesphp.com/↩

.http://getbootstrap.com/↩

.https://docs.zendframework.com/zend-paginator/↩

12

1

2

3

4

5

6

7

8

9

10

11

12

Paginatingdatacollectionswithzend-paginator

47

Page 48: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

LoggingPHPapplicationsbyEnricoZimuel

EveryPHPapplicationgenerateserrors,warnings,andnoticesandthrowsexceptions.Ifwedonotlogthisinformation,weloseawaytoidentifyandsolveproblemsatruntime.Moreover,wemayneedtologspecificactionssuchasauserloginandlogoutattempts.Allsuchinformationshouldbefilteredandstoredinanefficientway.

PHPoffersthefunctionerror_log() tosendanerrormessagetothedefinedsystemlogger,andthefunctionset_error_handler() tospecifyahandlerforinterceptingwarnings,errors,andnoticesgeneratedbyPHP.

Thesefunctionscanbeusedtocustomizeerrormanagement,butit'suptothedevelopertowritethelogictofilterandstorethedata.

ZendFrameworkoffersaloggingcomponent,zend-log ;thelibrarycanbeusedasageneralpurposeloggingsystem.Itsupportsmultiplelogbackends,formattingmessagessenttothelog,andfilteringmessagesfrombeinglogged.

Lastbutnotleast,zend-logiscompliantwithPSR-3 ,theloggerinterfacestandard.

InstallationYoucaninstallzend-log usingComposer:

composerrequirezendframework/zend-log

Usagezend-logcanbeusedtocreatelogentriesindifferentformatsusingmultiplebackends.Youcanalsofilterthelogdatafrombeingsaved,andprocessthelogeventpriortofilteringorwriting,allowingtheabilitytosubstitute,add,remove,ormodifythedatayoulog.

Basicusageofzend-logrequiresbothawriterandaloggerinstance.Awriterstoresthelogentryintoabackend,andtheloggerconsumesthewritertoperformloggingoperations.

Asanexample:

12

3

4

5

LoggingPHPapplications

48

Page 49: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Log\Logger;

useZend\Log\Writer\Stream;

$logger=newLogger;

$writer=newStream('php://output');

$logger->addWriter($writer);

$logger->log(Logger::INFO,'Informationalmessage');

Theaboveproducesthefollowingoutput:

2017-09-11T15:07:46+02:00INFO(6):Informationalmessage

Theoutputisastringcontainingatimestamp,apriority(INFO(6))andthemessage(Informationalmessage).TheoutputformatcanbechangedusingthesetFormatter()methodofthewriterobject($writer).Thedefaultlogformat,producedbytheSimpleformatter,isasfollows:

%timestamp%%priorityName%(%priority%):%message%%extra%

where%extra%isanoptionalvaluecontainingadditionalinformation.

Forinstance,ifyouwantedtochangetheformattoincludeonlylog%message%,youcoulddothefollowing:

$formatter=newZend\Log\Formatter\Simple('log%message%'.PHP_EOL);

$writer->setFormatter($formatter);

LogPHPeventszend-logcanalsobeusedtologPHPerrorsandexceptions.YoucanlogPHPerrorsusingthestaticmethodLogger::registerErrorHandler($logger)andinterceptexceptionsusingthestaticmethodLogger::registerExceptionHandler($logger).

6

LoggingPHPapplications

49

Page 50: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Log\Logger;

useZend\Log\Writer;

$logger=newLogger;

$writer=newWriter\Stream(__DIR__.'/test.log');

$logger->addWriter($writer);

//LogPHPerrors

Logger::registerErrorHandler($logger);

//Logexceptions

Logger::registerExceptionHandler($logger);

FilteringdataAsmentioned,wecanfilterthedatatobelogged;filteringremovesmessagesthatmatchthefiltercriteria,preventingthemfrombeinglogged.

WecanusetheaddFilter()methodoftheWriterinterface toaddaspecificfilter.

Forinstance,wecanfilterbypriority,acceptingonlylogentrieswithaprioritylessthanorequaltoaspecificvalue:

$filter=newZend\Log\Filter\Priority(Logger::CRIT);

$writer->addFilter($filter);

Intheaboveexample,theloggerwillonlystorelogentrieswithaprioritylessthanorequaltoLogger::CRIT(critical).TheprioritiesaredefinedbytheZend\Log\Loggerclass:

constEMERG=0;//Emergency:systemisunusable

constALERT=1;//Alert:actionmustbetakenimmediately

constCRIT=2;//Critical:criticalconditions

constERR=3;//Error:errorconditions

constWARN=4;//Warning:warningconditions

constNOTICE=5;//Notice:normalbutsignificantcondition

constINFO=6;//Informational:informationalmessages

constDEBUG=7;//Debug:debugmessages

Assuch,onlyemergency,alerts,orcriticalentrieswouldbelogged.

Wecanalsofilterlogdatabasedonregularexpressions,timestamps,andmore.Onepowerfulfilterusesazend-validator ValidatorInterfaceinstancetofilterthelog;onlyvalidentrieswouldbeloggedinsuchcases.

7

8

LoggingPHPapplications

50

Page 51: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ProcessingdataIfyouneedtoprovideadditionalinformationtologsinanautomatedfashion,youcanuseaZend\Log\Processerclass.Aprocessorisexecutedbeforethelogdataarepassedtothewriter.Theinputofaprocessorisalogevent,anarraycontainingalloftheinformationtolog;theoutputisalsoalogevent,butcancontainmodifiedoradditionalvalues.Aprocessormodifiesthelogeventtopriortosendingittothewriter.

Youcanreadaboutprocessoradaptersofferedbyzend-loginthedocumentation .

MultiplebackendsOneofthecoolfeatureofzend-logisthepossibilitytowritelogsusingmultiplebackends.Forinstance,youcanwritealogtobothafileandadatabaseusingthefollowingcode:

9

LoggingPHPapplications

51

Page 52: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Db\Adapter\AdapterasDbAdapter;

useZend\Log\Formatter;

useZend\Log\Writer;

useZend\Log\Logger;

//Createouradapter

$db=newDbAdapter([

'driver'=>'Pdo',

'dsn'=>'mysql:dbname=testlog;host=localhost',

'username'=>'root',

'password'=>'password'

]);

//Mapeventdatatodatabasecolumns

$mapping=[

'timestamp'=>'date',

'priority'=>'type',

'message'=>'event',

];

//Createourdatabaselogwriter

$writerDb=newWriter\Db($db,'log',$mapping);//logtable

$formatter=newFormatter\Base();

$formatter->setDateTimeFormat('Y-m-dH:i:s');//MySQLDATETIMEformat

$writerDb->setFormatter($formatter);

//Createourfilelogwriter

$writerFile=newWriter\Stream(__DIR__.'/test.log');

//Createourloggerandregisterbothwriters

$logger=newLogger();

$logger->addWriter($writerDb,1);

$logger->addWriter($writerFile,100);

//Loganinformationmessage

$logger->info('Informationalmessage');

Thedatabasewriterrequiresthecredentialstoaccessthetablewhereyouwillstoreloginformation.Youcancustomizethefieldnamesforthedatabasetableusinga$mappingarray,containinganassociativearraymappinglogfieldstodatabasecolumns.

Thedatabasewriteriscomposedin$writerDbandthefilewriterin$writerFile.ThewritersareaddedtotheloggerusingtheaddWriter()methodwithaprioritynumber;higherintegervaluesindicatehigherpriority(triggeredearliest).Wechosepriority1forthedatabasewriter,andpriority100forthefilewriter;thismeansthefilewriterwilllogfirst,followedbyloggingtothedatabase.

Note:weusedaspecialdateformatterforthedatabasewriter.ThisisrequiredtotranslatethelogtimestampintotheDATETIMEformatofMySQL.

LoggingPHPapplications

52

Page 53: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

PSR-3supportIfyouneedtobecompatiblewithPSR-3 ,youcanuseZend\Log\PsrLoggerAdapter.ThisloggercanbeusedanywhereaPsr\Log\LoggerInterfaceisexpected.

Asanexample:

usePsr\Log\LogLevel;

useZend\Log\Logger;

useZend\Log\PsrLoggerAdapter;

$zendLogLogger=newLogger;

$psrLogger=newPsrLoggerAdapter($zendLogLogger);

$psrLogger->log(LogLevel::INFO,'WehaveaPSR-compatiblelogger');

ToselectaPSR-3backendforwriting,wecanusetheZend\Log\Writer\Psrclass.Inordertouseit,youneedtopassaPsr\Log\LoggerInterfaceinstancetothe$psrLoggerconstructorargument:

$writer=newZend\Log\Writer\Psr($psrLogger);

zend-logalsosupportsPSR-3messageplaceholders viatheZend\Log\Processor\PsrPlaceholderclass.Touseit,youneedtoaddaPsrPlaceholderinstancetoalogger,usingtheaddProcess()method.Placeholdernamescorrespondtokeysinthe"extra"arraypassedwhenloggingamessage:

useZend\Log\Logger;

useZend\Log\Processor\PsrPlaceholder;

$logger=newLogger;

$logger->addProcessor(newPsrPlaceholder);

$logger->info('Userwithemail{email}registered',['email'=>'[email protected]']);

AninformationallogentrywillbestoredwiththemessageUserwithemailuser@example.orgregistered.

LogginganMVCapplicationIfyouareusingazend-mvc basedapplication,youcanusezend-logasmodule.zend-logprovidesaModule.php class,whichregistersZend\Logasamoduleinyourapplication.

10

11

1213

LoggingPHPapplications

53

Page 54: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Inparticular,thezend-logmoduleprovidesthefollowingservices(underthenamespaceZend\Log):

Logger::class=>LoggerServiceFactory::class,

'LogFilterManager'=>FilterPluginManagerFactory::class,

'LogFormatterManager'=>FormatterPluginManagerFactory::class,

'LogProcessorManager'=>ProcessorPluginManagerFactory::class,

'LogWriterManager'=>WriterPluginManagerFactory::class,

TheLogger::classservicecanbeconfiguredusingthelogconfigkey;thedocumentationprovidesconfigurationexamples .

InordertousetheLoggerserviceinyourMVCstack,grabitfromtheservicecontainer.Forinstance,youcanpasstheLoggerserviceinacontrollerusingafactory:

useZend\Log\Logger;

useZend\ServiceManager\Factory\FactoryInterface;

classIndexControllerFactoryimplementsFactoryInterface

{

publicfunction__invoke(

ContainerInterface$container,

$requestedName,

array$options=null

){

returnnewIndexController(

$container->get(Logger::class)

);

}

}

viathefollowingserviceconfigurationfortheIndexController:

'controllers'=>[

'factories'=>[

IndexController::class=>IndexControllerFactory::class,

],

],

LoggingamiddlewareapplicationYoucanalsointegratezend-loginyourmiddlewareapplications.IfyouareusingExpressive ,addthecomponent'sConfigProvider toyourconfig/config.phpfile.

14

15 16

17

LoggingPHPapplications

54

Page 55: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Note:ifyouareusingzend-component-installer ,youwillbepromptedtoinstallzend-log'sconfigproviderwhenyouinstallthecomponentviaComposer.

Note:Thisconfigurationregistersthesameservicesprovidedinthezend-mvcexample,above.

Tousezend-loginmiddleware,grabitfromthedependencyinjectioncontainerandpassitasadependencytoyourmiddleware:

namespaceApp\Action;

usePsr\Container\ContainerInterface;

useZend\Log\Logger;

classHomeActionFactory

{

publicfunction__invoke(ContainerInterface$container):HomeAction

{

returnnewHomeAction(

$container->get(Logger::class)

);

}

}

Asanexampleoflogginginmiddleware:

17

LoggingPHPapplications

55

Page 56: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceApp\Action;

useInterop\Http\ServerMiddleware\DelegateInterface;

useInterop\Http\ServerMiddleware\MiddlewareInterfaceasServerMiddlewareInterface;

usePsr\Http\Message\ServerRequestInterface;

useZend\Log\Logger;

classHomeActionimplementsServerMiddlewareInterface

{

private$logger;

publicfunction__construct(Logger$logger)

{

$this->logger=logger;

}

publicfunctionprocess(

ServerRequestInterface$request,

DelegateInterface$delegate

){

$this->logger->info(__CLASS__.'hasbeenexecuted');

//...

}

}

ListeningforerrorsinExpressiveExpressiveandStratigility provideadefaulterrorhandlermiddlewareimplementation,Zend\Stratigility\Middleware\ErrorHandlerwhichlistensforPHPerrorsandexceptions/throwables.Bydefault,itspitsoutasimpleerrorpagewhenanerroroccurs,butitalsoprovidestheabilitytoattachlisteners,whichcanthenactontheprovidederror.

Listenersreceivetheerror,therequest,andtheresponsethattheerrorhandlerwillbereturning.Wecanusethatinformationtologinformation!

First,wecreateanerrorhandlerlistenerthatcomposesalogger,andlogstheinformation:

18

LoggingPHPapplications

56

Page 57: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useException;

usePsr\Http\Message\ResponseInterface;

usePsr\Http\Message\ServerRequestInterface;

useThrowable;

useZend\Log\Logger;

classLoggingErrorListener

{

/**

*Logmessagestringwithplaceholders

*/

constLOG_STRING='{status}[{method}]{uri}:{error}';

private$logger;

publicfunction__construct(Logger$logger)

{

$this->logger=$logger;

}

publicfunction__invoke(

$error,

ServerRequestInterface$request,

ResponseInterface$response

){

$this->logger->error(self::LOG_STRING,[

'status'=>$response->getStatusCode(),

'method'=>$request->getMethod(),

'uri'=>(string)$request->getUri(),

'error'=>$error->getMessage(),

]);

}

}

TheErrorHandlerimplementationcastsPHPerrorstoErrorExceptioninstances,whichmeansthat$errorisalwayssomeformofthrowable.

WecanthenwriteadelegatorfactorythatwillregisterthisasalistenerontheErrorHandler:

LoggingPHPapplications

57

Page 58: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useLoggingErrorListener;

usePsr\Container\ContainerInterface;

useZend\Log\Logger;

useZend\Log\Processor\PsrPlaceholder;

useZend\Stratigility\Middleware\ErrorHandler;

classLoggingErrorListenerFactory

{

publicfunction__invoke(

ContainerInterface$container,

$serviceName,

callable$callback

):ErrorHandler{

$logger=$container->get(Logger::class);

$logger->addProcessor(newPsrPlaceholder());

$listener=newLoggingErrorListener($logger);

$errorHandler=$callback();

$errorHandler->attachListener($listener);

return$errorHandler;

}

}

Andthenregisterthedelegatorinyourconfiguration:

//InaConfigProvider,oraconfig/autoload/*.global.phpfile:

useLoggingErrorListenerFactory;

useZend\Stratigility\Middleware\ErrorHandler;

return[

'dependencies'=>[

'delegators'=>[

ErrorHandler::class=>[

LoggingErrorListenerFactory::class,

],

],

],

];

Atthispoint,yourerrorhandlerwillnowalsologerrorstoyourconfiguredwriters!

SummaryThezend-logcomponentoffersawidesetoffeatures,includingsupportformultiplewriters,filteringoflogdata,compatibilitywithPSR-3 ,andmore.19

LoggingPHPapplications

58

Page 59: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Hopefullyyoucanusetheexamplesaboveforconsumingzend-loginyourstandalone,zend-mvc,Expressive,orgeneralmiddlewareapplications!

Learnmoreinthezend-logdocumentation .

Footnotes

.http://php.net/error_log↩

.http://php.net/set_error_handler↩

.https://docs.zendframework.com/zend-log/↩

.http://www.php-fig.org/psr/psr-3/↩

.https://docs.zendframework.com/zend-log/↩

.https://github.com/zendframework/zend-log/blob/master/src/Formatter/Simple.php↩

.https://github.com/zendframework/zend-log/blob/master/src/Writer/WriterInterface.php↩

.https://docs.zendframework.com/zend-validator/↩

.https://docs.zendframework.com/zend-log/processors/↩

.http://www.php-fig.org/psr/psr-3/↩

.http://www.php-fig.org/psr/psr-3/#12-message↩

.https://docs.zendframework.com/zend-mvc/↩

.https://github.com/zendframework/zend-log/blob/master/src/Module.php↩

.https://docs.zendframework.com/zend-log/service-manager/#zend-log-as-a-module↩

.https://docs.zendframework.com/zend-expressive/↩

.https://github.com/zendframework/zend-log/blob/master/src/ConfigProvider.php↩

.https://docs.zendframework.com/zend-component-installer/↩

.https://docs.zendframework.com/zend-stratigility/↩

.http://www.php-fig.org/psr/psr-3/↩

.https://docs.zendframework.com/zend-log/↩

20

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

LoggingPHPapplications

59

Page 60: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

LoggingPHPapplications

60

Page 61: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

DiscoverandReadRSSandAtomFeedsbyMatthewWeierO'Phinney

RememberRSSandAtomfeeds?

Chancesare,youmayhavediscoveredthisbookbecauseitwasannouncedonafeed:

AnumberofTwitterservicespollfeedsandsendlinkswhennewentriesarediscovered.SomeofyoumaybeusingfeedreaderssuchasFeedly .Manynewsaggregatorservices,includingtoolssuchasGoogleNow,useRSSandAtomfeedsassources.

Aninterestingfact:AtomitselfisoftenusedasadatatransferformatforRESTservices,particularlycontentmanagementplatforms!Assuch,beingfamiliarwithfeedsandhavingtoolstoworkwiththemisanimportantskillforawebdeveloper!

Inthisfirstofatwopartseriesonfeeds,we'lllookatfeeddiscovery,aswellasreading,usingzend-feed'sReadersubcomponent.

GettingstartedFirst,ofcourse,youneedtoinstallzend-feed:

$composerrequirezendframework/zend-feed

Asofversion2.6.0,thecomponenthasaveryminimalsetofdependencies:itonlyrequireszendframework/zend-escaperandzendframework/zend-stdlibinordertowork.Ithasanumberofadditional,optionalrequirementsdependingonfeaturesyouwanttoopt-into:

psr/http-messageand/orzend-http,toallowpollingpagesforfeeds,feedsthemselves,orPubSubHubbubservices.zendframework/zend-cache,toallowcachingfeedsbetweenrequests.zendframework/zend-db,whichisusedwhenusingthePubSubHubbubsubcomponent,inorderforPuSHsubscriberstostoreupdates.zendframework/zend-validator,forvalidatingaddressesusedinAtomfeedsandentrieswhenusingtheWritersubcomponent.

Forourexamples,wewillneedanHTTPclientinordertofetchpages.Forthesakeofsimplicity,we'llgoaheadandusezendframework/zend-http;ifyouarealreadyusingGuzzleinyourapplication,youcancreateawrapperforitfollowinginstructionsinthezend-feed

1

2

DiscoverandReadRSSandAtomFeeds

61

Page 62: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

manual .

$composerrequirezendframework/zend-http

Nowthatwehavethesepiecesinplace,wecanmoveontolinkdiscovery!

LinkdiscoveryTheReadersubcomponentcontainsfacilitiesforfindingAtomandRSSlinkswithinanHTMLpage.Let'strythisnow:

//Indiscovery.php:

useZend\Feed\Reader\Reader;

require'vendor/autoload.php';

$feedUrls=[];

$feedLinks=Reader::findFeedLinks('https://framework.zend.com');

foreach($feedLinksas$link){

switch($link['type']){

case'application/atom+xml':

$feedUrls[]=$link['href'];

break;

case'application/rss+xml':

$feedUrls[]=$link['href'];

break;

}

}

var_export($feedUrls);

Ifyouruntheabove,youshouldgetalistlikethefollowing(atthetimeofwriting):

array(

0=>'https://framework.zend.com/security/feed',

1=>'https://framework.zend.com/blog/feed-atom.xml',

2=>'https://framework.zend.com/blog/feed-rss.xml',

3=>'https://framework.zend.com/releases/atom.xml',

4=>'https://framework.zend.com/releases/rss.xml',

)

That'sratheruseful!Wecanpollapagetodiscoverlinks,andthenfollowthem!

2

DiscoverandReadRSSandAtomFeeds

62

Page 63: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Internally,thereturned$feedLinksisaZend\Feed\Reader\FeedSetinstance,whichisreallyjustanArrayObjectwhereeachitemitcomposesisitselfaFeedSetwithspecificattributesset(includingthetype,href,andrel,usually).Itonlyreturnslinksthatareknownfeedtypes;anyothertypeoflinkisignored.

ReadingafeedNowthatweknowwheresomefeedsare,wecanreadthem.

Todothat,wepassaURLforafeedtothereader,andthenpulldatafromthereturnedfeed:

//Inreader.php:

useZend\Feed\Reader\Reader;

require'vendor/autoload.php';

$feed=Reader::import('https://framework.zend.com/releases/rss.xml');

printf(

"[%s](%s):%s\n",

$feed->getTitle(),

$feed->getLink(),

$feed->getDescription()

);

Theabovewillresultin:

[ZendFrameworkReleases](https://github.com/zendframework):ZendFrameworkandzfcamp

usreleases

Theaboveisconsideredthefeedchanneldata;it'sinformationaboutthefeeditself.Mostlikely,though,wewanttoknowwhatentriesareinthefeed!

GettingfeedentriesThefeedreturnedbyReader::import()isitselfiterable,whicheachitemofiterationbeinganentry.Atitsmostbasic:

DiscoverandReadRSSandAtomFeeds

63

Page 64: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

foreach($feedas$entry){

printf(

"[%s](%s):%s\n",

$entry->getTitle(),

$entry->getLink(),

$entry->getDescription()

);

}

Thiswillloopthrougheachentry,listingthetitle,thecanonicallinktotheitem,andadescriptionoftheentry.

Theabovewillworkacrossanytypeoffeed.However,feedcapabilitiesvarybasedontype.RSSandAtomfeedentrieswillhavedifferentdataavailable;infact,Atomisconsideredanextensibleprotocol,whichmeansthatsuchentriescanpotentiallyexposequitealotofadditionaldata!

Youmaywanttoreaduponwhat'savailable;followthefootnotestofindrelevantlinks:

RSSentrypropertiesAtomentries

Untilnexttimezend-feed'sReadersubcomponentoffersanumberofothercapabilities,including:

Importingactualfeedstrings(versusfetchingviaanHTTPclient)TheabilitytoutilizealternateHTTPclients.TheabilitytoextendtheAtomprotocolinordertoaccessadditionaldata.

Thezend-feedcomponenthasextensivedocumentation ,whichwillanswermostquestionsyoumayhaveatthispoint.

Wehopethisquickprimergetsyoustartedconsumingfeeds!

Footnotes

.https://feedly.com↩

.https://docs.zendframework.com/zend-feed/psr7-clients/↩

.https://docs.zendframework.com/zend-feed/consuming-rss/#get-properties↩

.https://docs.zendframework.com/zend-feed/consuming-atom/↩

34

5

1

2

3

4

5

DiscoverandReadRSSandAtomFeeds

64

Page 65: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

.https://docs.zendframework.com/zend-feed/↩5

DiscoverandReadRSSandAtomFeeds

65

Page 66: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

CreateRSSandAtomFeedsbyMatthewWeierO'Phinney

Inthepreviousarticle,wedetailedRSSandAtomfeeddiscoveryandparsing.Inthisarticlewe'regoingtocoveritscomplement:feedcreation!

zend-feedprovidestheabilitytocreatebothAtom1.0andRSS2.0feeds,andevensupportscustomextensionsduringfeedgeneration,including:

Atom(xmlns:atom;RSS2only):providelinkstoAtomfeedsandPubsubhubbubURIswithinyourRSSfeed.Content(xmlns:content;RSS2only):provideCDATAencodedcontentforindividualfeeditems.DublinCore(xmlns:dc;RSS2only):providemetadataaroundcommoncontentelementssuchasauthor/publisher/contributor/creator,dates,languages,etc.iTunes(xmlns:itunes):createpodcastfeedsanditemscompatiblewithiTunes.Slash(xmlns:slash;RSS2only):communicatecommentcountsperitem.Threading(xmlns:thr;RSS2only):providemetadataaroundthreadingfeeditems,includingindicatingwhatanitemisinreplyto,linkingtoreplies,andmetricsaroundeach.WellFormedWeb(xmlns:wfw;RSS2only):providealinktoaseparatecommentsfeedforagivenentry.

Youcanalsoprovideyourowncustomextensionsifdesired;thesearejustwhatweshipoutofthebox!Inmanycases,youdon'tevenneedtoknowabouttheextensions,aszend-feedwilltakecareofaddinginthosethatarerequired,basedonthedatayouprovideinthefeedandentries.

CreatingafeedThefirststep,ofcourse,ishavingsomecontent!I'llassumeyouhaveitemsyouwanttopublish,andthosewillbein$data,whichwe'llloopover.Howthatdatalookswillbedependentonyourapplication,sopleasebeawarethatyoumayneedtoadjustanyexamplesbelowtofityourowndatasource.

Next,weneedtohavezend-feedinstalled;dothatviaComposer:

$composerrequirezendframework/zend-feed

CreateRSSandAtomFeeds

66

Page 67: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Nowwecanfinallygetstarted.We'llbeginbycreatingafeed,andpopulatingitwithsomebasicmetadata:

useZend\Feed\Writer\Feed;

$feed=newFeed();

//Titleofthefeed

$feed->setTitle('TutorialFeed');

//Linktothefeed'starget,usuallyahomepage:

$feed->setLink('https://example.com/');

//Linktothefeeditself,andthefeedtype:

$feed->setFeedLink('https://example.com/feed.xml','rss');

//Feeddescription;onlyrequiredforRSS:

$feed->setDescription('Thisisatutorialfeedforexample.com');

Acouplethingstonote:First,youneedtoknowwhattypeoffeedyou'recreatingupfront,asitwillaffectwhatpropertiesmustbeset,aswellaswhichareactuallyavailable.Ipersonallyliketogeneratefeedsofbothtypes,soI'lldotheabovewithinamethodcallthatacceptsthefeedtypeasanargument,andthenputssomedeclarationswithinconditionalsbasedonthattype.

Second,you'llneedtoknowthefully-qualifiedURIstothefeedtargetandthefeeditself.Thesewillgenerallybesomethingyougenerate;mostroutinglibrarieswillhavethesecapabilities,andyou'llgeneratethesewithinyourapplication,insteadofhard-codingthemasIhavedonehere.

AddingitemsNowthatwehaveourfeed,we'llloopoverourdatasetandadditems.Itemsgenerallyhave:

atitlealinktotheitemanauthorthedateswhenitwasmodified,andlastupdatedcontent

Puttingittogether:

CreateRSSandAtomFeeds

67

Page 68: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$latest=newDateTime('@0');

foreach($dataas$datum){

//Createanemptyentry:

$entry=$feed->createEntry();

//Settheentrytitle:

$entry->setTitle($datum->getTitle());

//Setthelinktotheentry:

$entry->setLink(sprintf('%s%s.html',$baseUri,$datum->getId()));

//Addanauthor,ifyoucan.Eachauthorentryshouldbean

//arraycontainingminimallya"name"key,andzeroormoreof

//thekeys"email"or"uri".

$entry->addAuthor($datum->getAuthor());

//Setthedatecreated:

$entry->setDateCreated(newDateTime($datum->getDateCreated()));

//Andthedatelastupdated:

$modified=newDateTime($datum->getDateModified());

$entry->setDateModified($modified);

//Andfinally,somecontent:

$entry->setContent($datum->getContent());

//Addthenewentrytothefeed:

$feed->addEntry($entry);

//Andmemoizethedatemodified,ifit'smorerecent:

$latest=$modified>$latest?$modified:$latest;

}

Therearequiteafewotherpropertiesyoucanset,andsomeofthesewillvarybasedoncustomextensionsyoumightregisterwiththefeed;theabovearethetypicalitemsyou'llincludeinafeedentry,however.

Whatisthatbitabout$latest,though?

Feedsneedtohaveatimestampindicatingwhentheyweremostrecentlymodified.

Why?Becausefeedsareintendedtobereadbymachinesandaggregators,andneedtoknowwhennewcontentisavailable.

Youcouldsetthedateofmodificationtowhateverthecurrenttimestampisattimeofexecution,butit'sbettertohaveitinsyncwiththemostrecententryinthefeeditself.Assuch,theabovecodecreatesatimestampsettotimestamp0,andchecksforamodifieddatethatisneweroneachiteration.

CreateRSSandAtomFeeds

68

Page 69: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Oncewehavethatinplace,wecanaddthemodifieddatetothefeeditself:

$feed->setDateModified($latest);

RenderingthefeedRenderingthefeedinvolvesexportingit,whichrequiresknowingthefeedtype;thisisnecessarysothatthecorrectXMLmarkupisgenerated.

So,let'screateanRSSfeed:

$rss=$feed->export('rss');

Ifwewanted,andwehavethecorrectpropertiespresent,wecanalsorenderAtom:

$atom=$feed->export('atom');

Nowwhat?

Ioftenpre-generatefeedsandcachethemtothefilesystem.Inthatcase,afile_put_contents()call,usingthegeneratedfeedasthestringcontents,isallthat'sneeded.

Ifyou'reservingthefeedbackoverHTTP,youwillwanttosendbackthecorrectHTTPContent-Typewhenyoudo.Additionally,it'sgoodtosendbackaLast-Modifiedheaderwiththesamedateasthefeed'sownlastmodifieddate,and/oranETagwithahashofthefeed;theseallowclientsperformingHEADrequeststodeterminewhetherornottheyneedtoretrievethefullcontent,oriftheyalreadyhavethelatest.

IfyouareusingPSR-7middleware,theseprocessesmightlooklikethis:

CreateRSSandAtomFeeds

69

Page 70: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Diactoros\Response\TextResponse;

$commonHeaders=[

'Last-Modified'=>$feed->getDateModified()->format('c'),

'ETag'=>hash('sha256',$feed)

];

//ForanRSSfeed:

returnnewTextResponse($rss,200,array_merge(

$commonHeaders,

['Content-Type'=>'application/rss+xml']

));

//ForanAtomfeed:

returnnewTextResponse($atom,200,array_merge(

$commonHeaders,

['Content-Type'=>'application/atom+xml']

));

Summingupzend-feed'sgenerationcapabilitiesareincrediblyflexible,whilemakingthegeneraluse-casestraight-forward.Wehavecreatedfeedsforblogposts,releases,tweets,andcommentingsystemsusingthecomponent;itdoesexactlywhatitadvertises.

Visitthezend-feeddocumentation formoreinformation.

Footnotes

.https://docs.zendframework.com/zend-feed/↩

1

1

CreateRSSandAtomFeeds

70

Page 71: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Managepermissionswithzend-permissions-rbacbyMatthewWeierO'Phinney

InthearticleManagepermissionswithzend-permissions-acl,wecoverusageofAccessControlLists(ACL)formanaginguserpermissions.Inthisarticle,we'llcoveranotheroptionprovidedbyZendFramework,zend-permissions-rbac ,ourlightweightrole-basedaccesscontrol(RBAC)implementation.

Installingzend-permissions-rbacJustasyouwouldanyofourcomponents,installzend-permissions-rbacviaComposer:

$composerrequirezendframework/zend-permissions-rbac

ThecomponenthasnorequirementsatthistimeotherthanaPHPversionofatleast5.5.

VocabularyInRBACsystems,wehavethreeprimaryitemstotrack:

TheRBACsystemcomposeszeroormoreroles.Aroleisgrantedzeroormorepermissions.Weassertwhetherornotaroleisgrantedagivenpermission.

zend-permissions-rbacsupportsroleinheritance,evenallowingaroletoinheritpermissionsfrommultipleotherroles.Thisallowsyoutocreatesomefairlycomplexandfine-grainedpermissionsschemes!

BasicsAsabasicexample,we'llcreateanRBACforacontent-basedwebsite.Let'sstartwitha"guest"role,thatonlyallows"read"permissions.

1

Managepermissionswithzend-permissions-rbac

71

Page 72: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Permissions\Rbac\Rbac;

useZend\Permissions\Rbac\Role;

//Createsomeroles

$guest=newRole('guest');

$guest->addPermission('read');

$rbac=newRbac();

$rbac->addRole($guest);

Wecanthenassertifagivenroleisgrantedspecificpermissions:

$rbac->isGranted('guest','read');//true

$rbac->isGranted('guest','write');//false

Unknownroles

Onethingtonote:iftheroleusedwithisGranted()doesnotexist,thismethodraisesanexception,specificallyaZend\Permissions\Rbac\Exception\InvalidArgumentException,indicatingtherolecouldnotbefound.

Inmanysituations,thismaynotbewhatyouwant;youmaywanttohandlenon-existentrolesgracefully.Youcoulddothisinthreeways.

First,youcantesttoseeiftheroleexistsbeforeyoucheckthepermissions,usinghasRole():

if(!$rbac->hasRole($foo)){

//failed,duetomissingrole

}

if(!$rbac->isGranted($foo,$permission)){

//failed,duetomissingpermissions

}

Second,youcanwraptheisGranted()callinatry/catchblock:

Managepermissionswithzend-permissions-rbac

72

Page 73: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

try{

if(!$rbac->isGranted($foo,$permission)){

//failed,duetomissingpermissions

}

}catch(RbacInvalidArgumentException$e){

if(!strstr($e->getMessage(),'couldbefound')){

//failed,duetomissingrole

}

//someothererroroccured

throw$e;

}

Personally,Idon'tliketouseexceptionsforapplicationflow;thatsaid,inmostcases,youwillbeworkingwitharoleinstancethatyou'vejustaddedtotheRBAC.

Third,zend-permissions-rbachasabuilt-inmechanismforthis:

$rbac->setCreateMissingRoles(true);

Aftercallingthismethod,anyisGranted()callsyoumakewithunknownroleidentifierswillsimplyreturnabooleanfalse.

Thismethodalsoensuresyoudonotencountererrorswhencreatingroleinheritancechainsandaddrolesout-of-order(e.g.,addingchildrenwhichhavenotyetbeencreatedtoaroleyouaredefining).

Assuch,pleaseassumethatallfurtherexampleshavecalledthismethodifcreationoftheRBACinstanceisnotdemonstrated.

RoleinheritanceLet'ssaywewanttobuildonthepreviousexample,andcreatean"editor"rolethatalsoincorporatesthepermissionsofthe"guest"role,andaddsa"write"permission.

Youmightbeinclinedtothinkofthe"editor"asinheritingfromthe"guest"role—inotherwords,thatitisadescendentorchildofit.However,inRBAC,inheritanceworksintheoppositedirection:aparentinheritsallpermissionsofitschildren.Assuch,we'llcreatetheroleasfollows:

Managepermissionswithzend-permissions-rbac

73

Page 74: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$editor=newRole('editor');

$editor->addChild($guest);

$editor->addPermission('write');

$rbac->addRole($editor);

$rbac->isGranted('editor','write');//true

$rbac->isGranted('editor','read');//true

$rbac->isGranted('guest','write');//false

Anotherrolemightbea"reviewer"whocan"moderate"content:

$reviewer=newRole('reviewer');

$reviewer->addChild($guest);

$reviewer->addPermission('moderate');

$rbac->addRole($reviewer);

$rbac->isGranted('reviewer','moderate');//true

$rbac->isGranted('reviewer','write');//false;editoronly!

$rbac->isGranted('reviewer','read');//true

$rbac->isGranted('guest','moderate');//false

Let'screateanother,an"admin"whocandoalloftheabove,butalsohaspermissionsfor"settings":

$admin=newRole('admin');

$admin->addChild($editor);

$admin->addChild($reviewer);

$admin->addPermission('settings');

$rbac->addRole($admin);

$rbac->isGranted('admin','settings');//true

$rbac->isGranted('admin','write');//true

$rbac->isGranted('admin','moderate');//true

$rbac->isGranted('admin','read');//true

$rbac->isGranted('editor','settings');//false

$rbac->isGranted('reviewer','settings');//false

$rbac->isGranted('guest','write');//false

Asyoucansee,permissionslookupsarerecursiveandcollective;theRBACexaminesallchildrenandeachoftheirdescendantsasfardownasitneedstodetermineifagivenpermissionisgranted!

CreatingyourRBAC

Managepermissionswithzend-permissions-rbac

74

Page 75: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

WhenshouldyoucreateyourRBAC,exactly?Andshoulditcontainallrolesandpermissions?

Inmostcases,youwillbevalidatingasingleuser'spermissions.What'sinterestingaboutzend-permissions-rbacisthatifyouknowthatuser'srole,thepermissionstheyhavebeenassigned,andanychildroles(andtheirpermissions)towhichtherolebelongs,youhaveeverythingyouneed.Thismeansthatyoucandomostlookupson-the-fly.

Assuch,youwilltypicallydothefollowing:

Createafinitesetofwell-knownrolesandtheirpermissionsasaglobalRBAC.Addroles(andoptionallypermissions)forthecurrentuser.ValidatethecurrentuseragainsttheRBAC.

Asanexample,let'ssayIhaveauserMariowhohastherole"editor",andalsoaddsthepermission"update".IfourRBACisalreadypopulatedpertheaboveexamples,Imightdothefollowing:

$mario=newRole('mario');

$mario->addChild($editor);

$mario->addPermission('update');

$rbac->addRole($mario);

$rbac->isGranted($mario,'settings');//false;adminonly!

$rbac->isGranted($mario,'update');//true;marioonly!

$rbac->isGranted('editor','update');//false;marioonly!

$rbac->isGranted($mario,'write');//true;alleditors

$rbac->isGranted($mario,'read');//true;allguests

AssigningrolestousersWhenyouhavesomesortofauthenticationsysteminplace,itwillreturnsomesortofidentityoruserinstancegenerally.YouwillthenneedtomapthistoRBACroles.Buthow?

Hopefully,youcanstoreroleinformationwhereveryoupersistyouruserinformation.Sincerolesareessentiallystoredinternallyasstringsbyzend-permissions-rbac,thismeansthatyoucanstoretheuserroleasadiscretedatumwithyouruseridentity.

Onceyouhave,youhaveafewoptions:

Usetheroledirectlyfromyouridentitywhencheckingpermissions:e.g.,$rbac->isGranted($identity->getRole(),'write')

CreateaZend\Permissions\Rbac\Roleinstance(orotherconcreteclass)withtherolefetchedfromtheidentity,andusethatforpermissionschecks:$rbac->isGranted(new

Managepermissionswithzend-permissions-rbac

75

Page 76: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Role($identity->getRole()),'write')

UpdateyouridentityinstancetoimplementZend\Permissions\Rbac\RoleInterface,andpassitdirectlytopermissionschecks:$rbac->isGranted($identity,'write')

Thislatterapproachprovidesanicesolution,asitthenalsoallowsyoutostorespecificpermissionsand/orchildrolesaspartoftheuserdata.

TheRoleInterfacelookslikethefollowing:

Managepermissionswithzend-permissions-rbac

76

Page 77: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceZend\Permissions\Rbac;

useRecursiveIterator;

interfaceRoleInterfaceextendsRecursiveIterator

{

/**

*Getthenameoftherole.

*

*@returnstring

*/

publicfunctiongetName();

/**

*Addpermissiontotherole.

*

*@param$name

*@returnRoleInterface

*/

publicfunctionaddPermission($name);

/**

*Checksifapermissionexistsforthisroleoranychildroles.

*

*@paramstring$name

*@returnbool

*/

publicfunctionhasPermission($name);

/**

*Addachild.

*

*@paramRoleInterface|string$child

*@returnRole

*/

publicfunctionaddChild($child);

/**

*@paramRoleInterface$parent

*@returnRoleInterface

*/

publicfunctionsetParent($parent);

/**

*@returnnull|RoleInterface

*/

publicfunctiongetParent();

}

Managepermissionswithzend-permissions-rbac

77

Page 78: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

TheZend\Permissions\Rbac\AbstractRolecontainsbasicimplementationsofmostmethodsoftheinterface,includinglogicforqueryingchildpermissions,sowesuggestinheritingfromthatifyoucan.

Asanexample,youcouldstorethepermissionsasacomma-separatedstringandtheparentroleasastringinternallywhencreatingyouridentityinstance:

useZend\Permissions\Rbac\AbstractRole;

useZend\Permissions\Rbac\RoleInterface;

useZend\Permissions\Rbac\Role;

classIdentityextendsAbstractRole

{

/**

*@paramstring$username

*@paramstring$role

*@paramarray$permissions

*@paramarray$childRoles

*/

publicfunction__construct(

string$username,

array$permissions=[],

array$childRoles=[]

){

//$nameisdefinedinAbstractRole

$this->name=$username;

foreach($this->permissionsas$permission){

$this->addPermission($permission);

}

$childRoles=array_merge(['guest'],$childRoles);

foreach($this->childRolesas$childRole){

$this->addChild($childRole);

}

}

}

Assumingyourauthenticationsystemusesadatabasetable,andalookupreturnsanarray-likerowwiththeuserinformationonasuccessfullookup,youmightthenseedyouridentityinstanceasfollows:

$identity=newIdentity(

$row['username'],

explode(',',$row['permissions']),

explode(',',$row['roles'])

);

Managepermissionswithzend-permissions-rbac

78

Page 79: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Thisapproachallowsyoutoassignpre-determinedrolestoindividualusers,whilealsoallowingyoutoaddfine-grained,individualpermissions!

CustomassertionsSometimesastaticassertionisnotenough.

Asanexample,wemaywanttoimplementarulethatthecreatorofacontentiteminourwebsitealwayshasrightstoedittheitem.Howwouldweimplementthatwiththeabovesystem?

zend-permissions-rbacallowsyoutodosoviadynamicassertions.SuchassertionsareclassesthatimplementZend\Permissions\Rbac\AssertionInterface,whichdefinesthesinglemethodpublicfunctionassert(Rbac$rbac).

Forthesakeofthisexample,let'sassume:

Thecontentitemisrepresentedasanobject.TheobjecthasamethodgetCreatorUsername()thatwillreturnthesameusernameaswemighthaveinourcustomidentityfromthepreviousexample.

BecausewehavePHP7atourdisposal,we'llcreatetheassertionasananonymousclass:

useZend\Permissions\Rbac\AssertionInterface;

useZend\Permissions\Rbac\Rbac;

useZend\Permissions\Rbac\RoleInterface;

$assertion=newclass($identity,$content)implementsAssertionInterface{

private$content;

private$identity;

publicfunction__construct(RoleInterface$identity,$content)

{

$this->identity=$identity;

$this->content=$content;

}

publicfunctionassert(Rbac$rbac)

{

return$this->identity->getName()===$this->content->getCreatorUsername();

}

};

$rbac->isGranted($mario,'edit',$assertion);//returnstrueif$mariocreated$conte

nt

Thisopensevenmorepossibilitiesthaninheritance!

Managepermissionswithzend-permissions-rbac

79

Page 80: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Summaryzend-permissions-rbacisquitesimpletooperate,butthatsimplicityhidesagreatamountofflexibilityandpower;youcancreateincrediblyfine-grainedpermissionsschemesforyourapplicationsusingthiscomponent!

Footnotes

.https://docs.zendframework.com/zend-permissions-rbac/↩1

Managepermissionswithzend-permissions-rbac

80

Page 81: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Managepermissionswithzend-permissions-aclbyMatthewWeierO'Phinney

InthearticleManagepermissionswithzend-permissions-rbac,wecoverusageofRoleBasedAccessControls(RBAC).Inthisarticle,we'llexploreanotheroptionprovidedbyZendFramework,zend-permissions-acl ,whichimplementsAccessControlLists(ACL).

Thispostwillfollowthesamebasicformatastheonecoveringzend-permissions-rbac,usingthesamebasicexamples.

Installingzend-permissions-aclJustasyouwouldanyofourcomponents,installzend-permissions-aclviaComposer:

$composerrequirezendframework/zend-permissions-acl

ThecomponenthasnorequirementsatthistimeotherthanaPHPversionofatleast5.5.

VocabularyInACLsystems,wehavethreeconcepts:

aresourceissomethingtowhichwecontrolaccess.aroleissomethingthatwillrequestaccesstoaresource.Eachresourcehasprivilegesforwhichaccesswillberequestedtospecificroles.

Asanexample,anauthormightrequesttocreate(privilege)ablogpost(resource);later,aneditor(role)mightrequesttoedit(privilege)ablogpost(resource).

ThechiefdifferencetoRBACisthatRBACessentiallycombinestheresourceandprivilegeintoasingleitem.Byseparatingthem,youcancreateasetofdiscretepermissionsforyourentireapplication,andthencreateroleswithmultiple-inheritanceinordertoimplementfine-grainedpermissions.

ACLs

1

Managepermissionswithzend-permissions-acl

81

Page 82: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

AnACLiscreatedbyinstantiatingtheAclclass:

useZend\Permissions\Acl\Acl;

$acl=newAcl();

Oncethatinstanceisavailable,wecanstartaddingroles,resources,andprivileges.

Forthisblogpost,ourACLwillbeusedforacontent-basedwebsite.

RolesRolesareaddedviathe$acl->addRole()method.Thismethodtakeseitherastringrolename,oraZend\Permissions\Acl\Role\RoleInterfaceinstance.

Let'sstartwitha"guest"role,thatonlyallows"read"permissions.

useZend\Permissions\Acl\Role\GenericRoleasRole;

//Createsomeroles

$guest=newRole('guest');

$acl->addRole($guest);

//OR

$acl->addRole('guest');

Referencingrolesandresources

Rolesaresimplystrings.Wemodelthemasobjectsinzend-permissions-aclinordertoprovidestrongtyping,buttheonlyrequirementisthattheyreturnastringrolename.Assuch,whencreatingpermissions,youcanuseeitheraroleinstance,ortheequivalentname.

Thesameistrueforresources,whichwecoverinalatersection.

Bydefault,zend-permissions-aclimplementsawhitelistapproach.Awhitelistdeniesaccesstoeverythingunlessitisexplicitlywhitelisted.(Thisisasopposedtoablacklist,whereaccessisallowedtoeverythingunlessitisintheblacklist.)Unlessyoureallyknowwhatyou'redoingwedonotsuggesttogglingthis;whitelistsarewidelyregardedasabestpracticeforsecurity.

Whatthatmeansisthat,outofthegate,whilewecandosomeprivilegeassertions:

Managepermissionswithzend-permissions-acl

82

Page 83: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$acl->isAllowed('guest','blog','read');

$acl->isAllowed('guest','blog','write');

thesewillalwaysreturnfalse,denyingaccess.So,weneedtostartaddingprivileges.

PrivilegesPrivilegesareassignedusing$acl->allow().

Fortheguestrole,we'llallowthereadprivilegeonanyresource:

$acl->allow('guest',null,'read');

Thesecondargumenttoallow()istheresource(orresources);specifyingnullindicatestheprivilegeappliestoallresources.Ifwere-runtheaboveassertions,wegetthefollowing:

$acl->isAllowed('guest','blog','read');//true

$acl->isAllowed('guest','blog','write');//false

Managepermissionswithzend-permissions-acl

83

Page 84: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Unknownrolesorresources

Onethingtonote:ifeithertheroleorresourceusedwithisAllowed()doesnotexist,thismethodraisesanexception,specificallyaZend\Permissions\Acl\Exception\InvalidArgumentException,indicatingtheroleorresourcecouldnotbefound.

Inmanysituations,thismaynotbewhatyouwant;youmaywanttohandlenon-existentrolesand/orresourcesgracefully.Youcoulddothisinacoupleways.First,youcantesttoseeiftheroleorresourceexistsbeforeyoucheckthepermissions,usinghasRole()and/orhasResource():

if(!$acl->hasRole($foo)){

//failed,duetomissingrole

}

if(!$acl->hasResource($bar)){

//failed,duetomissingresource

}

if(!$acl->isAllowed($foo,$bar,$privilege)){

//failed,duetoinvalidprivilege

}

Alternately,wraptheisAllowed()callinatry/catchblock:

try{

if(!$acl->isAllowed($foo,$bar,$privilege)){

//failed,duetomissingprivileges

}

}catch(AclInvalidArgumentException$e){

//failed,duetomissingroleorresource

}

Personally,Idon'tliketouseexceptionsforapplicationflow,soIrecommendthefirstsolution.Thatsaid,inmostcases,youwillbeworkingwitharoleinstancethatyou'vejustaddedtotheACL,andshouldonlyperformassertionsagainstknownresources.

ResourcesNowlet'saddsomeactualresources.Thesearealmostexactlylikerolesintermsofusage:youcreateaResourceInterfaceinstancetopasstotheACL,or,moresimply,astring;resourcesareaddedviathe$acl->addResource()method.

Managepermissionswithzend-permissions-acl

84

Page 85: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Permissions\Acl\Resource\GenericResourceasResource;

$resource=newResource('blog');

$acl->addResource($resource);

//OR:

$acl->addResource('blog');

Aresourceisanythingtowhichyouwanttoapplypermissions.Intheremainingexamplesofthispost,we'llusea"blog"astheresource,andprovideavarietyofpermissionsrelatedtoit.

InheritanceLet'ssaywewanttobuildonourpreviousexamples,andcreatean"editor"rolethatalsoincorporatesthepermissionsofthe"guest"role,andaddsa"write"permissiontothe"blog"resource.

UnlikeRBAC,rolesthemselvescontainnoinformationaboutinheritance;instead,theACLtakescareofthatwhenyouaddtheroletotheACL:

$editor=newRole('editor');

$acl->addRole($editor,$guest);//OR:

$acl->addRole($editor,'guest');

Theabovecreatesanewrole,editor,whichinheritsthepermissionsofourguestrole.Now,let'saddaprivilegeallowingeditorstowritetoourblog:

$acl->allow('editor','blog','write');

Withthisinplace,let'sdosomeassertions:

$acl->isAllowed('editor','blog','write');//true

$acl->isAllowed('editor','blog','read');//true

$acl->isAllowed('guest','blog','write');//false

Anotherrolemightbea"reviewer"whocan"moderate"content:

Managepermissionswithzend-permissions-acl

85

Page 86: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$acl->addRole('reviewer','guest');

$acl->allow('reviewer','blog','moderate');

$acl->isAllowed('reviewer','blog','moderate');//true

$acl->isAllowed('reviewer','blog','write');//false;editoronly!

$acl->isAllowed('reviewer','blog','read');//true

$acl->isAllowed('guest','blog','moderate');//false

Let'screateanother,an"admin"whocandoalloftheabove,butalsohaspermissionsfor"settings":

$acl->addRole('admin',['guest','editor','reviewer']);

$acl->allow('admin','blog','settings');

$acl->isAllowed('admin','blog','settings');//true

$acl->isAllowed('admin','blog','write');//true

$acl->isAllowed('admin','blog','moderate');//true

$acl->isAllowed('admin','blog','read');//true

$acl->isAllowed('editor','blog','settings');//false

$acl->isAllowed('reviewer','blog','settings');//false

$acl->isAllowed('guest','blog','write');//false

NotethattheaddRole()callhereprovidesanarrayofrolesasthesecondvaluethistime;whencalledthisway,thenewrolewillinherittheprivilegesofeveryrolelisted;thisallowsformultiple-inheritanceattherolelevel.

Resourceinheritance

ResourceinheritanceworksexactlythesameasRoleinheritance!AddoneormoreparentresourceswhencallingaddResource()ontheACL,andanyprivilegesassignedtothatparentresourcewillalsoapplytothenewresource.

Asanexample,Icouldhavea"news"sectioninmywebsitethathasthesameprivilegeandroleschemaasmyblog:

$acl->addResource('news','blog');

Funwithprivileges!Privilegesareassignedusingallow().Interestingly,likeaddRole()andaddResource(),theroleandresourceargumentspresentedmaybearraysofeach;infact,socantheprivilegesthemselves!

Managepermissionswithzend-permissions-acl

86

Page 87: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Asanexample,wecoulddothefollowing:

$acl->allow(

['reviewer','editor'],

['blog','homepage'],

['write','maintenance']

);

Thiswouldassignthe"write"and"maintenance"privilegesoneachofthe"blog"and"homepage"resourcestothe"reviewer"and"editor"roles!Duetoinheritance,the"admin"rolewouldalsogaintheseprivileges.

CreatingyourACLWhenshouldyoucreateyourACL,exactly?Andshoulditcontainallrolesandpermissions?

Typically,youwillcreateafinitenumberofapplicationordomainpermissions.Inouraboveexamples,wecouldomittheblogresourceandapplytheACLonlywithintheblogdomain(forexample,onlywithinamoduleofazend-mvcorExpressiveapplication);alternately,itcouldbeanapplication-wideACL,withresourcessegregatedbyspecificdomainwithintheapplication.

Ineithercase,youwillgenerally:

Createafinitesetofwell-knownroles,resources,andprivilegesasaglobalorper-domainACL.Createacustomroleforthecurrentuser,typicallyinheritingfromthesetofwell-knownroles.ValidatethecurrentuseragainsttheACL.

UnlikeRBAC,youtypicallywillnotaddcustompermissionsforauser.Thereasonforthisisduetothecomplexityofstoringthecombinationofroles,resources,andprivilegesinadatabase.Storingrolesistrivial:

user_id fullname roles

mario Mario editor,reviewer

Youcouldthencreatetherolebysplittingtherolesfieldandassigningeachasparents:

$acl->addRole($user->getId(),explode(',',$user->getRoles());

Managepermissionswithzend-permissions-acl

87

Page 88: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

However,forfine-grainedpermissions,youwouldessentiallyneedanadditionallookuptablemappingtheusertoaresourceandlistofprivileges:

user_id resource privileges

mario blog update,delete

mario news update

Whileitcanbedone,itisresourceandcodeintensive.

Puttingitalltogether,let'ssaytheuser"mario"hasloggedin,withtherole"editor";further,let'sassumethattheidentityinstanceforouruserimplementsRoleInterface.IfourACLisalreadypopulatedpertheaboveexamples,Imightdothefollowing:

$acl->addRole($mario,$mario->getRoles());

$acl->isAllowed($mario,'blog','settings');//false;adminonly!

$acl->isAllowed($mario,'blog','write');//true;alleditors

$acl->isAllowed($mario,'blog','read');//true;allguests

Now,let'ssaywe'vegonetotheworkofcreatingthejointablenecessaryforstoringuserACLinformation;wemighthavesomethinglikethefollowingtofurtherpopulatetheACL:

foreach($mario->getPrivileges()as$resource=>$privileges){

$acl->allow($mario,$resource,explode(',',$privileges));

}

Wecouldthendothefollowingassertions:

$acl->isAllowed($mario,'blog','update');//true

$acl->isAllowed('editor','blog','update');//false;marioonly!

$acl->isAllowed($mario,'blog','delete');//true

$acl->isAllowed('editor','blog','delete');//false;marioonly!

CustomassertionsFine-grainedastheprivilegesystemcanbe,sometimesit'snotenough.

Asanexample,wemaywanttoimplementarulethatthecreatorofacontentiteminourwebsitealwayshasrightstoedittheitem.Howwouldweimplementthatwiththeabovesystem?

Managepermissionswithzend-permissions-acl

88

Page 89: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

zend-permissions-aclallowsyoutodosoviadynamicassertions.SuchassertionsareclassesthatimplementZend\Permissions\Acl\Assertion\AssertionInterface,whichdefinesasinglemethod:

namespaceZend\Permissions\Assertion;

useZend\Permissions\Acl\Acl;

useZend\Permissions\Acl\Resource\ResourceInterface;

useZend\Permissions\Acl\Role\RoleInterface;

interfaceAssertionInterface

{

/**

*@returnbool

*/

publicfunctionassert(

Acl$acl,

RoleInterface$role=null,

ResourceInterface$resource=null,

$privilege=null

);

}

Forthesakeofthisexample,let'sassume:

WecastouridentitytoaRoleInterfaceinstanceafterretrieval.Thecontentitemisrepresentedasanobject.TheobjecthasamethodgetCreatorUsername()thatwillreturnthesameusernameaswemighthaveinourcustomidentityfromthepreviousexample.Iftheusernameisthesameasthecustomidentity,allowanyprivileges.

BecausewehavePHP7atourdisposal,we'llcreatetheassertionasananonymousclass:

Managepermissionswithzend-permissions-acl

89

Page 90: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Permissions\Acl\Acl;

useZend\Permissions\Acl\Assertion\AssertionInterface;

useZend\Permissions\Acl\Resource\ResourceInterface;

useZend\Permissions\Acl\Role\RoleInterface;

$assertion=newclass($identity,$content)implementsAssertionInterface{

private$content;

private$identity;

publicfunction__construct(RoleInterface$identity,$content)

{

$this->identity=$identity;

$this->content=$content;

}

/**

*@returnbool

*/

publicfunctionassert(

Acl$acl,

RoleInterface$role=null,

ResourceInterface$resource=null,

$privilege=null

){

if(null===$role||$role->getRoleId()!==$this->identity->getRoleId()){

returnfalse;

}

if(null===$resource||'blog'!==$resource->getResourceId()){

returnfalse;

}

return$this->identity->getRoleId()===$this->content->getCreatorUsername();

}

};

//Attachtheassertiontoallrolesontheblogresource;

//customassertionsareprovidedasafourthargumenttoallow().

$acl->allow(null,'blog',null,$assertion);

$acl->isAllowed('mario','blog','edit');//returnstrueif$mariocreated$content

Theabovecreatesanewassertionthatwilltriggerforthe"blog"resourcewhenaprivilegewedonotalreadyknowaboutisqueried.Inthatparticularcase,ifthecreatorofourcontentisthesameasthecurrentuser,itwillreturntrue,allowingaccess!

Bycreatingsuchassertionsin-placewithdataretrievedatruntime,youcanachieveanincredibleamountofflexibilityforyourACLs.

Managepermissionswithzend-permissions-acl

90

Page 91: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Wrappingupzend-permissions-aclprovidesahugeamountofpower,andtheabilitytoprovidebothroleandresourceinheritancecanvastlysimplifysetupofcomplexACLs.Additionally,theprivilegesystemprovidesmuch-neededgranularity.

IfyouwantedtouseACLsinmiddleware,theusageisquitesimilartozend-permissions-rbac:injectyourACLinstanceinyourmiddleware,retrieveyouruseridentity(andthusrole)fromtherequest,andperformqueriesagainsttheACLusingthecurrentmiddlewareorrouteasaresource,andeithertheHTTPmethodorthedomainactionyouwillperformastheprivilege.

Themaindifficultywithzend-permissions-aclisthatthereisno1:1relationshipbetweenaroleandaprivilege,whichmakesstoringACLinformationinadatabasemorecomplex.Ifyoufindyourselfstrugglingwiththatfact,youmaywanttouseRBACinstead.

Footnotes

.https://docs.zendframework.com/zend-permissions-acl/↩1

Managepermissionswithzend-permissions-acl

91

Page 92: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ImplementJSON-RPCwithzend-json-serverbyMatthewWeierO'Phinney

zend-json-server providesaJSON-RPC implementation.JSON-RPCissimilartoXML-RPCorSOAPinthatitimplementsaRemoteProcedureCallserveratasingleURIusingapredictablecallingsemantic.Likeeachoftheseotherprotocols,itprovidestheabilitytointrospecttheserverinordertodeterminewhatcallsareavailable,whatargumentseachcallexpects,andtheexpectedreturnvalue(s);JSON-RPCimplementsthisviaaServiceMappingDescription(SMD) ,whichisusuallyavailableviaanHTTPGETrequesttotheserver.

zend-json-serverwasdesignedtoworkstandalone,allowingyoutomapaURLtoaspecificscriptthatthenhandlestherequest:

$server=newZend\Json\Server\Server();

$server->setClass('Calculator');

//SMDrequest

if('GET'===$_SERVER['REQUEST_METHOD']){

//IndicatetheURLendpoint,andtheJSON-RPCversionused:

$server->setTarget('/json-rpc')

->setEnvelope(Zend\Json\Server\Smd::ENV_JSONRPC_2);

//GrabtheSMD

$smd=$server->getServiceMap();

//ReturntheSMDtotheclient

header('Content-Type:application/json');

echo$smd;

return;

}

//Normalrequest

$server->handle();

Whattheaboveexampledoesis:

Createaserver.Attachaclassorobjecttotheserver.Theserverintrospectsthatclassinordertoexposeanypublicmethodsonitascallsontheserveritself.IfanHTTPGETrequestoccurs,wepresenttheservicemappingdescription.

1 2

3

ImplementJSON-RPCwithzend-json-server

92

Page 93: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Otherwise,weattempttohandletherequest.

AllservercomponentsinZendFrameworkworksimilartotheabove.Introspectionviafunctionorclassreflectionallowsquicklycreatingandexposingservicesviatheseservers,aswellasenablestheserverstoprovideSMD,WSDL,orXML-RPCsysteminformation.

However,thisapproachcanleadtodifficulties:

WhatifIneedaccesstootherapplicationservices?orwanttousethefully-configuredapplicationdependencyinjectioncontainer?WhatifIwanttobeabletocontroltheURIviaarouter?WhatifIwanttobeabletoaddauthenticationorauthorizationinfrontoftheserver?

Inotherwords,howdoIusetheJSON-RPCserveraspartofalargerapplication?

Below,I'lloutlineusingzend-json-serverinbothaZendFrameworkMVCapplication,aswellasviaPSR-7middleware.Inbothcases,youmayassumethatAcme\ServiceModelisaclassexposingpublicmethodswewishtoexposeviatheserver.

Usingzend-json-serverwithinzend-mvcTousezend-json-serverwithinazend-mvcapplication,youwillneedto:

ProvideaZend\Json\Server\ResponseinstancetotheServerinstance.TelltheServerinstancetoreturntheresponse.PopulatetheMVC'sresponsefromtheServer'sresponse.ReturntheMVCresponse(whichwillshort-circuittheviewlayer).

Thisthirdsteprequiresabitoflogic,asthedefaultresponsetype,Zend\Json\Server\Response\Http,doessomelogicaroundsettingheadersthatyou'llneedtoduplicate.

Afullexamplewilllooklikethefollowing:

ImplementJSON-RPCwithzend-json-server

93

Page 94: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceAcme\Controller;

useAcme\ServiceModel;

useZend\Json\Server\ResponseasJsonResponse;

useZend\Json\Server\ServerasJsonServer;

useZend\Mvc\Controller\AbstractActionController;

classJsonRpcControllerextendsAbstractActionController

{

private$model;

publicfunction__construct(ServiceModel$model)

{

$this->model=$model;

}

publicfunctionendpointAction()

{

$server=newJsonServer();

$server

->setClass($this->model)

->setResponse(newJsonResponse())

->setReturnResponse();

/**@varJsonResponse$jsonRpcResponse*/

$jsonRpcResponse=$server->handle();

/**@var\Zend\Http\Response$response*/

$response=$this->getResponse();

//Dowehaveanemptyresponse?

if(!$jsonRpcResponse->isError()

&&null===$jsonRpcResponse->getId()

){

$response->setStatusCode(204);

return$response;

}

//Setthecontent-type

$contentType='application/json-rpc';

if(null!==($smd=$jsonRpcResponse->getServiceMap())){

//SMDisbeingreturned;usealternatecontenttype,ifpresent

$contentType=$smd->getContentType()?:$contentType;

}

//Settheheadersandcontent

$response->getHeaders()->addHeaderLine('Content-Type',$contentType);

$response->setContent($jsonRpcResponse->toJson());

return$response;

}

}

ImplementJSON-RPCwithzend-json-server

94

Page 95: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Injectyourdependencies!

You'llnotethattheaboveexampleacceptstheAcme\ServiceModelinstanceviaitsconstructor.Thismeansthatyouwillneedtoprovideafactoryforyourcontroller,toensurethatitisinjectedwithafullyconfiguredinstance—andthatlikelyalsomeansafactoryforthemodel,too.

Tosimplifythis,youmaywanttocheckouttheConfigAbstractFactory orReflectionBasedAbstractFactory ,bothofwhichwereintroducedinversion3.2.0ofzend-servicemanager.

Usingzend-json-serverwithinPSR-7middlewareUsingzend-json-serverwithinPSR-7middlewareissimilartozend-mvc:

ProvideaZend\Json\Server\ResponseinstancetotheServerinstance.TelltheServerinstancetoreturntheresponse.CreateandreturnaPSR-7responsebasedontheServer'sresponse.

Thecodeendsuplookinglikethefollowing:

namespaceAcme\Controller;

useAcme\ServiceModel;

usePsr\Http\Message\ResponseInterface;

usePsr\Http\Message\ServerRequestInterface;

useZend\Diactoros\Response\EmptyResponse;

useZend\Diactoros\Response\TextResponse;

useZend\Json\Server\ResponseasJsonResponse;

useZend\Json\Server\ServerasJsonServer;

classJsonRpcMiddleware

{

private$model;

publicfunction__construct(ServiceModel$model)

{

$this->model=$model;

}

publicfunction__invoke(

ServerRequestInterface$request,

ResponseInterface$response,

callable$next

){

$server=newJsonServer();

45

ImplementJSON-RPCwithzend-json-server

95

Page 96: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$server

->setClass($this->model)

->setResponse(newJsonResponse())

->setReturnResponse();

/**@varJsonResponse$jsonRpcResponse*/

$jsonRpcResponse=$server->handle();

//Dowehaveanemptyresponse?

if(!$jsonRpcResponse->isError()

&&null===$jsonRpcResponse->getId()

){

returnnewEmptyResponse();

}

//Getthecontent-type

$contentType='application/json-rpc';

if(null!==($smd=$jsonRpcResponse->getServiceMap())){

//SMDisbeingreturned;usealternatecontenttype,ifpresent

$contentType=$smd->getContentType()?:$contentType;

}

returnnewTextResponse(

$jsonRpcResponse->toJson(),

200,

['Content-Type'=>$contentType]

);

}

}

Intheaboveexample,Iuseacoupleofzend-diactoros -specificresponsetypestoensurethatwehavenoextraneousinformationinthereturnedresponses.IuseTextResponsespecifically,asthetoJson()methodonthezend-json-serverresponsereturnstheactualJSONstring,versusadatastructurethatcanbecasttoJSON.

Perthenoteabove,youwillneedtoconfigureyourdependencyinjectioncontainertoinjectthemiddlewareinstancewiththemodel.

Summaryzend-json-serverprovidesaflexible,robust,andsimplewaytocreateJSON-RPCservices.Thedesignofthecomponentmakesitpossibletouseitstandalone,orwithinanyapplicationframeworkyoumightbeusing.Hopefullytheexamplesabovewillaidyouinadaptingitforusewithinyourownapplication!

6

7

ImplementJSON-RPCwithzend-json-server

96

Page 97: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Visitthezend-json-serverdocumentation tofindoutwhatelseyoumightbeabletodowiththiscomponent!

Footnotes

.https://docs.zendframework.com/zend-json-server/↩

.http://groups.google.com/group/json-rpc/↩

.http://www.jsonrpc.org/specification↩

.https://docs.zendframework.com/zend-servicemanager/config-abstract-factory/↩

.https://docs.zendframework.com/zend-servicemanager/reflection-abstract-factory/↩

.https://docs.zendframework.com/zend-diactoros↩

.https://docs.zendframework.com/zend-json-server/↩

7

1

2

3

4

5

6

7

ImplementJSON-RPCwithzend-json-server

97

Page 98: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ImplementanXML-RPCserverwithzend-xmlrpcbyMatthewWeierO'Phinney

zend-xmlrpc providesafull-featuredXML-RPC clientandserverimplementation.XML-RPCisaRemoteProcedureCallprotocolusingHTTPasthetransportandXMLforencodingtherequestsandresponses.

EachXML-RPCrequestconsistsofamethodcall,whichnamestheprocedure(methodName)tocall,alongwithitsparameters.Theserverthenreturnsaresponse,thevaluereturnedbytheprocedure.

Asanexampleofarequest:

POST/xml-rpcHTTP/1.1

Host:api.example.com

Content-Type:text/xml

<?xmlversion="1.0"?>

<methodCall>

<methodName>add</methodName>

<params>

<param>

<value><i4>20</i4></value>

</param>

<param>

<value><i4>22</i4></value>

</param>

</params>

</methodCall>

Theaboveisessentiallyrequestingadd(20,22)fromtheserver.

Aresponsemightlooklikethis:

1 2

ImplementanXML-RPCserverwithzend-xmlrpc

98

Page 99: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

HTTP/1.1200OK

Connection:close

Content-Type:text/xml

<?xmlversion="1.0"?>

<methodResponse>

<params>

<param>

<value><i4>42</i4></value>

</param>

</params>

</methodResponse>

Inthecaseofanerror,yougetafaultresponse,detailingtheproblem:

HTTP/1.1200OK

Connection:close

Content-Type:text/xml

<?xmlversion="1.0"?>

<methodResponse>

<fault>

<value>

<struct>

<member>

<name>faultCode</name>

<value><int>4</int></value>

</member>

<member>

<name>faultString</name>

<value><string>Toofewparameters.</string></value>

</member>

</struct>

</value>

</fault>

</methodResponse>

Content-Length

ThespecificationindicatesthattheContent-Lengthheadermustbepresentinbothrequestsandresponses,andmustbecorrect.IhaveyettoworkwithanyXML-RPCclientsorserversthatfollowedthisrestriction.

Values

ImplementanXML-RPCserverwithzend-xmlrpc

99

Page 100: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

XML-RPCismeanttobeintentionallysimple,andsupportsimpleproceduraloperationswithalimitedsetofallowedvalues.ItpredatesJSON,butsimilarlydefinesarestrictedlistofallowedvaluetypesinordertoallowrepresentingalmostanydatastructure—andnotethatterm,datastructure.Typedobjectswithbehaviorarenevertransferred,onlydata.(ThisishowSOAPdifferentiatesfromXML-RPC.)

KnowingwhatvaluetypesmaybetransmittedoverXML-RPCallowsyoutodeterminewhetherornotit'sagoodfitforyourwebserviceplatform.

Thevaluesallowedinclude:

Integers,viaeither<int>or<i4>tags.(<i4>pointstothefactthatthespecificationrestrictsintegerstofour-bytesignedintegers.)Booleans,via<boolean>;thevaluesareeither0or1.Strings,via<string>.Floatsordoubles,via<double>.Date/Timevalues,inISO-8601format,via<dateTime.iso8601>.Base64-encodedbinaryvalues,via<base64>.

Therearealsotwocompositevaluetypes,<struct>and<array>.A<struct>contains<member>values,whichinturncontaina<name>anda<value>:

<struct>

<member>

<name>minimum</name>

<value><int>0</int></value>

</member>

<member>

<name>maximum</name>

<value><int>100</int></value>

</member>

</struct>

ThesecanbevisualizedasassociativearraysinPHP.

An<array>consistsofa<data>elementcontaininganynumberof<value>items:

<array>

<data>

<value><int>0</int></value>

<value><int>10</int></value>

<value><int>20</int></value>

<value><int>30</int></value>

<value><int>50</int></value>

</data>

</array>

ImplementanXML-RPCserverwithzend-xmlrpc

100

Page 101: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Thevalueswithinanarrayorastructdonotneedtobeofthesametype,whichmakesthemverysuitablefortranslatingtoPHPstructures.

Whilethesevaluesareeasyenoughtocreateandparse,doingsomanuallyleadstoalotofoverhead,particularlyifyouwanttoensurethatyourserverand/orclientisrobust.zend-xmlrpcprovidesallthetoolstoworkwiththis

AutomaticallyservingclassmethodsTosimplifycreatingservers,zend-xmlrpcusesPHP'sReflectionAPI toscanfunctionsandclassmethodsinordertoexposethemasXML-RPCservices.ThisallowsyoutoaddanarbitrarynumberofmethodstoyourXML-RPCserver,whichcanthembehandledviaasingleendpoint.

InvanillaPHP,thisthenlookslike:

$server=newZend\XmlRpc\Server;

$server->setClass('Calculator');

echo$server->handle();

Internally,zend-xmlrpcwilltakecareoftypeconversionsfromtheincomingrequest.Todoso,however,youmayneedtodocumentyourtypesusingslightlydifferentnotationwithinyourdocblocks.Asexamples,thefollowingtypesdonothavedirectanaloguesinPHP:

dateTime.iso8601base64struct

Ifyouwanttoacceptorreturnanyofthesetypes,documentthem:

/**

*@paramdateTime.iso8601$data

*@parambase64$data

*@paramstruct$map

*@returnbase64

*/

functionmethodWithOddParameters($date,$data,array$map)

{

}

3

ImplementanXML-RPCserverwithzend-xmlrpc

101

Page 102: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Structs

zend-xmlrpcdoescontainlogictodetermineifanarrayvalueisanindexedarrayoranassociativearray,andwillgenerallyproperlyconvertthese.However,westillrecommenddocumentingthemorespecifictypesasnotedaboveforpurposesofusingthesystem.methodHelpfunctionality,whichisdetailedbelow.

Youmayalsoaddfunctions:

$server->addFunction('add');

Aservercanacceptmultiplefunctionsandclasses.However,beawarethatwhendoingso,youneedtobecarefulaboutnamingconflicts.Fortunately,zend-xmlrpchaswaystoresolvethose,aswell!

IfyoulookatmanyXML-RPCexamples,theywillusemethodnamessuchascalculator.addortransaction.process.zend-xmlrpc,whenperformingreflection,usesthemethodorfunctionnamebydefault,whichwillbetheportionfollowingthe.inthepreviousexamples.However,youcanalsonamespacethese,usinganadditionalargumenttoeitheraddFunction()orsetClass():

//ExposesCalculatormethodsundercalculator.*:

$server->setClass('Calculator','calculator');

//Exposestransaction.process:

$server->addFunction('process','transaction');

Thiscanbeparticularlyusefulwhenexposingmultipleclassesthatmayexposethesamemethodnames.

ServerintrospectionWhilenotanofficialpartofthestandard,manyserversandclientssupporttheXML-RPCIntrospectionprotocol .Theprotocoldefinesthreemethods:

system.listMethods,whichreturnsastructofmethodssupportedbytheserver.system.methodSignature,whichreturnsastructdetailingtheargumentstotherequestedmethod.system.methodHelp,whichreturnsastringdescriptionoftherequestedmethod.

Theserverimplementationinzend-xmlrpcsupportstheseout-of-the-box,allowingyourclientstogetinformationonexposedservices!

4

ImplementanXML-RPCserverwithzend-xmlrpc

102

Page 103: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

zend-xmlrpcclientandintrospection

Theclientexposedwithinzend-xmlrpcwillnativelyusetheintrospectionprotocolinordertoprovideafluent,method-likewayofinvokingXML-RPCmethods:

$client=newZend\XmlRpc\Client('https://xmlrpc.example.com/');

$service=$client->getProxy();//invokesintrospection!

$value=$service->calculator->add(20,22);//invokescalculator.add(20,22)

FaultsandexceptionsBydefault,zend-xmlrpccatchesexceptionsinyourserviceclasses,andraisesfaultresponses.However,thesefaultresponsesomittheexceptiondetailsbydefault,topreventleakingsensitiveinformation.

Youcan,however,whitelistexceptiontypeswiththeserver:

useApp\Exception;

useZend\XmlRpc\Server\Fault;

Fault::attachFaultException(Exception\InvalidArgumentException::class);

Whenyoudoso,theexceptioncodeandmessagewillbeusedtogeneratethefaultresponse.Note:anyexceptioninthatparticularinheritancehierarchywillthenbeexposedaswell!

Integratingwithzend-mvcTheaboveexamplesalldemonstrateusageinstandalonescripts;whatifyouwanttousetheserverinsidezend-mvc?

Todoso,weneedtodotwothingsdifferently:

WeneedtocreateourownZend\XmlRpc\RequestandseeditfromtheMVCrequestcontent.WeneedtocasttheresponsereturnedbyZend\XmlRpc\Server::handle()toanMVCresponse.

ImplementanXML-RPCserverwithzend-xmlrpc

103

Page 104: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceAcme\Controller;

useAcme\Model\Calculator;

useZend\XmlRpc\RequestasXmlRpcRequest;

useZend\XmlRpc\ResponseasXmlRpcResponse;

useZend\XmlRpc\ServerasXmlRpcServer;

useZend\Mvc\Controller\AbstractActionController;

classXmlRpcControllerextendsAbstractActionController

{

private$calculator;

publicfunction__construct(Calculator$calculator)

{

$this->calculator=$calculator;

}

publicfunctionendpointAction()

{

/**@var\Zend\Http\Request$request*/

$request=$this->getRequest();

//SeedtheXML-RPCrequest

$xmlRpcRequest=newXmlRpcRequest();

$xmlRpcRequest->loadXml($request->getContent());

//Createtheserver

$server=newXmlRpcServer();

$server->setClass($this->calculator,'calculator');

/**@varXmlRpcResponse$xmlRpcResponse*/

$xmlRpcResponse=$server->handle($xmlRpcRequest);

/**@var\Zend\Http\Response$response*/

$response=$this->getResponse();

//Settheheadersandcontent

$response->getHeaders()->addHeaderLine('Content-Type','text/xml');

$response->setContent($xmlRpcResponse->saveXml());

return$response;

}

}

ImplementanXML-RPCserverwithzend-xmlrpc

104

Page 105: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Injectyourdependencies!

You'llnotethattheaboveexampleacceptstheAcme\Model\Calculatorinstanceviaitsconstructor.Thismeansthatyouwillneedtoprovideafactoryforyourcontroller,toensurethatitisinjectedwithafullyconfiguredinstance—andthatlikelyalsomeansafactoryforthemodel,too.

Tosimplifythis,youmaywanttocheckouttheConfigAbstractFactory orReflectionBasedAbstractFactory ,bothofwhichwereintroducedinversion3.2.0ofzend-servicemanager.

Usingzend-xmlrpc'sserverwithinPSR-7middlewareUsingthezend-xmlrpcserverwithinPSR-7middlewareissimilartozend-mvc.

56

ImplementanXML-RPCserverwithzend-xmlrpc

105

Page 106: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceAcme\Controller;

useAcme\Model\Calculator;

usePsr\Http\Message\ResponseInterface;

usePsr\Http\Message\ServerRequestInterface;

useZend\Diactoros\Response\HtmlResponse;

useZend\XmlRpc\RequestasXmlRpcRequest;

useZend\XmlRpc\ResponseasXmlRpcResponse;

useZend\XmlRpc\ServerasXmlRpcServer;

classXmlRpcMiddleware

{

private$calculator;

publicfunction__construct(Calculator$calculator)

{

$this->calculator=$calculator;

}

publicfunction__invoke(

ServerRequestInterface$request,

ResponseInterface$response,

callable$next

){

//SeedtheXML-RPCrequest

$xmlRpcRequest=newXmlRpcRequest();

$xmlRpcRequest->loadXml((string)$request->getBody());

$server=newXmlRpcServer();

$server->setClass($this->calculator,'calculator');

/**@varXmlRpcResponse$xmlRpcResponse*/

$xmlRpcResponse=$server->handle($xmlRpcRequest);

returnnewHtmlResponse(

$xmlRpcResponse->saveXml(),

200,

['Content-Type'=>'text/xml']

);

}

}

Intheaboveexample,Iusethezend-diactoros -specificHtmlResponsetypetogeneratetheresponse;thiscouldbeanyotherresponsetype,aslongastheContent-Typeheaderissetcorrectly,andthestatuscodeissetto200.

Perthenoteabove,youwillneedtoconfigureyourdependencyinjectioncontainertoinjectthemiddlewareinstancewiththemodel.

7

ImplementanXML-RPCserverwithzend-xmlrpc

106

Page 107: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

SummaryWhileXML-RPCmaynotbedujour,itisatriedandtruemethodofexposingwebservicesthathaspersistedforclosetotwodecades.zend-xmlrpc'sserverimplementationprovidesaflexible,robust,andsimplewaytocreateXML-RPCservicesaroundtheclassesandfunctionsyoudefineinPHP,makingitpossibletouseitstandalone,orwithinanyapplicationframeworkyoumightbeusing.Hopefullytheexamplesabovewillaidyouinadaptingitforusewithinyourownapplication!

Visitthezend-xmlrpcserverdocumentation tofindoutwhatelseyoumightbeabletodowiththiscomponent.

Footnotes

.https://docs.zendframework.com/zend-xmlrpc/↩

.http://xmlrpc.scripting.com/spec.html↩

.http://php.net/Reflection↩

.http://xmlrpc-c.sourceforge.net/introspection.html↩

.https://docs.zendframework.com/zend-servicemanager/config-abstract-factory/↩

.https://docs.zendframework.com/zend-servicemanager/reflection-abstract-factory/↩

.https://docs.zendframework.com/zend-diactoros↩

.https://docs.zendframework.com/zend-xmlrpc/server/↩

8

1

2

3

4

5

6

7

8

ImplementanXML-RPCserverwithzend-xmlrpc

107

Page 108: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ImplementaSOAPserverwithzend-soapbyMatthewWeierO'Phinney

zend-soap providesafull-featuredSOAP implementation.SOAPisanXML-basedwebprotocoldesignedtoallowdescribingmessages,and,optionally,operationstoperform.It'ssimilartoXML-RPC,butwithafewkeydifferences:

Arbitrarydatastructuresmaybedescribed;youarenotlimitedtothebasicscalar,list,andstructtypesofXML-RPC.Messagesareoftenserializationsofspecificobjecttypesoneitherorboththeclientandserver.TheSOAPmessagemayincludeinformationonitsownstructuretoallowtheserverorclienttodeterminehowtointerpretthemessage.

Multipleoperationsmaybedescribedinamessageaswell,versustheonecall,oneoperationstructureofXML-RPC.

Inotherwords,it'sanextensibleprotocol.Thisprovidesobviousbenefits,butalsoadisadvantage:creatingandparsingSOAPmessagescanquicklybecomequitecomplex!

Toalleviatethatcomplexity,ZendFrameworkprovidesthezend-soapcomponent,whichincludesaserverimplementation.

WhythesearticlesonRPCservices?

WeloveREST;oneofourprojectsisApigility ,whichallowsyoutosimplyandquicklybuildRESTAPIs.However,thereareoccasionswhereRPCmaybeabetterfit:

Ifyourservicesarelessresourceoriented,andmorefunctionoriented(e.g.,providingcalculations).

Ifconsumersofyourservicesmayneedmoreuniformityintheservicearchitectureinordertoensuretheycanquicklyandeasilyconsumetheservices,withoutneedingtocreateuniquetoolingforeachserviceexposed.WhilethegoalofRESTistoofferdiscovery,wheneverypayloadtosendorreceiveisdifferent,thiscanoftenleadtoanexplosionofcodewhenconsumingmanyservices.

Someorganizationsandcompaniesmaystandardizeoncertainwebserviceprotocolsduetoexistingtooling,abilitytotraindevelopers,etc.

WhileRESTmaybethepreferredwaytoarchitectwebservices,theseandotherreasonsoftendictateotherapproaches.Assuch,weprovidetheseRPCalternativesforPHPdevelopers.

1 2

3

ImplementaSOAPserverwithzend-soap

108

Page 109: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

WhatbenefitsdoesitofferoverthePHPextension?PHPprovidesSOAPclientandservercapabilitiesalreadyviaitsSOAPextension ;whydoweofferacomponent?

Bydefault,PHP'sSoapServer::handle()will:

GrabthePOSTbody(php://input),unlessanXMLstringispassedtoit.EmittheheadersandSOAPXMLresponsebodytotheoutputbuffer.

ExceptionsorPHPerrorsraisedduringprocessingmayresultinaSOAPfaultresponse,withnodetails,orcanresultininvalid/emptySOAPresponsesreturnedtotheclient.

Theprimarybenefitzend-soapprovides,then,iserrorhandling.Youcanwhitelistexceptiontypes,and,whenencountered,faultresponsescontainingtheexceptiondetailswillbereturned.PHPerrorswillbeemittedasSOAPfaults.

Thenextthingthatzend-soapoffersisWSDLgeneration.WSDLallowsyoutodescribethewebservicesyouoffer,sothatclientsknowhowtoworkwithyourservices.ext/soapprovidesnofunctionalityaroundcreatingWSDL;itsimplyexpectsthatyouwillhaveavalidoneforusewiththeclientorserver.

zend-soapprovidesanAutoDiscoverclassthatusesreflectionontheclassesandfunctionsyoupassitinordertobuildavalidWSDLforyou;youcanthenprovidethistoyourserverandyourclients.

CreatingaserverTherearetwopartstoprovidingaSOAPserver:

Providingtheserveritself,whichwillhandlerequests.ProvidingtheWSDL.

Buildingeachfollowsthesameprocess;yousimplyemitthemwithdifferentHTTPContent-Typeheaders,andunderdifferentHTTPmethods(theserverwillalwaysreacttoPOSTrequests,whileWSDLshouldbeavailableviaGET).

First,let'sdefineafunctionforpopulatingaserverinstancewithclassesandfunctions:

4

ImplementaSOAPserverwithzend-soap

109

Page 110: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useAcme\Model;

functionpopulateServer($server,array$env)

{

//Exposeaclassanditsmethods:

$server->setClass(Model\Calculator::class);

//Orexposeanobjectinstanceanditsmethods.

//However,thisonlyworksforZend\Soap\Server,notAutoDiscover,so

//shouldnotbeusedhere.

//$server->setObject(newModel\Env($env));

//Exposeafunction:

$server->addFunction('Acme\Model\ping');

}

Notethat$serverisnottype-hinted;therationaleforthisdecisionwillbecomemoreobvioussoon.

Now,let'sassumethattheabovefunctionisavailabletous,anduseittocreateourWSDL:

//File/soap/wsdl.php

useZend\Soap\AutoDiscover;

if($_SERVER['REQUEST_METHOD']!=='GET'){

//OnlyhandleGETrequests

header('HTTP/1.1400ClientError');

exit;

}

$wsdl=newAutoDiscover();

populateServer($wsdl,$_ENV);

$wsdl->handle();

Done!TheabovewillemittheWSDLforeithertheclientorservertoconsume.

Now,let'screatetheserver.Theserverrequiresafewthings:

Thepublic,HTTP-accessiblelocationoftheWSDL.SoapServeroptions,includingtheactorURIfortheserverandSOAPversiontargeted.

Additionally,we'llneedtonotifytheserverofitscapabilities,viathepopulateServer()function.

ImplementaSOAPserverwithzend-soap

110

Page 111: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

//File/soap/server.php

useZend\Soap\Server;

if($_SERVER['REQUEST_METHOD']!=='POST'){

//OnlyhandlePOSTrequests

header('HTTP/1.1400ClientError');

exit;

}

$server=newServer(dirname($_SERVER['REQUEST_URI']).'/wsdl.php',[

'actor'=>$_SERVER['REQUEST_URI'],

]);

populateServer($server,$_ENV);

$server->handle();

Thereasonforthelackoftype-hintshouldnowbeclear;boththeServerandAutoDiscoverclasseshavethesameAPIforpopulatingtheinstanceswithclasses,objects,andfunctions;havingacommonfunctionfordoingsoallowsustoensuretheWSDLandserverdonotgooutofsync.

Fromhere,youcanpointyourclientsat/soap/server.phponyourdomain,andtheywillhavealltheinformationtheyneedtoworkwithyourservice.

setObject()

Zend\Soap\ServeralsoexposesasetObject()method,whichwilltakeanobjectinstance,reflectit,andexposeitspublicmethodstotheserver.However,thismethodisonlyavailableintheServerclass,nottheAutoDiscoverclass.

Assuch,ifyouwanttocreatelogicthatcanbere-usedbetweentheServerandAutoDiscoverinstances,youmustconfineyourusagetosetClass().Ifthatclassrequiresconstructorargumentsorotherwaysofsettinginstancestate,youshouldvarythelogicforcreationoftheWSDLviaAutoDiscoverandcreationoftheserverviaServer.

Usingzend-soapwithinazend-mvcapplicationTheabovedetailsanapproachusingvanillaPHP;whataboutusingzend-soapwithinazend-mvccontext?

Todothis,we'llneedtolearnafewmorethings.

ImplementaSOAPserverwithzend-soap

111

Page 112: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

First,youcanprovideServer::handle()withtherequesttoprocess.Thismustbeoneofthefollowing:

aDOMDocumentaDOMNodeaSimpleXMLElementanobjectimplementing__toString(),wherethatmethodreturnsanXMLstringanXMLstring

WecangrabthisinformationfromtheMVCrequestinstance'sbodycontent.

Second,wewillneedtheservertoreturntheresponse,sowecanuseittopopulatetheMVCresponseinstance.WecandothatbycallingServer::setReturnResponse(true).Whenwedo,Server::handle()willreturnanXMLstringrepresentingtheSOAPresponsemessage.

Let'sputitalltogether:

namespaceAcme\Controller;

useAcme\Model;

useZend\Soap\AutoDiscoverasWsdlAutoDiscover;

useZend\Soap\ServerasSoapServer;

useZend\Mvc\Controller\AbstractActionController;

classSoapControllerextendsAbstractActionController

{

private$env;

publicfunction__construct(Model\Env$env)

{

$this->env=$env;

}

publicfunctionwsdlAction()

{

/**@var\Zend\Http\Request$request*/

$request=$this->getRequest();

if(!$request->isGet()){

return$this->prepareClientErrorResponse('GET');

}

$wsdl=newWsdlAutoDiscover();

$this->populateServer($wsdl);

/**@var\Zend\Http\Response$response*/

$response=$this->getResponse();

$response->getHeaders()->addHeaderLine('Content-Type','application/wsdl+xml')

ImplementaSOAPserverwithzend-soap

112

Page 113: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

;

$response->setContent($wsdl->toXml());

return$response;

}

publicfunctionserverAction()

{

/**@var\Zend\Http\Request$request*/

$request=$this->getRequest();

if(!$request->isPost()){

return$this->prepareClientErrorResponse('POST');

}

//Createtheserver

$server=newSoapServer(

$this->url()

->fromRoute('soap/wsdl',[],['force_canonical'=>true]),

[

'actor'=>$this->url()

->fromRoute('soap/server',[],['force_canonical'=>true]),

]

);

$server->setReturnResponse(true);

$this->populateServer($server);

$soapResponse=$server->handle($request->getContent());

/**@var\Zend\Http\Response$response*/

$response=$this->getResponse();

//Settheheadersandcontent

$response->getHeaders()->addHeaderLine('Content-Type','application/soap+xml')

;

$response->setContent($soapResponse);

return$response;

}

privatefunctionprepareClientErrorResponse($allowed)

{

/**@var\Zend\Http\Response$response*/

$response=$this->getResponse();

$response->setStatusCode(405);

$response->getHeaders()->addHeaderLine('Allow',$allowed);

return$response;

}

privatefunctionpopulateServer($server)

{

//Exposeaclassanditsmethods:

$server->setClass(Model\Calculator::class);

//Exposeanobjectinstanceanditsmethods:

ImplementaSOAPserverwithzend-soap

113

Page 114: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$server->setObject($this->env);

//Exposeafunction:

$server->addFunction('Acme\Model\ping');

}

}

Theaboveassumesyou'vecreatedroutessoap/serverandsoap/wsdl,andusesthosetogeneratetheURIsfortheserverandWSDL,respectively;thesoap/serverrouteshouldmaptotheSoapController::serverAction()methodandthesoap/wsdlrouteshouldmaptotheSoapController::wsdlAction()method.

Injectyourdependencies!

You'llnotethattheaboveexampleacceptstheAcme\Model\Envinstanceviaitsconstructor,allowingustoinjectafully-configuredinstanceintotheserverand/orWSDLautodiscovery.Thismeansthatyouwillneedtoprovideafactoryforyourcontroller,toensurethatitisinjectedwithafullyconfiguredinstance—andthatlikelyalsomeansafactoryforthemodel,too.

Tosimplifythis,youmaywanttocheckouttheConfigAbstractFactory orReflectionBasedAbstractFactory ,bothofwhichwereintroducedinversion3.2.0ofzend-servicemanager.

Usingzend-soapwithinPSR-7middlewareUsingzend-soapinPSR-7middlewareisessentiallythesameaswhatwedetailforzend-mvc:you'llneedtopulltherequestcontentfortheserver,andusetheSOAPresponsereturnedtopopulateaPSR-7responseinstance.

Theexamplebelowassumesthefollowing:

YouareusingtheUrlHelperandServerUrlHelperfromzend-expressive-helpers togenerateURIs.Youareroutingtoeachmiddlewaresuchthat:

The'soap.server'routewillmaptotheSoapServerMiddleware,andonlyallowPOSTrequests.The'soap.wsdl'routewillmaptotheWsdlMiddleware,andonlyallowGETrequests.

namespaceAcme\Middleware;

useAcme\Model;

56

7

ImplementaSOAPserverwithzend-soap

114

Page 115: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

usePsr\Http\Message\ResponseInterface;

usePsr\Http\Message\ServerRequestInterface;

useZend\Diactoros\Response\TextResponse;

useZend\Soap\AutoDiscoverasWsdlAutoDiscover;

useZend\Soap\ServerasSoapServer;

traitCommon

{

private$env;

private$urlHelper;

private$serverUrlHelper;

publicfunction__construct(

Model\Env$env,

UrlHelper$urlHelper,

ServerUrlHelper$serverUrlHelper

){

$this->env=$env;

$this->urlHelper=$urlHelper;

$this->serverUrlHelper=$serverUrlHelper;

}

privatefunctionpopulateServer($server)

{

//Exposeaclassanditsmethods:

$server->setClass(Model\Calculator::class);

//Exposeanobjectinstanceanditsmethods:

$server->setObject($this->env);

//Exposeafunction:

$server->addFunction('Acme\Model\ping');

}

}

classSoapServerMiddleware

{

useCommon;

publicfunction__invoke(

ServerRequestInterface$request,

ResponseInterface$response,

callable$next

){

$server=newSoapServer($this->generateUri('soap.wsdl'),[

'actor'=>$this->generateUri('soap.server')

]);

$server->setReturnResponse(true);

$this->populateServer($server);

$xml=$server->handle((string)$request->getBody());

ImplementaSOAPserverwithzend-soap

115

Page 116: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

returnnewTextResponse($xml,200,[

'Content-Type'=>'application/soap+xml',

]);

}

privatefunctiongenerateUri($route)

{

return($this->serverUrlHelper)(

($this->urlHelper)($route)

);

}

}

classWsdlMiddleware

{

useCommon;

publicfunction__invoke(

ServerRequestInterface$request,

ResponseInterface$response,

callable$next

){

$server=newWsdlAutoDiscover();

$this->populateServer($server);

returnnewTextResponse($server->toXml(),200,[

'Content-Type'=>'application/wsdl+xml',

]);

}

}

Sinceeachmiddlewarehasthesamebasicconstruction,I'vecreatedatraitwiththecommonfunctionality,andcomposeditintoeachmiddleware.Asyouwillnote,theactualworkofeachmiddlewareisrelativelysimple;createaserver,andmarshalaresposnetoreturn.

Intheaboveexample,Iusethezend-diactoros -specificTextResponsetypetogeneratetheresponse;thiscouldbeanyotherresponsetype,aslongastheContent-Typeheaderissetcorrectly,andthestatuscodeissetto200.

Perthenoteabove,youwillneedtoconfigureyourdependencyinjectioncontainertoinjectthemiddlewareinstanceswiththemodelandhelpers.

Summary

8

ImplementaSOAPserverwithzend-soap

116

Page 117: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

WhileSOAPisoftenmalignedinPHPcircles,itisstillinwideusewithinenterprises,andusedinmanycasestoprovidecross-platformwebserviceswithpredictablebehaviors.Itcanbequitecomplex,butzend-soaphelpssmoothoutthebulkofthecomplexity.Youcanuseitstandalone,withinaZendFrameworkMVCapplication,orwithinanyapplicationframeworkyoumightbeusing.

Visitthezend-soapdocumentation tofindoutwhatelseyoumightbeabletodowiththiscomponent.

Footnotes

.https://docs.zendframework.com/zend-soap/↩

.https://en.wikipedia.org/wiki/SOAP↩

.https://apigility.org↩

.http://php.net/soap↩

.https://docs.zendframework.com/zend-servicemanager/config-abstract-factory/↩

.https://docs.zendframework.com/zend-servicemanager/reflection-abstract-factory/↩

.https://docs.zendframework.com/zend-expressive/features/helpers/url-helper↩

.https://docs.zendframework.com/zend-diactoros↩

.https://docs.zendframework.com/zend-soap/↩

9

1

2

3

4

5

6

7

8

9

ImplementaSOAPserverwithzend-soap

117

Page 118: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Context-specificescapingwithzend-escaperbyMatthewWeierO'Phinney

SecurityofyourwebsiteisnotjustaboutmitigatingandpreventingthingslikeSQLinjection;it'salsoaboutprotectingyourusersastheybrowsethesitefromthingslikecross-sitescripting(XSS)attacks,cross-siterequestforgery(CSRF),andmore.Inparticular,youneedtobeverycarefulabouthowyougenerateHTML,CSS,andJavaScripttoensurethatyoudonotcreatesuchvectors.

Asthemantragoes,filterinput,andescapeoutput.

Believeitornot,escapinginPHPisnotterriblyeasytogetright.Forexample,toproperlyescapeHTML,youneedtousehtmlspecialchars(),withtheflagsENT_QUOTES|ENT_SUBSTITUTE,andprovideacharacterencoding.Whoreallywantstowrite

htmlspecialchars($string,ENT_QUOTES|ENT_SUBSTITUTE,'utf-8')

everysingletimetheyneedtoescapeastringforuseinHTML?

EscapingHTMLattributes,CSS,andJavaScripteachrequirearegularexpressiontoidentifyknownproblemstrings,andanumberofheuristicstoreplaceunicodecharacterswithhexentities,eachwithdifferentrules.Whilemuchofthiscanbedonewithbuilt-inPHPfeatures,thesefeaturesdonotcatchallpotentialattackvectors.Acomprehensivesolutionisrequired.

ZendFrameworkprovidesthezend-escaper componenttomanagethiscomplexityforyou,exposingfunctionalityforescapingHTML,HTMLattributes,JavaScript,CSS,andURLstoensuretheyaresafeforthebrowser.

Installationzend-escaperonlyrequiresPHP(ofatleastversion5.5atthetimeofwriting),andisinstallableviacomposer:

$composerrequirezendframework/zend-escaper

1

Context-specificescapingwithzend-escaper

118

Page 119: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

UsageWhileweconsideredmakingzend-escaperactaseitherfunctionsorstaticmethods,therewasonethingintheway:properescapingrequiresknowledgeoftheintendedoutputcharacterset.Assuch,Zend\Escaper\Escapermustfirstbeinstantiatedwiththeoutputcharacterset;onceithas,youcallmethodsonit.

useZend\Escaper\Escaper;

$escaper=newEscaper('iso-8859-1');

Bydefault,ifnocharactersetisprovided,itassumesutf-8;werecommendusingUTF-8unlessthereisacompellingreasonnotto.Assuch,inmostcases,youcaninstantiateitwithnoarguments:

useZend\Escaper\Escaper;

$escaper=newEscaper();

Theclassprovidesfivemethods:

escapeHtml(string$html):stringwillescapethestringsoitmaybesafelyusedasHTML.Ingeneral,thismeans<,>,and&characters(aswellasothers)areescapedtopreventinjectionofunwantedtagsandentities.escapeHtmlAttr(string$value):stringescapesastringsoitmaysafelybeusedwithinanHTMLattributevalue.escapeJs(string$js):stringescapesastringsoitmaysafelybeusedwithina<script>tag.Inparticular,thisensuresthatthecodeinjectedcannotcontaincontinuationsandescapesequencesthatleadtoXSSvectors.escapeCss(string$css):stringescapesastringtouseasCSSwithin<style>tags;similartoJS,itpreventscontinuationsandescapesequencesthatcanleadtoXSSvectors.escapeUrl(string$urlPart):stringescapesastringtousewithinaURL;itshouldnotbeusedtoescapetheentireURLitself.ItshouldbeusedtoescapethingssuchastheURLpath,querystringparameters,andfragment,however.

So,asexamples:

Context-specificescapingwithzend-escaper

119

Page 120: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

echo$escaper->escapeHtml('<script>alert("zf")</script>');

//resultsin"&lt;script&gt;alert(&quot;zf&quot;)&lt;/script&gt;"

echo$escaper->escapeHtmlAttr("<script>alert('zf')</script>");

//resultsin"&lt;script&gt;alert&#x28;&#x27;zf&#x27;&#x29;&lt;&#x2F;script&gt;"

echo$escaper->escapeJs("bar&quot;;alert(&quot;zf&quot;);varxss=&quot;true");

//resultsin"bar\x26quot\x3B\x3B\x20alert\x28\x26quot\x3Bzf\x26quot\x3B\x29\x3B\x20v

ar\x20xss\x3D\x26quot\x3Btrue"

echo$escaper->escapeCss("background-image:url('/zf.png?</style><script>alert(\'zf\')

</script>');");

//resultsin"background\2Dimage\3A\20url\28\27\2Fzf\2Epng\3F\3C\2Fstyle\3E

\3Cscript\3Ealert\28\5C\27zf\5C\27\29\3C\2Fscript\3E\27\29\3B"

echo$escaper->escapeUrl('/foo"onmouseover="alert(\'zf\')');

//resultsin"%2Ffoo%20%22%20onmouseover%3D%22alert%28%27zf%27%29"

Asyoucanseefromtheseexamples,thecomponentaggresivelyfilterseachstringtoensureitisescapedcorrectlyforthecontextforwhichitisintended.

Howandwheremightyouusethis?

Withintemplates,toensureoutputisproperlyescaped.Forexample,zend-viewincludeshelpersforit;itwouldbeeasytoaddsuchfunctionalitytoPlates andothertemplatingsolutions.Inemailtemplates.InserializersforAPIs,toensurethingslikeURLsorXMLattributedataareproperlyescaped.Inerrorhandlers,toensureerrormessagesareescapedanddonotcontainXSSvectors.

Themainpointisthatescapingcanbeeasywithzend-escaper;startsecuringyouroutputtoday!

Footnotes

.https://docs.zendframework.com/zend-escaper↩

.https://docs.zendframework.com/zend-view↩

.http://platesphp.com↩

23

1

2

3

Context-specificescapingwithzend-escaper

120

Page 121: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Filterinputusingzend-filterbyMatthewWeierO'Phinney

Whensecuringyourwebsite,themantrais"Filterinput,escapeoutput."WecoveredescapingoutputintheContext-specificescapingwithzend-escaperarticle.We'renowgoingtoturntofilteringinput.

Filteringinputisrathercomplex,andspansanumberofpractices:

Filtering/normalizinginput.Asanexample,yourwebpagemayhaveaformthatallowssubmittingacreditcardnumber.Thesehaveavarietyofformatsthatmayincludespacesordashesordots—buttheonlycharactersthatareofimportancearethedigits.Assuch,youwillwanttonormalizesuchinputtostripouttheunwantedcharacters.Validatinginput.Onceyouhavedonesuchnormalization,youcanthenchecktoseethatthedataisactuallyvalidforitscontext.Thismayincludeoneormorerules.Usingourcreditcardexample,youmightfirstcheckitisofanappropriatelength,andthenverifythatitbeginswithaknownvendordigit,andonlyafterthosepass,validatethenumberagainstaonlineservice.

Fornow,we'regoingtolookatthefirstitem,filteringandnormalizinginput,usingthecomponentzend-filter .

InstallationToinstallzend-filter,useComposer:

$composerrequirezendframework/zend-filter

Currently,theonlyrequireddependencyiszend-stdlib.However,afewothercomponentsaresuggested,basedonwhichfiltersand/orfeaturseyoumaywanttouse:

zendframework/zend-servicemanagerisusedbytheFilterChaincomponentforlookingupfiltersbytheirshortname(versusfullyqualifiedclassname).zendframework/zend-cryptisusedbytheencryptionanddecryptionfilters.zendframework/zend-uriisusedbytheUriNormalizefilter.zendframework/zend-i18nisusedbyseveralfiltersthatprovideinternationalizationfeatures.

1

Filterinputusingzend-filter

121

Page 122: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Forourexamples,we'llbeusingtheFilterChainfunctionality,sowewillalsowanttoinstallzend-servicemanager:

$composerrequirezendframework/zend-servicemanager

FilterInterfaceFilterscanbeoneoftwothings:acallablethatacceptsasingleargument(thevaluetofilter),oraninstanceofZend\Filter\FilterInterface:

namespaceZend\Filter;

interfaceFilterInterface

{

publicfunctionfilter($value);

}

Thevaluecanbeliterallyanything,andthefiltercanreturnanythingitself.Generallyspeaking,ifafiltercannotoperateonthevalue,itisexpectedtoreturnitverbatim.

zend-filterprovidesafewdozenfiltersforcommonoperations,includingthingslike:

Normalizingstrings,integers,etc.totheircorrespondingbooleanvalues.Normalizingstringsrepresentingintegerstointegervalues.Normalizingemptyvaluestonullvalues.Normalizinginputsetsrepresentingdateand/ortimeselectionsfromformstoDateTimeinstances.NormalizingURIvalues.Comparingvaluestowhitelistsandblacklists.Trimmingwhitespace,strippingnewlines,andremovingHTMLtagsorentities.Upperandlowercasingwords.Strippingeverythingbutdigits.PerformingPCREregexpreplacements.Wordinflection(camel-casetounderscoresandviceversa,etc.).Decryptingandencryptingfilecontents,aswellascastingfilecontentstoloweroruppercase.Compressinganddecompressingvalues.Decryptingandencryptingvalues.

Anyofthesemaybeusedbythemselves.However,inmostcases,ifthat'sallyou'redoing,youmightaswelljustdothefunctionalityinline.So,what'sthebenefitofzend-filter?

Filterinputusingzend-filter

122

Page 123: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Chainingfilters!

FilterChainWhenwegetinputfromtheweb,itgenerallycomesasstrings,andistheresultofuserinput.Assuch,weoftengetalotofgarbage:extraspaces,unnecessarynewlines,HTMLcharacters,etc.

Whenfilteringsuchinput,wemightwanttoperformseveraloperations:

$value=$request->getParsedBody()['phone']??'';

$value=trim($value);

$value=preg_replace("/[^\n\r]/",'',$value);

$value=preg_replace('/[^\d]/','',$value);

Wethenneedtotestourcodetoensurethatwe'refilteringcorrectly.Additionally,ifatanypointwefailtore-assign,wemaylosethechangeswewereperforming!

Withzend-filter,wecaninsteaduseaFilterChain.Theaboveexamplebecomes:

useZend\Filter\FilterChain;

$filter=newFilterChain();

//attachByNameusestheclassname,minusthenamespace,and

$filter->attachByName('StringTrim');

$filter->attachByName('StripNewlines');

$filter->attachByName('Digits');

$value=$filter->filter($request->getParsedBody()['phone']??'');

Here'sanotherexample:let'ssaywehaveconfigurationkeysthatareinsnake_case_format,andwhichmaybereadfromafile,andwewishtoconvertthosevaluestoCamelCase.

useZend\Filter;

$filter=newFilter\FilterChain();

//attachletsyouprovidetheinstanceyouwishtouse;thiswillwork

//evenwithoutzend-servicemanagerinstalled.

$filter->attach(newFilter\StringTrim());

$filter->attach(newFilter\StripNewlines());//becausewemayhave\rcharacters

$filter->attach(newFilter\Word\UnderscoreToCamelCase());

$configKeys=array_map([$filter,'filter'],explode("\n",$fileContents));

Filterinputusingzend-filter

123

Page 124: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ThisnewexampledemonstratesakeyfeatureofaFilterChain:youcanre-useit!Insteadofhavingtoputthecodefornormalizingthevalueswithinanarray_mapcallback,wecaninsteaddirectlyuseouralreadyconfiguredFilterChain,invokingitonceforeachvalue!

Wrappingupzend-filtercanbeapowerfultoolinyourarsenalfordealingwithuserinput.Pairedwithgoodvalidation,youcanprotectyourapplicationfrommaliciousormalformedinput.

Footnotes

.https://docs.zendframework.com/zend-filter/↩1

Filterinputusingzend-filter

124

Page 125: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Validateinputusingzend-validatorbyMatthewWeierO'Phinney

InourarticleFilterinputusingzend-filter,wecoveredfilteringdata.Thefiltersinzend-filteraregenerallyusedtopre-filterornormalizeincomingdata.Thisisallwellandgood,butwestilldon'tknowifthedataisvalid.That'swherezend-validatorcomesin.

InstallationToinstallzend-validator,useComposer:

$composerrequirezendframework/zend-validator

Likezend-filter,theonlyrequireddependencyiszend-stdlib.However,afewothercomponentsaresuggested,basedonwhichfiltersand/orfeaturesyoumaywanttouse:

zendframework/zend-servicemanagerisusedbytheValidatorPluginManagerandValidatorChaintolookupvalidatorsbytheirshortname(versusfullyqualifiedclassname),aswellastoallowusageofvalidatorswithdependencies.zendframework/zend-dbisusedbyapairofvalidatorsthatcancheckifamatchingrecordexists(ordoesnot!).zendframework/zend-uriisusedbytheUrivalidator.TheCSRFvalidatorrequiresbothzendframework/zend-mathandzendframework/zend-session.zendframework/zend-i18nandzendframework/zend-i18n-resourcescanbeinstalledinordertoprovidetranslationofvalidationerrormessages.

Forourexamples,we'llbeusingtheValidatorChainfunctionalitywithaValidatorPluginManager,sowewillalsowanttoinstallzend-servicemanager:

$composerrequirezendframework/zend-servicemanager

ValidatorInterfaceThecurrentincarnationofzend-validatorisstateful;validationerrormessagesarestoredinthevalidatoritself.Assuch,validatorsmustimplementtheValidatorInterface:

Validateinputusingzend-validator

125

Page 126: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

namespaceZend\Validator;

interfaceValidatorInterface

{

/**

*@parammixed$value

*@returnbool

*/

publicfunctionisValid($value);

/**

*@returnarray

*/

publicfunctiongetMessages();

}

The$valuecanbeliterallyanything;avalidatorexaminesittoseeifitisvalid,andreturnsabooleanresult.Ifitisinvalid,asubsequentcalltogetMessages()shouldreturnanassociativearraywiththekeysbeingmessageidentifiers,andthevaluesthehuman-readablemessagestrings.

Assuch,usagelookslikethefollowing:

if(!$validator->isValid($value)){

//Invalidvalue

echo"Failedvalidation:\n";

foreach($validator->getMessages()as$message){

printf("-%s\n",$message);

}

returnfalse;

}

//Validvalue!

returntrue;

Statelessvalidationsareplanned

Atthetimeofwriting,wehaveproposed anewvalidationcomponenttoworkinparallelwithzend-validator;thisnewcomponentwillimplementastatelessarchitecture.Itsproposedvalidationinterfacewillnolongerreturnaboolean,butratheraValidationResult.Thatinstancewillprovideamethodfordeterminingifthevalidationwassuccessful,encapsulatethevaluethatwasvalidated,and,forinvalidvalues,provideaccesstothevalidationerrormessages.Doingsowillallowbetterre-useofvalidatorswithinthesameexecutionprocess.

Thisproposalalsoincludescodeforadaptingexistingzend-validatorimplementationstoworkwiththestatelessdesign.

1

Validateinputusingzend-validator

126

Page 127: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

zend-validatorprovidesafewdozenfiltersforcommonoperations,includingthingslike:

CommonconditionalslikeLessThan,GreaterThan,Identical,NotEmpty,IsInstanceOf,InArray,andBetween.Stringvalues,suchasStringLength,Regex.Network-relatedvaluessuchasHostname,Ip,Uri,andEmailAddress.BusinessvaluessuchasBarcode,CreditCard,GpsPoint,Iban,andUuid.DateandtimerelatedvaluessuchasDate,DateStep,andTimezone.

Anyofthesevalidatorsmaybeusedbythemselves.

Inmanycases,though,yourvalidationmayberelatedtoasetofvalidations:asanexample,thevaluemustbenon-empty,acertainnumberofcharacters,andfulfillaregularexpression.Likefilters,zend-validatorallowsyoutodothiswithchains.

ValidatorChainUsageofavalidatorchainissimilartofilterchains:attachvalidatorsyouwanttoexecute,andthenpassthevaluetothechain:

useZend\Validator;

$validator=newValidator\ValidatorChain();

$validator->attach(newValidator\NotEmpty());

$validator->attach(newValidator\StringLength(['min'=>6]));

$validator->attach(newValidator\Regex('/^[a-f0-9]{6,12}$/');

if(!$validator->isValid($value)){

//Failedvalidation

var_dump($validator->getMessages());

}

Theaboveusesvalidatorinstances,eliminatingtheneedforValidatorPluginManager,andthusavoidsusageofzend-servicemanager.However,ifwehavezend-servicemanagerinstalled,wecanreplaceusageofattach()withattachByName():

Validateinputusingzend-validator

127

Page 128: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Validator;

$validator=newValidator\ValidatorChain();

$validator->attachByName('NotEmpty');

$validator->attachByName('StringLength',['min'=>6]);

$validator->attachByName('Regex',['pattern'=>'/^[a-f0-9]{6,12}$/']);

if(!$validator->isValid($value)){

//Failedvalidation

var_dump($validator->getMessages());

}

BreakingthechainIfyouweretoruneitheroftheseexampleswith$value='',youmaydiscoversomethingunexpected:you'llgetvalidationerrormessagesforeverysinglevalidator!Thisseemswasteful;there'snoneedtoruntheStringLengthorRegexvalidatorsifthevalueisempty,isthere?

Tosolvethisproblem,whenattachingavalidator,wecantellthechaintobreakexecutionifthegivenvalidatorfails.Thisisdonebypassingabooleanflag:

asthesecondargumenttoattach()asthethirdargumenttoattachByName()(thesecondargumentisanarrayofconstructoroptions)

Let'supdatethesecondexample:

useZend\Validator;

$validator=newValidator\ValidatorChain();

$validator->attachByName('NotEmpty',[],$breakChainOnFailure=true);

$validator->attachByName('StringLength',['min'=>6],true);

$validator->attachByName('Regex',['pattern'=>'/^[a-f0-9]{6,12}$/']);

if(!$validator->isValid($value)){

//Failedvalidation

var_dump($validator->getMessages());

}

Theaboveaddsabooleantrueasthe$breakChainOnFailureargumenttotheattachByName()methodcallsoftheNotEmptyandStringLengthvalidators(wehadtoprovideanemptyarrayofoptionsfortheNotEmptyvalidatorsowecouldpasstheflag).Inthesecases,ifthevaluefailsvalidation,nofurthervalidatorswillbeexecuted.

Validateinputusingzend-validator

128

Page 129: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Thus:

$value=''willresultinasinglevalidationfailuremessage,producedbytheNotEmptyvalidator.$value='test'willresultinasinglevalidationfailuremessage,producedbytheStringLengthvalidator.$value='testthis'willresultinasinglevalidationfailuremessage,producedbytheRegexvalidator.

PrioritizationValidatorsareexecutedinthesameorderinwhichtheyareattachedtothechainbydefault.However,internally,theyarestoredinaPriorityQueue;thisallowsyoutoprovideaspecificorderinwhichtoexecutethevalidators.Highervaluesexecuteearlier,whilelowervalues(includingnegativevalues)executelast.Thedefaultpriorityis1.

Priorityvaluesmaybepassedasthethirdargumenttoattach()andfourthargumenttoattachByName().

Asanexample:

$validator=newValidator\ValidatorChain();

$validator->attachByName('StringLength',['min'=>6],true,1);

$validator->attachByName('Regex',['pattern'=>'/^[a-f0-9]{6,12}$/'],false,-100);

$validator->attachByName('NotEmpty',[],true,100);

Intheabove,whenexecutingthevalidationchain,theorderwillstillbeNotEmpty,followedbyStringLength,followedbyRegex.

Whyprioritize?

Whywouldyouusethisfeature?Themainreasonisifyouwanttodefinevalidationchainsviaconfiguration,andcannotguaranteetheorderinwhichtheitemswillbepresentinconfiguration.Byaddingapriorityvalue,youcanensurethatrecreationofthevalidationchainwillpreservetheexpectedorder.

ContextSometimeswemaywanttovaryhowwevalidateavaluebasedonwhetherornotanotherpieceofdataispresent,orbasedonthatotherpieceofdata'svalue.zend-validatoroffersanunofficialAPIforthat,viaanoptional$contextvalueyoucanpasstoisValid().The

Validateinputusingzend-validator

129

Page 130: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

ValidatorChainacceptsthisvalue,and,ifpresent,willpassittoeachvalidatoritcomposes.

Asanexample,let'ssayyouwanttocaptureanemailaddress(formfield"contact"),butonlyiftheuserhasselectedaradiobuttonallowingyoutodoso(formfield"allow_contact").Wemightwritethatvalidatorasfollows:

useArrayAccess;

useArrayObject;

useZend\Validator\EmailAddress;

useZend\Validator\ValidatorInterface;

classContactEmailValidatorimplementsValidatorInterface

{

constERROR_INVALID_EMAIL='contact-email-invalid';

/**@varstring*/

private$contextVariable;

/**@varEmailAddress*/

private$emailValidator;

/**@varstring[]*/

private$messages=[];

/**@varstring[]*/

private$messageTemplates=[

self::ERROR_INVALID_EMAIL=>'Emailaddress"%s"isinvalid',

];

publicfunction__construct(

EmailAddress$emailValidator=null,

string$contextVariable='allow_contact'

){

$this->emailValidator=$emailValidator?:newEmailAddress();

$this->contextVariable=$contextVariable;

}

publicfunctionisValid($value,$context=null)

{

$this->messages=[];

if(!$this->allowsContact($context)){

//Valuewillbediscarded,soalwaysvalid.

returntrue;

}

if($this->emailValidator->isValid($value)){

returntrue;

}

$this->messages[self::ERROR_INVALID_EMAIL]=sprintf(

$this->messageTemplates[self::ERROR_INVALID_EMAIL],

Validateinputusingzend-validator

130

Page 131: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

var_export($value,true)

);

returnfalse;

}

publicfunctiongetMessages()

{

return$this->messages;

}

privatefunctionallowsContact($context):bool

{

if(!$context||

!(is_array($context)

||$contextinstanceofArrayObject

||$contextinstanceofArrayAccess)

){

returnfalse;

}

$allowsContact=$context[$this->contextVariable]??false;

return(bool)$allowsContact;

}

}

Wewouldthenaddittothevalidatorchain,andcallitlikeso:

$validator->attach(newContactEmailValidator());

if(!$validator->isValid($data['contact'],$data)){

//Failedvalidation!

}

Thisapproachcanallowforsomequitecomplexvalidationroutines,particularlyifyounestvalidationchainswithincustomvalidators!

Registeringyourownvalidators.Ifyouwriteyourownvalidators,chancesareyou'llwanttousethemwiththeValidatorChain.ThisclasscomposesaValidatorPluginManager,whichisapluginmanagerbuiltontopofzend-servicemanager.Assuch,youcanregisteryourvalidatorswithit:

Validateinputusingzend-validator

131

Page 132: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$plugins=$validator->getPluginManager();

$plugins->setFactory(ContactEmailValidator::class,ContactEmailValidatorFactory::class

);

$plugins->setService(ContactEmailValidator::class,$contactEmailValidator);

Alternately,ifusingzend-mvcorExpressive,youcanprovideconfigurationviathevalidatorsconfigurationkey:

return[

'validators'=>[

'factories'=>[

ContactEmailValidator::class=>ContactEmailValidatorFactory::class,

],

],

];

Ifyouwanttousea"shortname"toidentifyyourvalidator,werecommendusinganalias,aliasingtheshortnametothefullyqualifiedclassname.

WrappingupBetweenusingzend-filtertonormalizeandpre-filtervalues,andzend-validatortovalidatethevalues,youcanstartlockingdowntheinputyouruserssubmittoyourapplication.

Thatsaid,whatwe'vedemonstratedsofarishowtoworkwithsinglevalues.Mostformssubmitsetsofvalues;usingtheapproachessofarcanleadtoalotofcode!

Wehaveasolutionforthisaswell,viaourzend-inputfiltercomponent.ReadthearticleValidatedatausingzend-inputfilterformoreinformation.

Footnotes

.https://discourse.zendframework.com/t/rfc-new-validation-component/208/↩1

Validateinputusingzend-validator

132

Page 133: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Validatedatausingzend-inputfilterbyMatthewWeierO'Phinney

InourarticlesFilterinputusingzend-filterandValidateinputusingzend-validator,wecoveredtheusageofzend-filterandzend-validator.Withthesetwocomponents,younowhavethetoolsnecessarytoensureanygivenuserinputisvalid,fulfillingthefirsthalfofthe"filterinput,escapeoutput"mantra.

However,aswediscussedinthezend-validatorarticle,aspowerfulasvalidationchainsare,theyonlyallowyoutovalidateasinglevalueatatime.Howdoyougoaboutvalidatingsetsofvalues—suchasdatasubmittedfromaform,oraresourceforanAPI?

Tosolvethatproblem,ZendFrameworkprovideszend-inputfilter .Aninputfilteraggregatesoneormoreinputs,anyoneofwhichmayalsobeanotherinputfilter,allowingyoutovalidatecomplex,multi-set,andnestedsetvalues.

InstallationToinstallzend-inputfilter,useComposer:

$composerrequirezendframework/zend-inputfilter

zend-inputfilteronlydirectlyrequireszend-filter,zend-stdlib,andzend-validator.Touseitspowerfulfactoryfeature,however,you'llalsoneedzend-servicemanager,asitgreatlysimplifiescreationofinputfilters:

$composerrequirezendframework/zend-servicemanager

TheoryofoperationAninputfiltercomposesoneormoreinputs,anyofwhichmayalsobeaninputfilter(andthusrepresentasetofdatavalues).

Anygiveninputisconsideredrequiredbydefault,butcanbeconfiguredtobeoptional.Whenrequired,aninputwillbeconsideredinvalidifthevalueisnotpresentinthedataset,orisempty.Whenoptional,ifthevalueisnotpresent,orisempty,itisconsideredvalid.An

1

Validatedatausingzend-inputfilter

133

Page 134: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

additionalflag,allow_empty,canbeusedtoallowemptyvaluesforrequiredelements;stillanotherflag,continue_if_empty,willforcevalidationtooccurforeitherrequiredoroptionalvaluesifthevalueispresentbutempty.

Whenvalidatingavalue,twostepsoccur:

Thevalueispassedtoafilterchaininordertonormalizethevalue.Typicalnormalizationsincludestrippingnon-digitcharactersforphonenumbersandcreditcardnumbers;trimmingwhitespace;etc.Thevalueisthenpassedtoavalidatorchaintodetermineifthenormalizedvalueisvalid.

Aninputfilteraggregatestheinputs,aswellasthevaluesthemselves.Youpasstheuserinputtotheinputfilterafterithasbeenconfigured,andthenchecktoseeifitisvalid.Ifitis,youcanpullthenormalizedvaluesfromit(aswellastherawvalues,ifdesired).Ifanyvalueisinvalid,youwouldthenpullthevalidationerrormessagesfromit.

Statelessoperation

Thecurrentapproachisstateful:valuesarepassedtotheinputfilterbeforeyouexecuteitsisValid()method,andthenthevaluesandanyvalidationerrormessagesarestoredwithintheinputfilterinstanceforlaterretrieval.Thiscancauseissuesifyouwishtousethesameinputfiltermultipletimesinthesamerequest.

Forthisreason,weareplanninganew,parallelcomponentthatprovidesstatelessvalidation:callingisValid()willrequirepassingthevalue(s)tovalidate,andbothinputsandinputfiltersalikewillreturnaresultobjectfromthismethodwiththerawandnormalizedvalues,theresultofvalidation,andanyvalidationerrormessages.

GettingstartedLet'sconsideraregistrationformwherewewanttocaptureauseremailandtheirpassword.Inourfirstexample,wewilluseexplicitusage,whichdoesnotrequiretheuseofpluginmanagers.

Validatedatausingzend-inputfilter

134

Page 135: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Filter;

useZend\InputFilter\Input;

useZend\InputFilter\InputFilter;

useZend\Validator;

$email=newInput('email');

$email->getFilterChain()

->attach(newFilter\StringTrim());

$email->getValidatorChain()

->attach(newValidator\EmailAddress());

$password=newInput('password');

$password->getValidatorChain()

->attach(newValidator\StringLength(8),true)

->attach(newValidator\Regex('/[a-z]/'))

->attach(newValidator\Regex('/[A-Z]/'))

->attach(newValidator\Regex('/[0-9]/'))

->attach(newValidator\Regex('/[.!@#$%^&*;:]/'));

$inputFilter=newInputFilter();

$inputFilter->add($email);

$inputFilter->add($password);

$inputFilter->setData($_POST);

if($inputFilter->isValid()){

echo"Theformisvalid\n";

$values=$inputFilter->getValues();

}else{

echo"Theformisnotvalid\n";

foreach($inputFilter->getInvalidInput()as$error){

var_dump($error->getMessages());

}

}

Theabovecreatestwoinputs,oneeachfortheincomingemailaddressandpassword.Theemailaddresswillbetrimmedofwhitespace,andthenvalidated.Thepasswordwillbevalidatedonly,checkingthatwehaveavalueofatleast8characters,withatleastoneeachoflowercase,uppercase,digit,andspecialcharacters.Further,ifanygivencharacterismissing,we'llgetavalidationerrormessagesothattheuserknowshowtocreatetheirpassword.

Eachinputisaddedtoaninputfilterinstance.Wepasstheformdata(viathe$_POSTsuperglobal),andthenchecktoseeifitisvalid.Ifso,wegrabthevaluesfromit(wecangettheoriginalvaluesviagetRawValues()).Ifnot,wegraberrormessagesfromit.

Bydefault,allinputsareconsideredrequired.Let'ssaywealsowantedtocollecttheuser'sfullname,butmakethatoptional.Wecouldcreateaninputlikethefollowing:

Validatedatausingzend-inputfilter

135

Page 136: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

$name=newInput('user_name');

$name->setRequired(false);//OPTIONAL!

$name>getFilterChain()

->attach(newFilter\StringTrim());

InputspecificationsAsnotedinthe"Installation"section,wecanleveragezend-servicemanagerandthevariouspluginmanagerscomposedinitinordertocreateourfilters.

Zend\InputFilter\InputFilterinternallycomposesZend\InputFilter\Factory,whichitselfcomposes:

Zend\InputFilter\InputFilterPluginManager,apluginmanagerformanagingZend\InputFilter\InputandZend\InputFilter\InputFilterinstances.Zend\Filter\FilterPluginManager,apluginmanagerforfilters.Zend\Validator\ValidatorPluginManager,apluginmanagerforvalidators.

Theupshotisthatwecanoftenusespecificationsinsteadofinstancestocreateourinputsandinputfilters.

Assuch,ouraboveexamplescanbewrittenlikethis:

Validatedatausingzend-inputfilter

136

Page 137: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\InputFilter\InputFilter;

$inputFilter=newInputFilter();

$inputFilter->add([

'name'=>'email',

'filters'=>[

['name'=>'StringTrim']

],

'validators'=>[

['name'=>'EmailAddress']

],

]);

$inputFilter->add([

'name'=>'user_name',

'required'=>false,

'filters'=>[

['name'=>'StringTrim']

],

]);

$inputFilter->add([

'name'=>'password',

'validators'=>[

[

'name'=>'StringLength',

'options'=>['min'=>8],

'break_chain_on_failure'=>true,

],

['name'=>'Regex','options'=>['pattern'=>'/[a-z]/'],

['name'=>'Regex','options'=>['pattern'=>'/[A-Z]/'],

['name'=>'Regex','options'=>['pattern'=>'/[0-9]/'],

['name'=>'Regex','options'=>['pattern'=>'/[.!@#$%^&*;:]/'],

],

]);

Thereareanumberofotherfieldsyoucoulduse:

typeallowsyoutospecifytheinputorinputfilterclasstousewhencreatingtheinput.error_messageallowsyoutospecifyasingleerrormessagetoreturnforaninputonvalidationfailure.Thisisoftenusefulasotherwiseyou'llgetanarrayofmessagesforeachinput.allow_emptyandcontinue_if_empty,whichwerediscussedearlier,andcontrolhowvalidationoccurswhenemptyvaluesareencountered.

Whywouldyoudothisinsteadofusingtheprogrammaticinterface,though?

First,thisapproachleveragesthevariouspluginmanagers,whichmeansthatanygiveninput,inputfilter,filter,orvalidatorwillbepulledfromtheirrespectivepluginmanager.Thisallowsyoutoprovideadditionaltypeseasily,but,moreimportantly,overrideexistingtypes.

Validatedatausingzend-inputfilter

137

Page 138: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Second,theconfiguration-basedapproachallowsyoutostorethedefinitionsinconfiguration,andpotentiallyevenoverridethedefinitionsviaconfigurationmerging!Apigilityutilizesthisfeatureheavily,inparttoprovidedifferentinputfiltersbasedonAPIversion.

Managingthepluginmanagers

Toensurethatyoucanusealreadyconfiguredpluginmanagers,youcaninjectthemintotheZend\InputFilter\Factorycomposedinyourinputfilter.Asanexample,consideringthefollowingservicefactoryforaninputfilter:

function(ContainerInterface$container)

{

$filters=$container->get('FilterManager');

$validators=$container->get('ValidatorManager');

$inputFilters=$container->get('InputFilterManager');

$inputFilter=newInputFilter();

$inputFilterFactory=$inputFilter->getFactory();

$inputFilterFactory->setDefaultFilterChain($filters);

$inputFilterFactory->setDefaultValidatorChain($validators);

$inputFilterFactory->setInputFilterManager($inputFilters);

//addinputstothe$inputFilter,andfinallyreturnit...

return$inputFilter;

}

ManagingInputFiltersTheInputFilterPluginManagerallowsyoutodefineinputfilterswithdependencies,whichgivesyoutheabilitytocreatere-usable,complexinputfilters.OnekeyaspecttousingthisfeatureisthattheInputFilterPluginManageralsoensurestheconfiguredfilterandvalidatorpluginmanagersareinjectedinthefactoryusedbytheinputfilter,ensuringanyoverridesorcustomfiltersandvalidatorsyou'vedefinedarepresent.

Tomakethiswork,thebaseInputFilterimplementationalsoimplementsZend\Stdlib\InitializableInterface,whichdefinesaninit()method;theInputFilterPluginManagercallsthisafterinstantiatingyourinputfilterandinjectingitwithafactorycomposingallthevariouspluginmanagerservices.

Whatthismeansisthatifyouusethismethodtoadd()yourinputsandnestedinputfilters,everythingwillbeproperlyconfigured!

Validatedatausingzend-inputfilter

138

Page 139: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Asanexample,let'ssaywehavea"transaction_id"field,andthatweneedtocheckifthattransactionidentifierexistsinthedatabase.Assuch,wemayhaveacustomvalidatorthatdependsonadatabaseconnectiontodothis.Wecouldwriteourinputfilterasfollows:

namespaceMyBusiness;

useZend\InputFilter\InputFilter;

classOrderInputFilterextendsInputFilter

{

publicfunctioninit()

{

$this->add([

'name'=>'transaction_id',

'validators'=>[

['name'=>TransactionIdValidator::class],

],

]);

}

}

Wewouldthenregisterthisinourinput_filtersconfiguration:

//inconfig/autoload/input_filters.global.php

return[

'input_filters'=>[

'invokables'=>[

MyBusiness\OrderInputFilter::class=>MyBusiness\OrderInputFilter::class,

],

],

'validators'=>[

'factories'=>[

MyBusiness\TransactionIdValidator::class=>MyBusiness\TransactionIdValida

torFactory::class,

],

],

];

Thisapproachworksbestwiththespecificationform;otherwiseyouneedtopullthevariouspluginmanagersfromthecomposedfactoryandpassthemtotheindividualinputs:

$transId=newInput();

$transId->getValidatorChain()

->setValidatorManager($this->getFactory()->getValidatorManager());

$transId->getValidatorChain()

->attach(TransactionIdValidator::class);

Validatedatausingzend-inputfilter

139

Page 140: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Specification-driveninputfiltersFinally,wecanlookatspecification-driveninputfilters.

ThecomponentprovidesanInputFilterAbstractServiceFactory.WhenyourequestaninputfilterorinputthatisnotdirectlyintheInputFilterPluginManager,thisabstractfactorywillthenchecktoseeifacorrespondingvalueispresentintheinput_filter_specsconfigurationarray.Ifso,itwillpassthatspecificationtoaZend\InputFilter\Factoryconfiguredwiththevariouspluginmanagersinordertocreatetheinstance.

Usingouroriginalexample,wecoulddefinetheregistrationforminputfilterasfollows:

return[

'input_filter_specs'=>[

'registration_form'=>[

[

'name'=>'email',

'filters'=>[

['name'=>'StringTrim']

],

'validators'=>[

['name'=>'EmailAddress']

],

],

[

'name'=>'user_name',

'required'=>false,

'filters'=>[

['name'=>'StringTrim']

],

],

[

'name'=>'password',

'validators'=>[

[

'name'=>'StringLength',

'options'=>['min'=>8],

'break_chain_on_failure'=>true,

],

['name'=>'Regex','options'=>['pattern'=>'/[a-z]/'],

['name'=>'Regex','options'=>['pattern'=>'/[A-Z]/'],

['name'=>'Regex','options'=>['pattern'=>'/[0-9]/'],

['name'=>'Regex','options'=>['pattern'=>'/[.!@#$%^&*;:]/'],

],

],

],

],

];

Validatedatausingzend-inputfilter

140

Page 141: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Wewouldthenretrieveitfromtheinputfilterpluginmanager:

$inputFilter=$inputFilters->get('registration_form');

Consideringmostinputfiltersdonotneedtocomposedependenciesotherthantheinputsandinputfilterstheyaggregate,thisapproachmakesforadynamicwaytodefineinputvalidation.

Topicsnotcoveredzend-inputfilterhasatonofotherfeaturesaswell:

Inputandinputfiltermerging.Handlingofarrayvalues.Collections(repeateddatasetsofthesamestructure).Filteringoffileuploads.

Ontopofallthis,itprovidesanumberofinterfacesagainstwhichyoucanprograminordertowritecompletelycustomfunctionality!

Onehugestrengthofzend-inputfilteristhatitcanbeusedforanysortofdatasetyouneedtovalidate:forms,obviously,butalsoAPIpayloads,dataretrievedfromamessagequeue,andmore.

Footnotes

.https://docs.zendframework.com/zend-inputfilter↩1

Validatedatausingzend-inputfilter

141

Page 142: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

End-to-endencryptionwithZendFramework3byEnricoZimuel

zend-crypt 3.1.0includesahybridcryptosystem ,afeaturethatcanbeusedtoimplementanend-to-endencryption schemainPHP.

Ahybridcryptosystemisacryptographicmechanismthatusessymmetricencyption(e.g.AES )toencryptamessage,andpublic-keycryptography(e.g.RSA )toprotecttheencryptionkey.Thismethodologyguaranteetwoadvantages:thespeedofasymmetricalgorithmandthesecurityofpublic-keycryptography.

BeforeIpresentthePHPimplementation,let'sexplorethehybridmechanisminmoredetail.Belowisadiagramdemonstratingahybridencryptionschema:

Auser(thesender)wantstosendaprotectedmessagetoanotheruser(thereceiver).He/shegeneratesarandomsessionkey(one-timepad)andusesthiskeywithasymmetricalgorithmtoencryptthemessage(inthefigure,Blockcipherrepresentsanauthenticatedencryption algorithm).Atthesametime,thesenderencryptsthesessionkeyusingthepublickeyofthereceiver.Thisoperationisdoneusingapublic-keyalgorithm,e.g.,RSA.Oncetheencryptionisdone,thesendercansendtheencryptedsessionkeyalongwiththeencryptedmessagetothereceiver.Thereceivercandecryptthesessionkeyusinghis/herprivatekey,andconsequentlydecryptthemessage.

Thisideaofcombiningtogethersymmetricandasymmetric(public-key)encryptioncanbeusedtoimplementend-to-endencryption(E2EE).E2EEisacommunicationsystemthatencryptsmessagesexchangedbytwouserswiththepropertythatonlythetwouserscandecryptthemessage.End-to-endencryptionhasbecomequitepopularinthelastyearsinsoftware,andparticularlymessagingsystems,suchasWhatsApp .Moregenerally,whenyouhavesoftwareusedbymanyusers,end-to-endencryptioncanbeusedtoprotectinformationexchangedbyusers.Onlytheuserscanaccess(decrypt)exchangedinformation;eventheadministratorofthesystemisnotabletoaccessthisdata.

Buildend-to-endencryptioninPHP

1 23

4 5

6

7

End-to-endencryptionwithZendFramework3

142

Page 143: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Wewanttoimplementend-to-endencryptionforawebapplicationwithuserauthentication.Wewillusezend-crypt3.1.0toimplementourcryptographicschemas.ThiscomponentofZendFrameworkusesPHP'sOpenSSLextension foritscryptographicprimitives.

Thefirststepistocreatepublicandprivatekeysforeachusers.Typically,thisstepcanbedonewhentheusercredentialsarecreated.Togenerarethepairsofkeys,wecanuseZend\Crypt\PublicKey\RsaOptions.Belowisanexampledemonstratinghowtogeneratepublicandprivatekeystostoreinthefilesystem:

useZend\Crypt\PublicKey\RsaOptions;

useZend\Crypt\BlockCipher;

$username='alice';

$password='test';//user'spassword

//Generatepublicandprivatekey

$rsaOptions=newRsaOptions();

$rsaOptions->generateKeys([

'private_key_bits'=>2048

]);

$publicKey=$rsaOptions->getPublicKey()->toString();

$privateKey=$rsaOptions->getPrivateKey()->toString();

//storethepublickeyina.pubfile

file_put_contents($username.'.pub',$publicKey);

//encryptandstoretheprivatekeyinafile

$blockCipher=BlockCipher::factory('openssl',array('algo'=>'aes'));

$blockCipher->setKey($password);

file_put_contents($username,$blockCipher->encrypt($privateKey));

Intheaboveexample,wegeneratedaprivatekeyof2048bits.Ifyouarewonderingwhynot4096bits,thisisquestionableanddependsontherealusecase.Forthemajorityofapplications,2048isstillagoodkeysize,atleastuntil2030.Ifyouwantmoresecurityandyoudon'tcareabouttheadditionalCPUtime,youcanincreasethekeysizeto4096.Isuggestreadingthefollowingblogpostsformoreinformationonkeykeysize:

RSAKeySizes:2048or4096bits?:https://danielpocock.com/rsa-key-sizes-2048-or-4096-bitsTheBigDebate,2048vs.4096,Yubico’sPosition:https://www.yubico.com/2015/02/big-debate-2048-4096-yubicos-stand/HTTPSPerformance,2048-bitvs4096-bit:https://blog.nytsoi.net/2015/11/02/nginx-https-performance

8

End-to-endencryptionwithZendFramework3

143

Page 144: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Intheexampleabove,wedidnotgeneratetheprivatekeyusingapassphrase;thisisbecausetheOpenSSLextensionofPHPdoesnotsupportAEAD(AuthenticatedEncryptwithAssociatedData)modeforciphersyet,whichisrequiredinordertousepassphrases.

ThedefaultpassphraseencryptionalgorithmforOpenSSLisdes-ede3-cbc usingPBKDF2 with2048iterationsforgeneratingtheencryptionkeyfromtheuser'spassword.Evenifthisencryptionalgorithmisquitegood,thenumberofiterationsofPBKDF2isnotoptimal;zend-cryptimprovesonthisinavarietyofways,out-of-the-box.Asdemonstratedabove,IuseZend\Crypt\BlockCiphertoencrypttheprivatekey;thisclassprovidesencrypt-then-authenticate usingtheAES-256algorithmforencryptionandHMAC-SHA-256forauthentication.Moreover,BlockCipherusesthePBKDF2 algorithmtoderivatetheencryptionkeyfromtheuser'skey(password).ThedefaultnumberofiterationsforPBKDF2is5000,andyoucanincreaseitusingtheBlockCipher::setKeyIteration()method.

Intheexample,Istoredthepublicandprivatekeysintwofilesnamed,respectively,$username.puband$username.Becausetheprivatefileisencrypted,usingtheuser'spassword,itcanbeaccessonlybytheuser.Thisisaveryimportantaspectforthesecurityoftheentiresystem(wetakeforgrantedthatthewebapplicationstoresthehashesoftheuser'spasswordsusingasecurealgorithmsuchasbcrypt ).

Oncewehavethepublicandprivatekeysfortheusers,wecanstartusingthehybridcryptosystemprovidedbyzend-crypt.Forinstance,imagineAlicewantstosendanencryptedmessagetoBob:

1011

1211

13

End-to-endencryptionwithZendFramework3

144

Page 145: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Crypt\Hybrid;

useZend\Crypt\BlockCipher;

$sender='alice';

$receiver='bob';

$password='test';//bob'spassword

$msg=sprintf('Asecretmessagefrom%s!',$sender);

//encryptthemessageusingthepublickeyofthereceiver

$publicKey=file_get_contents($receiver.'.pub');

$hybrid=newHybrid();

$ciphertext=$hybrid->encrypt($msg,$publicKey);

//sendtheciphertexttothereceiver

//decrypttheprivatekeyofbob

$blockCipher=BlockCipher::factory('openssl',['algo'=>'aes']);

$blockCipher->setKey($password);

$privateKey=$blockCipher->decrypt(file_get_contents($receiver));

$plaintext=$hybrid->decrypt($ciphertext,$privateKey);

printf("%s\n",$msg===$plaintext?"Themessageis:$msg":'Error!');

Theaboveexampledemonstratesencryptinginformationbetweentwousers.Ofcourse,inthiscase,thesender(Alice)knowsthemessagebecauseshewroteit.Moreingeneral,ifweneedtostoreasecretbetweenmultipleusers,weneedtospecifythepublickeystobeusedforencryption.

Thehybridcomponentofzend-cryptsupportsencryptingmessagesformultiplerecipients.Todoso,passanarrayofpublickeysinthe$publicKeyparameterofZend\Crypt\Hybrid::encrypt($data,$publicKey).

Belowdemonstratesencryptingafilefortwousers,AliceandBob.

End-to-endencryptionwithZendFramework3

145

Page 146: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

useZend\Crypt\Hybrid;

useZend\Crypt\BlockCipher;

$data=file_get_contents('path/to/file/to/protect');

$pubKeys=[

'alice'=>file_get_contents('alice.pub'),

'bob'=>file_get_contents('bob.pub')

];

$hybrid=newHybrid();

//Encryptusingthepublickeysofbothaliceandbob

$ciphertext=$hybrid->encrypt($data,$pubKeys);

file_put_contents('file.enc',$ciphertext);

$blockCipher=BlockCipher::factory('openssl',['algo'=>'aes']);

$passwords=[

'alice'=>'passwordofAlice',

'bob'=>'passwordofBob'

];

//decryptusingtheprivatekeysofaliceandbob,oneattime

foreach($passwordsas$id=>$pass){

$blockCipher->setKey($pass);

$privateKey=$blockCipher->decrypt(file_get_contents($id));

$plaintext=$hybrid->decrypt($ciphertext,$privateKey,null,$id);

printf("%sfor%s\n",$data===$plaintext?'Decryptionok':'Error',$id);

}

Fordecryption,Iusedahardcodedpasswordfortheusers.Usually,theuser'spasswordisprovidedduringtheloginprocessofawebapplicationandshouldnotbestoredaspermanentdata;forinstance,theuser'spasswordcanbesavedinaPHPsessionvariablefortemporaryusage.Ifyouusesessionstosavetheuser'spassword,ensurethatdataisprotected;thePHP-Secure-Session libraryortheSuhosin PHPextensionwillhelpyoudoso.

Todecryptthefile,IusedtheZend\Crypt\Hybrid::decrypt()method,whereIspecifiedthe$privateKey,anullpassphrase,andfinallythe$idoftheprivateKey.Thisparametersarenecessarytofindthecorrectkeytouseintheheaderoftheencryptedmessage.

Footnotes

.https://github.com/zendframework/zend-crypt↩

.https://docs.zendframework.com/zend-crypt/hybrid/↩

14 15

1

2

3

End-to-endencryptionwithZendFramework3

146

Page 147: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

.https://en.wikipedia.org/wiki/End-to-end_encryption↩

.https://en.wikipedia.org/wiki/Advanced_Encryption_Standard↩

.https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29↩

.https://en.wikipedia.org/wiki/Authenticated_encryption↩

.https://www.whatsapp.com/faq/en/general/28030015↩

.http://php.net/manual/en/book.openssl.php↩

.https://wiki.php.net/rfc/openssl_aead↩

.https://en.wikipedia.org/wiki/Triple_DES↩

.https://en.wikipedia.org/wiki/PBKDF2↩

.http://www.daemonology.net/blog/2009-06-24-encrypt-then-mac.html↩

.https://en.wikipedia.org/wiki/Bcrypt↩

.https://github.com/ezimuel/PHP-Secure-Session↩

.https://suhosin.org↩

3

4

5

6

7

8

9

10

11

12

13

14

15

End-to-endencryptionwithZendFramework3

147

Page 148: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

CreateZPKstheEasyWaybyEnricoZimuel

ZendServer providestheabilitytodeployapplicationstoasingleserverorclusterofserversviatheZPK packageformat.Weofferthepackagezfcampus/zf-deploy forcreatingZPKpackagesfromZendFrameworkandApigilityapplications,buthowcanyoucreatetheseforExpressive,or,really,anyPHPapplication?

RequirementsTocreatetheZPK,youneedafewthings:

Thezipbinary.ZPKsareZIPfileswithspecificartifacts.Thecomposerbinary,soyoucaninstalldependencies.An.htaccessfile,ifyourZendServerinstallationisusingApache.Adeployment.xmlfile.

htaccessIfyouareusingApache,you'llwanttomakesurethatyousetupthingslikerewriterulesforyourapplication.WhilethiscanbedonewhendefiningthevhostintheZendServeradminUI,usingan.htaccessfilemakesiteasiertomakechangestotherulesbetweendeployments.

Thefollowing.htaccessfilewillworkformany(most?)PHPprojects.Placeitrelativetoyourproject'sfrontcontrollerscript;inthecaseofExpressive,ZendFramework,andApigility,thatwouldmeanpublic/index.php,andthuspublic/.htaccess:

12 3

CreateZPKstheEasyWay

148

Page 149: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

RewriteEngineOn

#ThefollowingruletellsApachethatiftherequestedfilename

#exists,simplyserveit.

RewriteCond%{REQUEST_FILENAME}-s[OR]

RewriteCond%{REQUEST_FILENAME}-l[OR]

RewriteCond%{REQUEST_FILENAME}-d

RewriteRule^.*$-[NC,L]

#Thefollowingrewritesallotherqueriestoindex.php.The

#conditionensuresthatifyouareusingApachealiasestodo

#massvirtualhosting,thebasepathwillbeprependedto

#allowproperresolutionoftheindex.phpfile;itwillwork

#innon-aliasedenvironmentsaswell,providingasafe,one-size

#fitsallsolution.

RewriteCond%{REQUEST_URI}::$1^(/.+)(.+)::\2$

RewriteRule^(.*)-[E=BASE:%1]

RewriteRule^(.*)$%{ENV:BASE}index.php[NC,L]

deployment.xmlThedeployment.xmltellsZendServerabouttheapplicationyouaredeploying.WhatislistedbelowwillworkforExpressive,ZendFramework,andApigilityapplications,andlikelyanumberofotherPHPapplications.Themainthingstopayattentiontoare:

Thenameshouldtypicallymatchtheapplicationnameyou'vesetupinZendServer.Theversion.releasevalueshouldbeupdatedforeachrelease;thisallowsyoutouserollbackfeatures.Theappdirvalueistheprojectroot.Anemptyvalueindicatesthesamedirectoryasthedeployment.xmllivesin.Thedocrootvalueisthedirectoryfromwhichthevhostwillservefiles.

So,asanexample:

<?xmlversion="1.0"encoding="utf-8"?>

<packageversion="2.0"xmlns="http://www.zend.com/server/deployment-descriptor/1.0">

<type>application</type>

<name>API</name>

<summary>APIforallthethings!</summary>

<version>

<release>1.0</release>

</version>

<appdir></appdir>

<docroot>public</docroot>

</package>

CreateZPKstheEasyWay

149

Page 150: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

InstallingdependenciesWhenyou'rereadytobuildapackage,youshouldinstallyourdependencies.However,don'tinstallthemanyoldway;installtheminaproduction-readyway.Thismeans:

Specifyingthatcomposeroptimizetheautoloader(--optimize-autoloader).Useproductiondependenciesonly(--no-dev).Preferdistributionpackages(versussourceinstalls)(--prefer-dist).

So:

$composerinstall--no-dev--prefer-dist--optimize-autoloader

CreatetheZPKFinally,wecannowcreatetheZPK,usingthezipcommand:

$zip-rapi-1.0.0.zpk.-xapi-1.0.0.zpk-x'*.git/*'

Thiscreatesthefileapi-1.0.0.zpkwithallcontentsofthecurrentdirectoryminusthe.gitdirectoryandtheZPKitself(theseareexcludedviathe-xflags).(Youmaywant/needtospecifyadditionalexclusions;theabovearetypical,however.)

YoucanthenuploadtheZPKtothewebinterface,orusetheZendServerSDK .

Simpleexample:single-directoryPoCLet'ssayyouwanttodoaproof-of-concept,andwillbecreatinganindex.phpintheprojectroottotestoutanidea.Youwouldusetheabove.htaccess,butkeepitintheprojectroot.Yourdeployment.xmlwouldlookthesame,exceptthatthedocrootvaluewouldbeempty:

4

CreateZPKstheEasyWay

150

Page 151: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

<?xmlversion="1.0"encoding="utf-8"?>

<packageversion="2.0"xmlns="http://www.zend.com/server/deployment-descriptor/1.0">

<type>application</type>

<name>POC</name>

<summary>Proof-of-conceptofaverycoolidea</summary>

<version>

<release>0.1.0</release>

</version>

<appdir></appdir>

<docroot></docroot>

</package>

You'dthenrun:

$zip-rpoc-0.1.0.zpk.-xpoc-0.1.0.zpk

Done!

FinZPKsmakecreatingandstagingdeploymentpackagesfairlyeasy—onceyouknowhowtocreatethepackages.WehopethatthisposthelpsdemystifythefirststepsincreatingaZPKforyourapplication.

VisittheZendServerdocumentation formoreinformationonZPKstructure.

Footnotes

.http://www.zend.com/en/products/zend_server↩

.http://files.zend.com/help/Zend-Server/content/application_package.htm↩

.https://github.com/zfcampus/zf-deploy↩

.https://github.com/zend-patterns/ZendServerSDK↩

.http://files.zend.com/help/Zend-Server/content/understanding_the_application_package_structure.htm↩

5

1

2

3

4

5

CreateZPKstheEasyWay

151

Page 152: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

UsingLaravelHomesteadwithZendFrameworkProjectsbyEnricoZimuel

LaravelHomestead isaninterestingprojectbytheLaravelcommunitythatprovidesaVagrant boxforPHPdevelopers.ItincludesafullsetofservicesforPHPdevelopers,suchastheNginxwebserver,PHP7.1,MySQL,Postgres,Redis,Memcached,Node,andmore.

Onethemostinterestingfeaturesofthisprojectistheabilitytoenableitperproject.ThismeansyoucanrunavagrantboxforyourspecificPHPproject.

Inthisarticle,we'llexamineusingitforZendFrameworkMVC,Expressive,andApigilityprojects.Ineachcase,installationandusageisexactlythesame.

InstalltheVagrantboxThefirststepistoinstallthelaravel/homestead vagrantbox.Thisboxworkswithavarietyofproviders:VirtualBox5.1 ,VMWare ,orParallels .

WeusedVirtualBoxandthefollowingcommandtoinstallthelaravel/homesteadbox:

$vagrantboxaddlaravel/homestead

Theboxis981MB,soitwilltakesomeminutestodownload.

Homestead,bydefault,usesthehostnamehomestead.app,andrequiresthatyouupdateyoursystemhostsfiletopointthatdomaintothevirtualmachineIPaddress.Tofaciliatethat,Homesteadprovidesintegrationwiththevagrant-hostsupdater Vagrantplugin.Werecommendinstallingthatbeforeyourinitialrunofthevirtualmachine:

$vagrantplugininstallvagrant-hostsupdater

UseHomesteadinZFprojectsOnceyouhaveinstalledthelaravel/homesteadvagrantbox,youcanuseitgloballyorperproject.

12

34 5 6

7

UsingLaravelHomesteadwithZendFrameworkProjects

152

Page 153: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

IfweinstallHomesteadper-project,wewillhaveafulldevelopmentserverconfigureddirectlyinthelocalfolder,withoutsharingserviceswithotherprojects.Thisisabigplus!

TouseHomesteadper-project,weneedtoinstallthelaravel/homestead packagewithinourZendFramework,Apigility,orExpressiveproject.ThiscanbedoneusingComposer withthefollowingcommand:

$composerrequire--devlaravel/homestead

Afterinstallation,executethehomesteadcommandtobuildtheVagrantfile:

$vendor/bin/homesteadmake

ThiscommandcreatesboththeVagrantFileandaHomestead.yamlconfigurationfile.

ConfiguringHomesteadBydefault,thevagrantboxissetupataddress192.168.10.10withthehostnamehomestead.app.YoucanchangetheIPaddressinHomestead.yamlifyouwant,aswellasthehostname(viathesites[].mapkey).

TheHomestead.yamlconfigurationfilecontainsalldetailsaboutthevagrantboxconfiguration.Thefollowingisanexample:

89

UsingLaravelHomesteadwithZendFrameworkProjects

153

Page 154: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

---

ip:"192.168.10.10"

memory:2048

cpus:1

hostname:expressive-homestead

name:expressive-homestead

provider:virtualbox

authorize:~/.ssh/id_rsa.pub

keys:

-~/.ssh/id_rsa

folders:

-map:"/home/enrico/expressive-homestead"

to:"/home/vagrant/expressive-homestead"

sites:

-map:homestead.app

to:"/home/vagrant/expressive-homestead/public"

databases:

-homestead

Thisconfigurationfileisverysimpleandintuitive;forinstance,thefolderstobeusedarereportedinthefolderssection;themapvalueisthelocalfolderoftheproject,thetovalueisthefolderonthevirtualmachine.

IfyouwanttoaddorchangemorefeaturesinthevirtualmachineyoucanusedtheHomestead.yamlconfigurationfile.Forinstance,ifyouprefertoaddMariaDBinsteadofMySQL,youneedtoaddthemariadboption:

ip:"192.168.10.10"

memory:2048

cpus:1

hostname:expressive-homestead

name:expressive-homestead

provider:virtualbox

mariadb:true

ThisoptionwillremoveMySQLandinstallMariaDB.

UsingLaravelHomesteadwithZendFrameworkProjects

154

Page 155: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

SSHkeysmanagedbyGPG

Oneofourteamusesthegpg-agentasanssh-agent,whichcausedsomeconfigurationproblemsinitially,asthe~/.ssh/id_rsaandits.pubsiblingwerenotpresent.

Whenusinggpg-agentforservingSSHkeys,youcanexportthekeyusingssh-add-L.Thismaylistseveralkeys,butyoushouldbeabletofindthecorrectone.Copyittothefile~/.ssh/gpg_key.pub,andthencopythatfileto~/.ssh/gpg_key.pub.pub.UpdatetheHomestead.yamlfiletoreflectthesenewfiles:

authorize:~/.ssh/gpg_key.pub.pub

keys:

-~/.ssh/gpg_key.pub

Thegpg-agentwilltakecareofsendingtheappropriatekeyfromthere.

RunningHomesteadTorunthevagrantbox,executethefollowingwithinyourprojectroot:

$vagrantup

Ifyouopenabrowsertohttp://homestead.appyoushouldnowseeyourapplicationrunning.

Manuallymanagingyourhostsfile

Ifyouchosenottousevagrant-hostsupdater,youwillneedtoupdateyoursystemhostsfile.

OnLinuxandMac,updatethe/etc/hostsfiletoaddthefollowingline:

192.168.10.10homestead.app

OnWindows,thehostfileislocatedinC:\Windows\System32\drivers\etc\hosts.

Moreinformation

UsingLaravelHomesteadwithZendFrameworkProjects

155

Page 156: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

We'vetestedthissetupwitheachoftheZendFrameworkzend-mvcskeletonapplication,Apigility,andExpressive,andfoundthesetup"justworked"!Wefeelitprovidesexcellentflexibilityinsettingupdevelopmentenvironments,givingdevelopersawiderangeoftoolsandtechnologiestoworkwithastheydevelopapplications.

FormoreinformationaboutLaravelHomestead,visittheofficialdocumentation oftheproject.

Footnotes

.https://laravel.com/docs/5.4/homestead↩

.https://www.vagrantup.com/↩

.https://atlas.hashicorp.com/laravel/boxes/homestead↩

.https://www.virtualbox.org/wiki/Downloads↩

.https://www.vmware.com/↩

.http://www.parallels.com/products/desktop/↩

.https://github.com/cogitatio/vagrant-hostsupdater↩

.https://github.com/laravel/homestead↩

.https://getcomposer.org/↩

.https://laravel.com/docs/5.4/homestead↩

10

1

2

3

4

5

6

7

8

9

10

UsingLaravelHomesteadwithZendFrameworkProjects

156

Page 157: Zend Framework 3 Cookbook - roguewave.com · Zend Framework 3 Cookbook During the year 2017, Matthew Weier O'Phinney and Enrico Zimuel started a series of blog posts on the offical

Copyrightnote

RogueWavehelpsthousandsofglobalenterprisecustomerstacklethehardestandmostcomplexissuesinbuilding,connecting,andsecuringapplications.Since1989,ourplatforms,tools,components,andsupporthavebeenusedacrossfinancialservices,technology,healthcare,government,entertainment,andmanufacturing,todelivervalueandreducerisk.FromAPImanagement,webandmobile,embeddableanalytics,staticanddynamicanalysistoopensourcesupport,wehavethesoftwareessentialstoinnovatewithconfidence.

https://www.roguewave.com/

©2017RogueWaveSoftware,Inc.Allrightsreserved

Copyrightnote

157