Chain of Responsibility Mobile Sniffer

snifferUpdate!

This pattern was used on the updated Sandlight Productions site. Check it out. (Now back to our regularly scheduled program.)

Sniffing along the Chain

While working on Learning PHP Design Patterns I build a sniffer to go with a CMS app that used an Observer pattern. At the time, I was thinking that I should really add a Chain of Responsibility (CoR) that could then send out content via the Observer pattern. In any event, I decided to go ahead with the project and post it on this blog.

To get started, take a look at the initial article on this blog explaining the Chain of Responsibility design pattern in PHP. The code in this application re-uses the bulk of the original one posted—remember that re-use is one of reasons to employ design patterns and this blog should certainly set examples where possible.

A Mobile Sniffer and a Chain of Choices

One thing we know for sure in the world of mobile devices is that more are sure to come. Once we make a nice big Web site that the client absolutely loves, along comes another mobile device.

“Ah, Joyce, could you add something so that the Bizarro 5000 can be detected?”

So don’t act like you didn’t know this was coming. That’s why the Chain of Responsibility is called into service. So we’ll add a class for the Bizarro 500 (Biz5000), link it to an appropriate Web page or make a new one, re-jigger the Client, and Bob’s your Uncle! Done and done.

Test the CoR Sniffer on your different devices to see the different results. If you have a device that you cannot connect, send in a comment and we’ll work out how to add your device to those sniffed out. Also, download the files for this post:

PlayDownload

To get started, create a Handler abstract class. This will provide the abstract methods that give us lots of flexibility and loosely couple the classes. (It’s not much different than the original one created on this blog.)

<?php
	abstract class Handler
	{
		protected $site;
		abstract public function handleRequest($request); 
		abstract public function setSuccessor($nextService);		
	}
?>

The $site variable is one that will be used to instantiate one of three Web page builders. The two mobile sites (phone and tablet) will use jQuery Mobile, and the desktop site will be treated as a default site and uses no special code—just plain CSS and HTML. All of the sites are generated inside a PHP file using HEREDOC formatting.

Keeping in mind the the Chain of Responsibility design pattern is super simple to build, all we need are some concrete handlers and a Client. The Client participant in this pattern is hard-working, and a helper class, Request, has been added to help shovel requests to the concrete handlers. For this example, only four concrete handler classes have been created:

  1. Iphone
  2. Android
  3. Ipad
  4. Desktop

You may well ask, “What about Kindle Fire, Surface, and Blackberry?” And the answer is, “Add them yourself.” Keep in mind that design patterns are flexible, and it’s easy to add and change the parts.

The Concrete Handlers

The following four concrete handlers are very similar, but while the Iphone and Android call for the same MobileSet class, the Desktop calls for DesktopSet and the Tablet for TableSet.

<?php
class Iphone extends Handler
{
    private $successor;
 
    public function setSuccessor($nextService)
    {
        $this->successor=$nextService;
    }
 
    public function handleRequest ($request)
    {
        if (stripos($request->getService(),'iphone'))
        {
           $this->site=new MobileSet();
        }
        else if ($this->successor != NULL)
        {
                $this->successor->handleRequest ($request);
        }
    }
}
?>
 
//Android
<?php
class Android extends Handler
{
    private $successor;
 
    public function setSuccessor($nextService)
    {
        $this->successor=$nextService;
    }
 
    public function handleRequest ($request)
    {
        if (stripos($request->getService(),'android'))
        {
           $this->site=new MobileSet();
        }
        else if ($this->successor != NULL)
        {
                $this->successor->handleRequest ($request);
        }
    }
}
?>
 
//Ipad
<?php
class Ipad extends Handler
{
    private $successor;
 
    public function setSuccessor($nextService)
    {
        $this->successor=$nextService;
    }
 
    public function handleRequest ($request)
    {
        if (stripos($request->getService(),'ipad'))
        {
           $this->site=new TabletSet();
        }
        else if ($this->successor != NULL)
        {
                $this->successor->handleRequest ($request);
        }
    }
}
?>
 
//Desktop
<?php
class Desktop extends Handler
{
    private $successor;
 
    public function setSuccessor($nextService)
    {
        $this->successor=$nextService;
    }
 
    public function handleRequest ($request)
    {
        $this->site=new DesktopSet();
    }
}
?>

The way the handlers are set up, each is checked for a match with a user agent except for the Desktop handler. It works as a residual category—if the sniffer doesn’t match any of the agents, use Desktop. So why have it extend Handler? Why not just have it launch DesktopSet with no muss or fuss? We know that the technology is going to change, and so just in case some other technology comes along and you need a successor to Desktop, it’s all set to go. (Like a giant TV screen….)

Creating Display Classes

There are probably a million different ways of calling a PHP or HTML file to launch the requested page, but I used classes and the PHP heredoc feature. Feel free to use any method that you prefer. Likewise, I employed jQuery Mobile for both the mobile phone and table sets, but again, use what you favor. The classes or files that make up the device display are not participants in the Chain of Responsibility pattern per se. That ends with the concrete handlers. These are helper classes that do the grunt work, and while they could have been placed in the handlers themselves (part of the concrete handler classes), I chose to set them off as separate classes.

The desktop represents plain vanilla HTML and CSS used in displaying a page.

//Desktop Display
<?php
class DesktopSet
{
	function __construct()
	{
		$setNow = <<<DESKTOP
		<!doctype html>
		<html>
		<head>
		<link type="text/css" rel="stylesheet" href="sandlight.css">
		<meta charset="UTF-8">
		<title>Desktop</title>
		</head>
		<body>
		<img class="imgLeft" src="logos/logo.png" alt="logo">
		<nav>
		<a href="#">PHP</a>&nbsp;|&nbsp;<a href="#">Mobile</a>&nbsp;|&nbsp;<a href="#">ActionScript 3.0</a>&nbsp;|&nbsp;<a href="#">Books</a>&nbsp;|&nbsp;<a href="#">About Us</a>&nbsp;|&nbsp;<a href="#">Contact</a>
		</nav>
		<h1>Hello Desktop World</h1>
		This is standard size text that should be readable on the desktop.
		<img class="center" src="covers/lrg.jpg" alt="cover">
		</body>
		</html>
DESKTOP;
echo $setNow;
	}
}
?>
 
//CSS for desktop
@charset "UTF-8";
/*sandlight.css*/
/* CSS Document 
1B8C4C dark green
80BF9B light green
BFD9CA pale green
D9B036 mustard
F29B30 muted orange*/
 
body {
	background-color: #BFD9CA;
	font-family: Verdana, Geneva, sans-serif;
	color: #1B8C4C;
	margin: 20px;
}
h1 {
	font-family: "Arial Black", Gadget, sans-serif;
	color: #1B8C4C;
}
nav {
	margin-top:40;
	margin-bottom:150;
	margin-left:250;
	font-family: "Arial Black", Gadget, sans-serif;
	background-color: #D9B036;
	text-align: center;
}
a {
	text-decoration: none;
	color: #1B8C4C;
}
.center {
	margin-top:20;
	display: block;
	margin-left: auto;
	margin-right: auto;
}
.imgLeft 
{ 
	float:left;
 	margin: 0 15px 5px 0; 
}

Figure 1 shows the display on the desktop.

Figure 1: Desktop and Laptop display

Figure 1: Desktop and Laptop display

I found that most desktop displays work pretty well with tablets except that the text that looks fine on a desktop, often looks tiny on a tablet. (At least on my iPad it’s tiny.) Adding a different stylesheet for a tablet using the general desktop CSS could handle that problem, but I decided to try and go with jQuery Mobile as you can see in the TabletSet class.

//Tablet Display
<?php
class TabletSet
{
	function __construct()
	{
		$setNow = <<<TABLET
		<!DOCTYPE html> 
<html>
<head>
<title>Sandlight Productions, LLC</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
<script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
<style>
.center {   display: block;   margin-left: auto;   margin-right: auto; }
</style>
</head>
<body>
<div data-role="page">
  <div data-role="header">
    <h1>Sandlight Productions</h1>
  </div>
  <div data-role="content"> <img src="logos/logo.png">
    <p>Hello everybody. This should be easy to read on a tablet.</p>
  </div>
  <ul data-role="listview" data-inset="true" data-theme="e">
    <li><a href="#">PHP</a></li>
    <li><a href="#">Mobile</a></li>
    <li><a href="#">ActionScript 3.0</a></li>
    <li><a href="#">Books</a></li>
    <li><a href="#">About Us</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
  <div data-role="content"> 
  <img class="center" src="covers/tabletCover.jpg" alt="cover">
  </div>
</div>
</body>
</html> 
TABLET;
echo $setNow;
	}
}
?>

Both the code and the output are very different for the tablet compared to the desktop, and I didn’t change the color scheme CSS for the tablet. Figure 2 shows the image on an iPad:

Figure 2: The Tablet output uses a different configuration than the desktop.

Figure 2: The Tablet output uses a different configuration than the desktop.

Finally, the MobileSet class is designed for mobile phones (or smartphones). It uses a smaller logo and book cover image than the table, but otherwise, it’s the same.

//Phone Display
<?php
class MobileSet
{
	function __construct()
	{
		$setNow = <<<MOBILE
		<!DOCTYPE html> 
<html>
<head>
<title>Sandlight Productions, LLC</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
<script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>
<style>
.center {   display: block;   margin-left: auto;   margin-right: auto; }
</style>
</head>
<body>
<div data-role="page">
  <div data-role="header">
    <h1>Sandlight Productions</h1>
  </div>
  <div data-role="content"> <img src="logos/phonelogo.png">
    <p>Hello everybody. This should be easy to read on a mobile phone.</p>
  </div>
  <ul data-role="listview" data-inset="true" data-theme="e">
    <li><a href="#">PHP</a></li>
    <li><a href="#">Mobile</a></li>
    <li><a href="#">ActionScript 3.0</a></li>
    <li><a href="#">Books</a></li>
    <li><a href="#">About Us</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
  <div data-role="content"> 
  <img class="center" src="covers/cat.gif">
  </div>
</div>
</body>
</html> 
MOBILE;
echo $setNow;
	}
}
?>

I believe that jQuery Mobile pays off the most for smartphones. As you can see in Figure 3, you cannot see the book cover image, but the text and buttons are clear and easy to tap.

Figure 3: When using smartphones, the main navigation is vertical.

Figure 3: When using smartphones, the main navigation is vertical.

Add Your Own Handlers

If your mobile device is different than those listed, all you have to do is:

  • Find the device user agent name (search engine hunt)
  • Add a handler for your device
  • Add the device to the succession chain in the Client

Make those changes, and you should be able to view what you need. If you have any problems, send in a comment, and I’ll see if I can fix it for you.

The beauty of both design patterns in general and the Chain of Responsibility in particular is that you can take even the most complex program and easily make changes. So don’t be shy if you cannot add your device to the Chain. Send in a comment, and we’ll see how to make the change.

Share

Copyright © 2012 William Sanders. All Rights Reserved.

0 Responses to “Chain of Responsibility Mobile Sniffer”


  • No Comments

Leave a Reply