Quick-and-dirty introduction to using Zend\Navigation in Zend Framework 2.
(1) Add Service Manager Factory
Add the following to your module configuration file (config/module.config.php) or place it in an autoloadable file in your application’s config/autoload folder:
This tells ZF2′s Serivce Manager to load the default Zend\Navigation instance factory. This factory will read a page tree from the application configuration and register a new instance of the navigation data container (Zend\Navigation\Navigation) with the service manager under the name Navigation.
(2) Configure Sitemap
For each module in your application you will need to construct a sitemap for the routes exposed by that module. As an example, here is one that I use with ZfcUser in my own application:
<?phpreturnarray(// All navigation-related configuration is collected in the 'navigation' key'navigation'=>array(// The DefaultNavigationFactory we configured in (1) uses 'default' as the sitemap key'default'=>array(// And finally, here is where we define our page hierarchy'account'=>array('label'=>'Account','route'=>'zfcuser','pages'=>array('home'=>array('label'=>'Dashboard','route'=>'zfcuser',),'login'=>array('label'=>'Sign In','route'=>'zfcuser/login',),'logout'=>array('label'=>'Sign Out','route'=>'zfcuser/logout',),'register'=>array('label'=>'Register','route'=>'zfcuser/register',),),),),),);
(3) Using the View Helpers
Now that you’ve set up the necessary configuration, all that’s left to do is use it! There are a number of bundled view helpers which you can use to inject navigation components into your views.
Example: Breadcrumbs
To render breadcrumbs add the following line to one of your views (I put it in my application’s layout view):
This view helper also provides additional methods for altering the output. For example, to display only the active branch of the menu, append the call setOnlyActiveBranch(true) like so:
Two weeks ago I oversaw the launch of our new public web site. The whole exercise was an unparalleled failure; as soon as the first peak load hit the server took a prolonged lie-down, and so the site was generally unreachable for end users. What went wrong? I didn’t adequately load test the application to ensure our production environment could cope before rolling it out.
I’ve learned much from that mistake, and regular load testing will now be part of our regular release process. On top of using ab and siege to pummel our staging environment before the code is promoted to production status, I wanted to add an extra comfort layer: replaying a sample of traffic to our production environment against staging. After much searching, I didn’t find anything that really fit the bill perfectly. So, I hacked together a simple Node.js-based application to do it: nodejs-logreplay
2012/10/03: With recent updates to Battlelog, this code no longer functions
Why?
I enjoy first-person shooters. I especially love Battlefield 3, and I enjoy it most when playing online with my friends. After a couple of failed gaming sessions where I signed on just as they were signing off, I’d had enough; there had to be a better way…so late one night Battlelog Notifier was born.
What?
Battlelog Notifier is a simple server script which scrapes my Battlelog periodically to find out the status of my friends and sends a DM to my Twitter account whenever it detects that one or more of them has started playing. No more missing out on the action!
How?
Thanks to the awesomesauce that is Zend Framework, it’s quite easy, really. All I have to do is harness Zend_Http_Client to send a POST request to the login page:
12345678910111213
<?php// Log into battlelogrequire_once"Zend/Http/Client.php";$c=newZend_Http_Client();$r=$c->setCookieJar()->setUri('https://battlelog.battlefield.com/bf3/gate/login/')->setParameterPost('redirect','')->setParameterPost('email',YOUR_BATTLELOG_EMAIL)->setParameterPost('password',YOUR_BATTLELOG_PASSWORD)->setParameterPost('remember','1')->setParameterPost('submit','Sign In')->request('POST');$contents=$r->getBody();
As it turns out, Battlelog’s page source contains beautiful hunks of JSON that describe pretty much the entire state of the page at the time it was generated. So I do some hacked-together regex magic:
12345678910111213141516171819202122232425262728
<?php// Hunt down and chop out the correct JSON bitif(preg_match_all('{<script type="text/javascript">([^<]+)</script>}s',$contents,$Matches)){foreach($Matches[1]as$K=>$ScriptContents){// The JSON we are looking for has a username key...if(preg_match('{"username":}s',$ScriptContents)){// ... and resides inside a call to feed.likeitems.surface_2_2.renderif(preg_match('{feed\.likeitems\.surface_2_2\.render\(([^)]*)\)}s',$ScriptContents,$FunctionMatches)){// ... and is, of course, delimited by curly bracesif(preg_match('{\{.*\}}s',$FunctionMatches[1],$JSONMatches)){// Decode the captured JSON bit$Data=Zend_Json::decode($JSONMatches[0]);// If it's an array...if(is_array($Data)){// ...we now have ALL THE THINGS!}}}}}}
Since we now have everything we need to know from battlelog, we can use it. $LastUserInfo is $Data[‘users’] from the previous script run, so that we can tell if someone has sign in/out or started/stopped playing.
Now all that is left to do is send the DM using Zend_Oauth and Zend_Service_Twitter:
12345678910111213141516171819
<?php$token=newZend_Oauth_Token_Access;$token->setParams(array('oauth_token'=>YOUR_OAUTH_TOKEN,'oauth_token_secret'=>YOUR_OAUTH_SECRET));$twitter=newZend_Service_Twitter(array('consumerKey'=>YOUR_TWITTER_CONSUMER_KEY,'consumerSecret'=>YOUR_TWITTER_CONSUMER_SECRET,'accessToken'=>$token));$msg='';if(count($Started)>0)$msg.=implode(", ",$Started).(count($Started)>1?' have':' has').' started playing BF3. ';if(count($Stopped)>0)$msg.=implode(", ",$Stopped).(count($Stopped)>1?' have':' has').' stopped playing BF3.';if(!empty($msg))$twitter->directMessage->new(YOUR_TWITTER_ID,$msg);
And the end result is a nice notification on Twitter:
I’ve been hacking away sporadically at packaging PHPUnit 3.4.x as a PHAR (see: ZF-11828 and related issues for context of why). I’ve put the code for my first-stab attempt up on my GitHub, but it doesn’t work properly.
(EDIT: Yes, I know PHPUnit provides a PHAR generator. See footnote at bottom (here) as to why I didn’t use it)
I’m running into a weird issue with the include path and including classes from within the PHAR stub. Here’s a quick (streamlined) snippet of what i’m doing in the stub (full src is in build/3.4/build_phar.php:
Essentially, this just prepends the PHAR to the include path, and then fetches the class PHPUnit_Util_Filter. When I build the PHAR and run it, the file phar://phpunit34.phar/PHPUnit/Util/Filter.php is included properly. The difficulty appears when the require_once statements are processed for that script:
PHP, as expected, loads file phar://phpunit34.phar/PHPUnit/Util/FilterIterator.php, and processes it’s require_once statements:
1
require_once 'PHPUnit/Util/Filter.php';</pre>
It dies at this point with the following error:
123456
PHP Fatal error: Cannot redeclare class PHPUnit_Util_Filter in /usr/local/lib/php/PHPUnit/Util/Filter.php on line 59PHP Stack trace:PHP 1. {main}() /path/to/phpunit34.phar:0PHP 2. require_once() /path/to/phpunit34.phar:6PHP 3. require_once() phar:///path/to/phpunit34.phar/PHPUnit/Util/Filter.php:47PHP 4. require_once() phar:///path/to/phpunit34.phar/PHPUnit/Util/FilterIterator.php:46
The second time around PHPUnit/Util/Filter.php is not detected as having already been included, and PHP skips the PHAR entry in the include path and keeps going until it encounters my PEAR install, pulls the file from there, and voila we get a redeclaration fatal error.
Stumped.
PHP Version:
1234
PHP 5.3.8 (cli) (built: Nov 22 2011 14:11:11)Copyright (c) 1997-2011 The PHP GroupZend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies with Xdebug v2.1.2, Copyright (c) 2002-2011, by Derick Rethans
NOTE: this server was running v5.3.3 prior to today, and the same issue occurred then as well.
Phar Section of phpinfo()
12345678910
Phar: PHP Archive support => enabledPhar EXT version => 2.0.1Phar API version => 1.1.1SVN revision => $Revision: 314419 $Phar-based phar archives => enabledTar-based phar archives => enabledZIP-based phar archives => enabledgzip compression => enabledbzip2 compression => disabled (install pecl/bz2)Native OpenSSL support => enabled
NOTE: this server was using the built-in PHAR in PHP v5.3.x prior to today, and the same issue occurred then as well.
Why I didn’t use PHPUnit’s provided PHAR script
Mostly just because that’s how I roll…but also, after I build the PHAR using the provided script, I can only use it from the location that the script spits it out info (root of git clone). If I try to execute it from elsewhere, or even copy the PHAR elsewhere (ie: to ZF tests directory), it fails with this error:
12345
PHP Fatal error: Class 'PHPUnit_Runner_BaseTestRunner' not found in /usr/local/lib/php/PHPUnit/TextUI/TestRunner.php on line 60PHP Stack trace:PHP 1. {main}() /path/to/zfdev/trunk/tests/phpunit.phar:0PHP 2. require() /path/to/zfdev/trunk/tests/phpunit.phar:4PHP 3. require_once() phar:///path/to/zfdev/trunk/tests/phpunit.phar/TextUI/Command.php:46</pre>
Here, PHPUnit_TextUI_Command is pulled from the PHAR, but the files it includes are not. Since I have PHPUnit v3.6 installed from PEAR, PHPUnit_TextUI_TestRunner is pulled from there, but as PHPUnit has changed significantly between 3.4 and 3.6, the class PHPUnit_Runner_BaseTestRunner no longer exists.
After much (MUCH) head->desk while trying to make Symantec Ghost Solution Suite’s WinPE boot disc pick up a network connection on the new HP ProBook 6560b, I’ve sorted the issue! Thanks, in no small part, to this thread: http://communities.intel.com/thread/21719
If you already have a GSS WinPE boot CD you can simply boot into it, and follow these instructions:
Using 7-Zip (or similar program) decompress the EXE to a folder on your hard drive
Copy the extracted folder to a USB drive
Plug the USB drive into the laptop which you’ve booted into WinPE
From the command prompt, navigate to the folder: E:\PROWin32\PRO1000\Win32\NDIS61 (your USB drive may have a different letter)
Run this command: drvload e1c6032.inf
Voila, you should now have wired network access!
Of course, you could also create a new WinPE image when using the Symantec Ghost Boot Wizard and include that NDIS61 folder directly in WinPE. That would eliminate the need to use drvload after booting into WinPE.
Earlier this week the third development snapshot of ZF2 - Zend Framework 2.0.0dev3 - was released, and it included our first official look at the new Dependency Injection component: Zend\Di. I’ve spent some time playing with it, and am quite impressed thus far.
To kick the tires, I created a simple Zend_Application-based ZF1 application (you know, zf create project and all that) which integrates Zend\Di to provide dependency injection to the action controllers in the project. The source code is available on my GitHub (here), and requires that you have ZF v1.11.x on your include path and have downloaded the dev3 snapshot of ZF2.
For the better part of this past year, my job has consisted mainly of building (or rebuilding) a number of web portals. These portals all have two things in common: (1) they are built upon Zend Framework, and (2) they need to interface with the Desire2Learn Learning Environment. The result of this confluence of needs is zfD2L.
What is zfD2L?
It is an object-oriented API for interacting with Desire2Learn Web Services from PHP5 web sites. Built in PHP5, and leveraging a number of components of the Zend Framework, zfD2L enables you to automate many of the common tasks necessary to integrate external PHP applications with Desire2Learn, such as:
Create, Update, and Delete User Accounts
Perform seamless single-signon into Desire2Learn
Create, Update, and Delete Organizational Units
Enroll/Unenroll user accounts into/out of organization units
Much, much more…eventually…
Is it ready to use now?
Yes, and no. It is still under heavy development, much of the functionality exposed by the Desire2Learn Web Services platform has not yet been implemented into zfD2L, and what currently exists in zfD2L has not been widely deployed and tested. In short: if you want to use it, go right ahead, but do so at your own risk.
Yes please! If you are a PHP developer and you have access to a Desire2Learn instance, you have everything you need to get started! Simply fork our project into your own GitHub account (they are free) and get crackin’. Once you’ve implemented something you want to contribute back, just open a pull request from the project’s GitHub “pulls” page (here) and we’ll take a look. I might even send you a bit of CDLI swag for your trouble ;)
Even if you can’t provide direct assistance with the code, there are still plenty of ways you can help; Writing documentation and examples pages for the wiki, reporting bugs/issues/quirks you’ve encountered while using zfD2L back to us, or even helping reproduce issues already reported to the issue tracker are all great ways to help out
Did you know that action controllers in Zend Framework can be nested (ie: placed in sub-directories within the controllers directory)? Neither did I! This undocumented feature was pointed out to me during a recent conversation with Ryan Mauger (bittarman) and Matthew Weier O’Phinney (weierophinney) on the #zftalk.dev IRC channel. Needless to say, this made my day!
However, they also pointed out that the default router does not handle routing to these nested controllers in a “pretty” fashion. For example, a nested controller Admin_Page_SubPageController.php (modules/admin/controllers/Page/SubPageController.php) would be accessed via the URI admin/page_sub-page/:action. This could be avoided by creating a custom route for each nested controller, and Ryan kindly provided an example in which he hand-coded a route for each nested controller in a bootstrap method. This approach, however, will add a lot of bulk to the bootstrap if you have many nested controllers. The solution: automate it!
I took Ryan’s example, mixed in Matthew’s backported ZF2 classmap-based autoloader, and created a bootstrap resource plugin which uses classmaps to automatically generate the necessary routes for you. I’ve posted the result, along with a sample application showing how to use it, on github:
The extras package in Zend Framework contains some pretty nifty components for working with both jQuery and jQuery UI in your Zend Framework-based applications. Â One thing I had to learn through trial and error is how to get the datePicker view helper to mesh with array field names. For example, if I wish to build a compound form element for selecting a date and time, I would create a new decorator (ie: extend Zend_Form_Decorator_Abstract) and do something like this in the render() method:
The issue with this is that $(“#OriginSiteDeparture[Date]”) is not a valid selector, due to the square brackets (which are necessary to group the values of all the sub-elements making up the compound element as an array so they are passed to the compound element as a group when the form is validated). The documentation for the jQuery form helpers (here) fails to mention this, but there is a quick and easy fix. The datePicker form helper function:
1
datePicker($id, $value, $params, $attribs);
takes a fourth argument, $attribs. Through this argument (an array) you can override the id attribute of the text box which forms the core of the jQuery Date Picker widget: