Adam.Lundrigan.ca

Quick-and-dirty ZF2: Zend\Navigation

| Comments

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:

1
2
3
4
5
'service_manager' => array(
    'factories' => array(
        'Navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
    ),
);

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:

config/autoload/nav_zfcuser.global.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
return array(
    // 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):

1
<?php echo $this->navigation('Navigation')->breadcrumbs(); ?>

Example: Menu

To render a sidebar navigation menu, add the following line to the location in your view script where you want the menu rendered:

1
<?php echo $this->navigation('Navigation')->menu(); ?>

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:

1
<?php echo $this->navigation('Navigation')->menu()->setOnlyActiveBranch(true); ?>

Load Testing by Replaying Apache HTTPD Access Logs

| Comments

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

Get the source code from GitHub

Spying on Your BF3 BattleLog Friends Made Easy With PHP + Zend Framework

| Comments

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// Log into battlelog
require_once "Zend/Http/Client.php";
$c = new Zend_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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
// Hunt down and chop out the correct JSON bit
if ( 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.render
            if ( preg_match('{feed\.likeitems\.surface_2_2\.render\(([^)]*)\)}s', $ScriptContents, $FunctionMatches) )
            {
                // ... and is, of course, delimited by curly braces
                if ( 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$Started = array();
$Stopped = array();
foreach ( $Data['users'] as $UID=>$UserInfo )
{
    if ( $UserInfo['presence']['isPlaying'] && !@$LastUserInfo[$UID]['presence']['isPlaying'] )
    {
        $Started[] = $UserInfo['username'];
    }
    elseif ( !$UserInfo['presence']['isPlaying'] && @$LastUserInfo[$UID]['presence']['isPlaying'] )
    {
        $Stopped[] = $UserInfo['username'];
    }
}

Now all that is left to do is send the DM using Zend_Oauth and Zend_Service_Twitter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$token = new Zend_Oauth_Token_Access;
$token->setParams(array(
    'oauth_token' => YOUR_OAUTH_TOKEN,
    'oauth_token_secret' => YOUR_OAUTH_SECRET
));
$twitter = new Zend_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:

Example Tweet

BattlelogNotifier on GitHub: https://github.com/adamlundrigan/BattlelogNotifier

Strange Problems When Packaging PHPUnit 3.4 as a PHAR

| Comments

NOTE: This effort on my part is long-since abandoned.
Click here for a way to install multiple PHPUnit versions side-by-side.

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:

build/3.4/build_phar.php
1
2
3
4
5
<?php
Phar::mapPhar('phpunit34.phar');
set_include_path(__FILE__ . PATH_SEPARATOR . get_include_path());
require_once "phar://phpunit34.phar/PHPUnit/Util/Filter.php";
__HALT_COMPILER();

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:

1
2
require_once 'PHPUnit/Framework/Exception.php';
require_once 'PHPUnit/Util/FilterIterator.php';

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:

1
2
3
4
5
6
PHP Fatal error:  Cannot redeclare class PHPUnit_Util_Filter in /usr/local/lib/php/PHPUnit/Util/Filter.php on line 59
PHP Stack trace:
PHP   1. {main}() /path/to/phpunit34.phar:0
PHP   2. require_once() /path/to/phpunit34.phar:6
PHP   3. require_once() phar:///path/to/phpunit34.phar/PHPUnit/Util/Filter.php:47
PHP   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:

1
2
3
4
PHP 5.3.8 (cli) (built: Nov 22 2011 14:11:11)
Copyright (c) 1997-2011 The PHP Group
Zend 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()

1
2
3
4
5
6
7
8
9
10
Phar: PHP Archive support =&gt; enabled
Phar EXT version =&gt; 2.0.1
Phar API version =&gt; 1.1.1
SVN revision =&gt; $Revision: 314419 $
Phar-based phar archives =&gt; enabled
Tar-based phar archives =&gt; enabled
ZIP-based phar archives =&gt; enabled
gzip compression =&gt; enabled
bzip2 compression =&gt; disabled (install pecl/bz2)
Native OpenSSL support =&gt; 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:

1
2
3
4
5
PHP Fatal error:  Class 'PHPUnit_Runner_BaseTestRunner' not found in /usr/local/lib/php/PHPUnit/TextUI/TestRunner.php on line 60
PHP Stack trace:
PHP   1. {main}() /path/to/zfdev/trunk/tests/phpunit.phar:0
PHP   2. require() /path/to/zfdev/trunk/tests/phpunit.phar:4
PHP   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.

Imaging the HP ProBook 6560b

| Comments

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:

  1. Download the latest Intel PRO1000 drivers here (The PROWin32.exe is the one you are looking for. Yes, it says Server 2003, but that’s OK)
  2. Using 7-Zip (or similar program) decompress the EXE to a folder on your hard drive
  3. Copy the extracted folder to a USB drive
  4. Plug the USB drive into the laptop which you’ve booted into WinPE
  5. From the command prompt, navigate to the folder: E:\PROWin32\PRO1000\Win32\NDIS61 (your USB drive may have a different letter)
  6. 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.

Using Zend\Di in ZF1

| Comments

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.

Enjoy!

zfD2L - Desire2Learn Web Services for Zend Framework

| Comments

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.

To see what’s currently implemented, see Implementation Progress on the zfD2L wiki.

How do I get it?

It’s available on GitHub: https://github.com/cdli/zfD2L

Can I help?

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

Nested Controllers in Zend Framework!

| Comments

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:

https://github.com/adamlundrigan/zf1-nestedcontrollers

jQuery UI DatePicker in Zend Framework

| Comments

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Render the combo box
$content .= $view->datePicker(
    $element->getName() . '[Date]',
    date("m/d/Y", $element->getValue()),
    array()
);      

// Separator
$content .= "&amp;nbsp;&amp;nbsp;@&amp;nbsp;&amp;nbsp;";

// Display the time selector
$content .= $view->formSelect(
    $element->getName() . '[Time]',
    date("H:i", $element->getValue()),
    NULL,
    $ListOfAvailableTimes
);

return $content;

The jQuery view helper will then add the following javascript to the page header to enable the date picker whenever this element is added to a form:

1
2
3
$(document).ready(function() {
    $("#OriginSiteDeparture[Date]").datepicker({"minDate":"0","showOn":"button","buttonImageOnly":true});
});

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:

1
2
3
4
5
6
7
// Render the combo box
$content .= $view->datePicker(
    $element->getName() . '[Date]',
    date("m/d/Y", $element->getValue()),
    array(),
    array('id'=>$element->getName() . '-DatePicker')
);  

With that addition, the javascript produced by the view helper is now correct:

1
2
3
$(document).ready(function() {
    $("#OriginSiteDeparture-DatePicker").datepicker({"minDate":"0","showOn":"button","buttonImageOnly":true});
});

And everything works as expected. Yay!