6 Mar/10

Zend Framework Applications for iPhone, BlackBerry & Co

Mobile devices have become pretty important during the last years. This tutorial will show you how to pimp up your website and make it ready for mobile devices like iPhone, BlackBerry, etc.

Before we start: as in all other posts of this blog, I expect that you are a software engineer and implemented your web application using the MVC (model / view / controller) pattern and probably also Zend_Layout.

1. What We Need

Basically, the only thing you have to do when making your web application ready for mobile devices is to a) detect if the user surfs to your site using a mobile or non-mobile device and b) change the V in MVC according to the result of a).

However, I find it quite useful to extent this approach a little bit. Beside replacing your views with views for a mobile device, we will do two other things: we will also replace the layout (Zend_Layout) used for your web application and we will use a different translation file (Zend_Translation). It is obvious why replacing the layout is useful, but why do we need to use a different translation file? Well, actually we don’t have to, but I found it quite handy if you have translation file for the big screen (where you might want to use looong textual descriptions) and a translation file for your mobile devices (with crisp descriptions, error messages, labels, etc.)

As we will see later on, Zend Framework’s Context Switch is (nearly) all we need… The ContextSwitch is an Action Helper that “is intended for facilitating returning different response formats on request”. This action helper comes with two different ready-to-use contexts: JSON and XML. For our example we will create an additional context named “mobile”.

Control Flow

Basically, our control flow has to work as depicted in figure “Control Flow” (click on the figure to enlarge it). If a user surfs to http://mobile.example.com/controller/action, we directly set the correct context “mobile”. If a user surfs to http://www.example.com/controller/action, we check if he is using a mobile device. If he is using a mobile device, we’ll ask the user if he wants to use the mobile or desktop version of our web app. As we don’t want to ask the user whether or not he wants to use the mobile version each time he request a page, we will store her / his decision in a session variable (and only ask her again, if she was inactive for several minutes).

As an example for this workflow, please have a look at http://www.qulpa.com

2. Creating our Mobile Plugin

To achieve our goals, we will implement a small plugin. Before implementing this plugin we need a function that checks whether or not the current user is using a mobile device. You could use a smart solution like WURFL to do this. However, for our example, we will use a simple function that returs true if the user is using a mobile device and false otherwise. You’ll find dozens of functions that will do the job if you google for it. I’ll use a function that I found at Brain Handles.

Now, lets create our plugin. In your \plugins folder, create a file called Mobile.php and copy and paste the following source code:

<?php
class Plugin_Mobile extends Zend_Controller_Plugin_Abstract
{
	// instead of defining all these parameters here,
	// you could also put them into your application.ini

	// if user is inactive vor X minutes and surfs to
	// www.example.com, we'll ask him again if he wants
	// to user mobile or desktop version
	private $ask_again_after_x_minutes = 10;

	// used to test your mobile layout. Set this
	// to 1 to emulate a mobile device
	private $test_mobile = 0;

   public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
    {
		// did we already ask the user?
		if (isset($_SESSION['mobileasked'])) {
			// is mobile session still valid?
			if (time() < $_SESSION['mobileasked']) {
				// update session
				$_SESSION['mobileasked'] = time() + $this->ask_again_after_x_minutes * 60;
				// continue with requested page
				return $request;
			}
		}

		// otherwise, check if user is using a mobile device
		// or if we are in test mode.
		if ($this->checkmobile() || ($this->test_mobile == 1)) {
			// if requested page != MOBILE.example.com
			if (!(Zend_Registry::isRegistered('directmobile') && Zend_Registry::get('directmobile') == '1')) {
				// set mobile session
				$_SESSION['mobileasked'] = time() + $this->ask_again_after_x_minutes * 60;
				// ask user if he wants to use mobile or desktop version
				$request->setControllerName('index')
			        	->setActionName('askmobile')
			        	->setParam('format', 'mobile')
			        	->setParams($request->getParams())
			        	->setDispatched(false);
			}
		}
		return $request;
    }

/**
  * This function returns true if user is using a mobile device. False otherwise.
  * (c) by http://www.brainhandles.com/techno-thoughts/detecting-mobile-browsers
  */

private function checkmobile(){
   if(isset($_SERVER["HTTP_X_WAP_PROFILE"])) return true;
   if(preg_match("/wap\.|\.wap/i",$_SERVER["HTTP_ACCEPT"])) return true;
   if(isset($_SERVER["HTTP_USER_AGENT"])){
      // Quick Array to kill out matches in the user agent
      // that might cause false positives
      $badmatches = array("OfficeLiveConnector","MSIE\  8\.0","OptimizedIE8","MSN\ Optimized","Creative\ AutoUpdate","Swapper");

      foreach($badmatches as $badstring){
        if(preg_match("/".$badstring."/i",$_SERVER["HTTP_USER_AGENT"])) return  false;
      }

      // Now we'll go for positive matches
      $uamatches = array("midp", "j2me", "avantg", "docomo", "novarra",  "palmos", "palmsource", "240x320", "opwv", "chtml", "pda", "windows\  ce", "mmp\/", "blackberry", "mib\/", "symbian", "wireless", "nokia",  "hand", "mobi", "phone", "cdm", "up\.b", "audio", "SIE\-", "SEC\-",  "samsung", "HTC", "mot\-", "mitsu", "sagem", "sony", "alcatel", "lg",  "erics", "vx", "NEC", "philips", "mmm", "xx", "panasonic", "sharp",  "wap", "sch", "rover", "pocket", "benq", "java", "pt", "pg", "vox",  "amoi", "bird", "compal", "kg", "voda", "sany", "kdd", "dbt", "sendo",  "sgh", "gradi", "jb", "\d\d\di", "moto","webos");

      foreach($uamatches as $uastring){
        if(preg_match("/".$uastring."/i",$_SERVER["HTTP_USER_AGENT"])) return  true;
      }
   }
   return false;
}

Make sure, that you register this plugin! To do so you need something like this in your bootstrap:

// init PluginLoader. Adopt folder to your application...
$loader = new Zend_Loader_PluginLoader(array(
    'Plugin' => APPLICATION_PATH . '/application/controllers/plugins',
));

// define plugin names and classes
$pluginList = array(
    'plugin1' => $loader->load('Plugin1'),
    'plugin2' => $loader->load('Plugin2'),
//  [...]
    'mobile'      => $loader->load('Mobile'),
);

// get your front controller
$frontController = Zend_Controller_Front::getInstance();

// Register your plugins
foreach ($pluginList as $pluginClass) {
    $frontController->registerPlugin(new $pluginClass());
}

3. Context detection

That’s all you have to do in your mobile plugin. Next thing we have to do is to make sure that we detect the correct context. We’ll do this in our bootstrap. Open your bootstrap.php and put something like this inside:

// set correct context
$domains = explode('.', $_SERVER['HTTP_HOST']);
if ($domains[0] == 'mobile' || $frontController->getParam('format') == 'mobile') {
	if ($domains[0] == 'mobile') {
		// if set, user will be redirected directly to requested page
		Zend_Registry::set('directmobile', '1');
	}
	Zend_Registry::set('context', '\mobile');
} else {
	Zend_Registry::set('context', '');
}

4. Asking the User

As we would like to ask the user if he wants to use the mobile or desktop version of our application, we will create a simple action in our index controller. We will redirect the user to this controller / action in our mobile plugin (see chapter 2).

Open your IndexController.php and create an askmobileAction:

	public function askmobileAction()
	{
		// nothing to do here...
	}

This action basically does… well, nothing ;-). Now, let’s have a look at the askmobile view. In your views folder, which probably will be \views\scripts\index create a file called askmobile.mobile.view and put something like this inside:

How do you want to use this application?<br/>
<a href="<?= 'http://mobile.fopp.de' . $_SERVER["REQUEST_URI"]?>">MOBILE VERSION</a>
<br></br>
<a href="<?= 'http://dev.fopp.de' . $_SERVER["REQUEST_URI"] ?>">DESKTOP VERSION</a>

That’s not really complicated, isn’t it? As you can see, the name of this view differs from the name of all the other views. As we will see later in this tutorial, ContextSwitch will make sure that the view name.MOBILE.phtlm is being called instead of name.phtml, if we are in the context MOBILE.

5. Your Mobile Layout

The next step is to create a unique layout for our mobile version. Whether or not this is necessary depends on your application. However, in most cases it will make sense to a complex layout for the desktop version of your application and a lightweight layout for the mobile version.
First, create a directory in your \layouts folder called \mobile (the full path with probably be something like \application\layouts\mobile but this depends on your application). Create a file called layout.phtml in this folder and put something this inside:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>        
 <?php echo $this->headTitle() . "\n" ?>
 <?php echo $this->headLink() . "\n" ?>
</head>
<body>
 <div id="header">This is your header</div>    
 <div id="content"><?= $this->layout()->content ?></div>
 <div id="footer">This is your footer</div>
</body>
</html>

That´s our very simple layout for the mobile context. It is very likely that your layout will be much more complex even if you create it for mobile devices as you will probably include a CSS file, etc.

6. Putting everything together

We are already nearly finished. The last step is to use the context in your controllers. For this tutorial we will use the init() method of the IndexController, which you can use as template for all the other controllers in your application. Actually, instead of copying this code to all your controllers I prefer a smarter way that makes use of OO-Design paradigms (e.g., create a class MyMobileController that extends Zend_Controller_Action and get all the necessary parameters from your application.ini), however, this will do the job for this tutorial.

Open your IndexController.php file and copy and paste the following source code into it:

    /**
     * Initializes the controller & context
     *
     * @return void
     */
    public function init()
    {
        parent::init();

        // are we in the mobile context?
       if (Zend_Registry::get('context') == '\mobile' || $this->getRequest()->getParam('format') == 'mobile')
       {
         	// Mobile format context
        	$mobileConfig =
    	        array(
	                'mobile' => array(
                    	'suffix'  => 'mobile',
                	    'headers' => array(
            	            'Content-type' => 'text/html; charset=utf-8')),
        	    );

     	    // Init the action helper
        	$contextSwitch = $this->_helper->contextSwitch();

    	    // Add new context
	        $contextSwitch->setContexts($mobileConfig);

        	// This is where you have to define
        	// which actions are available in the mobile context
        	// ADOPT THIS TO YOUR NEEDS!
        	$contextSwitch->addActionContext('index', 'mobile');
        	$contextSwitch->addActionContext('askmobile', 'mobile');

        	// enable layout but set different path to layout file
        	$contextSwitch->setAutoDisableLayout(false);
        	$this->getHelper('layout')->setLayoutPath(APPLICATION_PATH . '/application/layouts/mobile');

        	// Initializes action helper
        	$contextSwitch->initContext('mobile');
        }
    }

7. Create your Mobile Views

Finally, you have to create your mobile views. For each view that is available in the mobile context (as defined in the init() method of your controllers), you have to create a mobile view. So, if you have an action called myaction you will need a myaction.phtml for the desktop version and a myaction.mobile.phtml for the mobile version of your application.

Congratulations! You’ve just created your first mobile web application ;-)

Appendix: Translation Files for a Mobile Device

As promised in chapter 1 we will use a different translation file for our mobile device / mobile context. This is quite handy as you might want to have shorter labels, description texts, error messages and so on. Of course, if you don’t need something like this you may simply skip this appendix.

Basically, all you need to do is to check in which context the application is and load the corresponding translation file.

Let’s assume that you store your translation files in the \application\translations\ folder and that you have an english and a french version of your application. Beside your fr.php and en.php files you should have a mobile version for each language in your translations folder: mobile_en.php and mobile_fr.php. The followin code snippet will load the corresponding translation file:

        // Init Zend_Locale with corresponding language (assuming that $lang is set to 'en' or 'fr')
        // and store the Zend_Locale object in the registry
        Zend_Registry::set('Zend_Locale', new Zend_Locale($lang));

        // Load translation file and store it in the registry
        $langFile = APPLICATION_PATH . '/application/translations/';
		if (Zend_Registry::get('context') == '\mobile') {
			// if context = mobile, get translation file for mobile device
			$langFile.= 'mobile_' . Zend_Registry::get('Zend_Locale')->getLanguage() . '.php';
		} else {
			$langFile.= Zend_Registry::get('Zend_Locale')->getLanguage() . '.php';
		}
        Zend_Registry::set(
            'Zend_Translate', new Zend_Translate('array', $langFile)
        );

No related posts.

Posted in Mobile Apps and Tutorial and Zend Framework by Christian on March 6th, 2010 at 6:16 PM.

15 comments - 3 pingbacks / trackbacks

15 Replies

  1. Patrick McD Jul 3rd 2010

    Thanks much.. really gave me some good ideas

  2. Trinity James Jul 9th 2010

    Mobile browsers are still kind of crude if you compare it to the desktop browsers we use on PC.;,;

  3. Lillian King Jul 26th 2010

    there would be a great demand for mobile browsers in the coming years that is for sure.,~;

  4. I’ve bookmarked this URL, keep up the great work!

  5. Chet Brogna Nov 3rd 2010

    Great post

  6. Thanks for a great article and interesting comments. I identified this put up whilst surfing the world-wide-web for Thanks for sharing this informative article.

  7. Plastic Pond Dec 20th 2010

    i have seen a few mobile browsers and used some of them, they are still a bit slow -:`

  8. Generally i don’t post my comments but this time I just want to say good job! :)

  9. Hello! Just wanted to say good blog. Continue with the good work!

  10. TheMinh May 7th 2011

    Hello, great post thanks !
    Actually I don’t want to redirect a mobile user to http://mobile.mywebsite.com, I just want the user to decide the version he wants and to switch context if necessary. How to do so ?
    Thanks

  11. Hi there! This is exactly what the plugin does (see Plugin_Mobile line 37). If the user uses a mobile device, it simply asks the user if he / she wants to use the mobile version or the desktop version of web site.

    Bye
    Christian

  12. cauboy Jan 25th 2012

    Hello,
    beside the IndexController I have several other Controllers, i.e. AuthenticationController.
    Now it seems like the authenticationController is completely ignored. when I put a die(); in the init() function of the AuthenticationController and use a mobile device the index Layout will be shown….
    what to do?

  13. Hi! What do you mean by “the authenticationController is completely ignored”? Zend Framework calls a controller, if the request contains something like “/authentication/action”. Did you set the context for this controller (see Step 6 in the tutorial).

  14. hmmm nice very nice i really like it keeep it up

  15. Can you help with the same implementation with Zend_http_user_agent instead of the custom code mentioned above ?


Leave a Reply

You must be logged in to post a comment.

Site tools