Tag Archives: Zf

ZF2 Dependency Injection: Managing Configuration – Part 2

In my previous post about Dependency Injection Container (DiC) configuration I made mention of some more complicated scenarios that you can use to configure your objects.  Not that you need help to make it  more complicated.  One of the things I have observed is that the more familiar I am with the DiC the more complicated the configuration gets.  This is both good and bad.  It’s good because you start understanding how and why all of those frigging long example DI configuration arrays are like that.  The bad is that  long DiC configuration arrays greatly reduce the ability of a new developer to start being productive.  That’s why I think this series of blog posts are good.  They show you how to get started small which is really the only way to get started if you aren’t intimately familiar with a DiC.

Let’s take our Test class and change its behavior by adding a method that allows you to set some test data (use your imagination here before complaining in the comments that it’s not a real world example).

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
class Test
  {
  protected $test;
  protected $data;
 
  public function setTestData($test)
  {
    $this->data = $test;
  }
 
  public function getTestData()
  {
    return $this->data;
  }
 
  public function setTest($test)
  {
    $this->test = $test;
  }
 
  public function getTest()
  {
    return $this->test;
  }
}

We dutifully set up our DiC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$config = array(
  'instance' => array(
    'Test' => array( // Class name
      'parameters' => array(
        'test' => 'some data'
      )
    )
  )
);
 
use Zend\Di\Configuration;
 
$diConfig = new Configuration($config);
$di = new Di();
$diConfig->configure($di);

And we run our test code getting our ‘some data’ parameter, right?

1
string(9) "some data"

Yep.  Hmm, but what about getTestData()?

1
var_dump($test->getTestData());

echos

1
string(9) "some data"

Hmm, that shouldn’t be happening.  We did’t tell the DiC that we wanted to inject data there, did we?  It looks like it’s injecting data into both setter methods.  But why?

Look at the names of the parameters for both setters.  They are both named “test”.  The DiC will find the first method with a matching parameter name and inject the value there (if it couldn’t find it in the constructor) .  You have a couple of options to fix this.  If it’s your own code and you don’t mind refactoring, refactor it.  However, if it’s third party code that you shouldn’t touch or code that other parts of the system are dependent on, you have a problem.

But to get around the problem you can specify the setter parameter as a fully qualified (FQ) parameter (I show another , probably better, method here).  The format is “class::method:paramPos”.  In this example it would be “Test::setTest:0″.  So let’s change our DI configuration.

1
2
3
4
5
6
7
8
9
$config = array(
  'instance' => array(
    'Test' => array( // Class name
      'parameters' => array(
        'Test::setTest:0' => 'some data'
      )
    )
  )
);

And now check the return values of both getters.

1
2
3
$test = $di->get('Test');
var_dump($test->getTest());
var_dump($test->getTestData());

Now we get

1
2
string(9) "some data"
NULL

Works.

ZF2 Dependency Injection: Managing Configuration – Part 1

Configuration is a big reason to use a Dependency Injection Container (DiC).  I’ve been doing a lot of playing around with the ZF2 DiC and one of the things that I like about it is the ability to retrieve fully configured objects in  one line of code.  I’ve been going a bit overboard lately but there’s a lot of stuff you can do that can just be downright complex to handle on your own, all the time.

In my previous blog post I showed how you could provide parameters to object that you’re pulling from a DiC and have them populated in the resulting object.  As cool as that is, it’s not a massive saving as you need to manually inject the parameters into the DiC.  You can often do the same thing by setting up the DiC ahead of time to get the object with those parameters pre-set.

Let’s start by defining a class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test
{
  protected $test;
 
  public function __construct($test)
  {
    $this->test = $test;
  }
 
  public function getTest()
  {
    return $this->test;
  }
}

What we did before was use call time parameters to set the $test variable.

1
2
3
$di = new Di();
$test = $di->get('Test', array('test' => 'some data'));
echo $test->getTest();

Running this produces

1
'some data'

What if we either don’t know what ‘test’ will be, we want to minimize our code or we want to centralize our injection requirements?  For any of those we can set up the DiC to inject that data for us.

1
2
3
4
5
6
7
8
9
$config = array(
  'instance' => array(
    'Test' => array( // Class name
      'parameters' => array(
        'test' => 'some data'
      )
    )
  )
);

The structure of the configuration is this.  The first level is the key “instance”.  This will limit the objects created to a single instance in the DiC when requested with given set of parameters, similar to a registry (someone in the ZF team can correct me if I’m wrong).  The second level of the array is the key of the class name.  Our class is called “Test” and so we call the key “Test”.  If it was “Api\Service\Account” the key would be ”Api\Service\Account”.  Next up are various options for the injection.  The only option I’m concerned with is the “parameters” one.  That is where you put in the various injections the object needs in a key/value array.

In the parameters array the keys will refer to the names of the variables you are injecting into the class.  The DiC will try to match it in two places.  The first is the named parameters in the constructor and the second are the variable names in the setters in the class.  The latter scenario may require some additional information, but we’ll look at that in a later blog post.

The first thing we need to do is take that configuration and provide it to a configuration object and then have the configuration object configure the DiC for us.

1
2
3
4
5
use Zend\Di\Configuration;
 
$diConfig = new Configuration($config);
$di = new Di();
$diConfig->configure($di);

We now have a configured DI object.  So to execute our code from earlier we change it just slightly.

1
2
$test = $di->get('Test');
echo $test->getTest();

The could echos

1
'some data'

This is now equivalent to our previous call.  Now this may seem all nice and well, but there’s more to it.  What if your object depends on an instance of another class.

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
class Test
{
  protected $test;
 
  public function __construct(Exam $test)
  {
    $this->test = $test;
  }
 
  public function getTest()
  {
    return $this->test;
  }
}
 
class Exam
{
  protected $grade;
 
  public function __construct($grade)
  {
    $this->grade = $grade;
  }
 
  public function getGrade()
  {
    return $this->grade;
  }
}

We have two classes.  One called Test and one called Exam.  Test requires Exam and so we need to define that injection requirement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$config = array(
  'instance' => array(
    'Test' => array( // Class name
      'parameters' => array(
        'test' => 'Exam'
      )
    ),
    'Exam' => array(
      'parameters' => array(
        'grade' => 'A+'
      )
    )
  )
);

We redefined our Test injection to require an instance of “Exam” and we added another instance definition for Exam which provides the grade.  Then we’ll change our code slightly.

1
2
3
$test = $di->get('Test');
echo get_class($test->getTest()) . "\n";
echo $test->getTest()->getGrade();

And when we run it we get

1
2
Exam
A+

With that you can now configure a DI Container and start providing references to various objects in and around your application.

There is more to talk about but we’ll leave this post at that.  Check back soon for more examples.

Zend Framework 2 Event Manager

Yes, I know I work for Zend and that means that I should automatically be familiar with everything the company does.  Especially when it comes to Zend Framework 2.  But I have to confess that while I’m most definitely watching it, I have not been able to work with it in any meaningful sense.

Until today.  I got to play with the Event Manager.  I did like the plugin functionality in ZF1, but it required some pretty static coding.  In some cases, like the front controller plugins, it makes more sense (though this way seems more desirable).  Of course there are frameworks out there that are more event based as a whole, but what if you’re more familiar with Zend Framework?  I am, and so it makes sense that I would use the event manager in ZF2.  It’s a ZF1 application, but since (it seems) the event manager is self-contained (and the autoloader works with both ZF1 and ZF2) you can simply paste it into your include_path and BOOM, you have an event manager.

So, I had a model (the software kind) layer where all of the models were based off of an abstract class.  The abstract class had a save() method that I wanted to provide various hooks for pre and post save and, more importantly, pre and post validation.  Why validation?  Because I want to stored a hashed password in the database, but I want to validate on the plaintext.  I could have created a bunch of hooks, but that really is annoying.  I could have also overridden the save() method, but that meant adding a fair amount of logic to that method and I’m becoming less and less a fan of overriding logic because it’s insufficient at higher levels of abstraction.  So, instead, I implemented the ZF2 event manager in a ZF1 application.

The model looks like this:

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
33
34
35
36
37
38
39
40
abstract class ModelAbstract
{
 
	const EVENT_PRESAVE = 'modelabstract-pre-save';
	const EVENT_PREVALIDATE = 'modelabstract-pre-validate';
	const EVENT_POSTVALIDATE = 'modelabstract-post-validate';
	const EVENT_POSTSAVE = 'modelabstract-post-save';
 
	/**
	 * @var EventManager
	 */
	protected $eventManager;
 
	public final function __construct()
	{
		$this->eventManager = new EventManager();
		$this->init();
	}
 
	public function init() {}
 
	public function save()
	{
		if ($this->tableName === null) {
			throw new ModelException('Improperly defined model');
		}
		$this->eventManager->trigger(self::EVENT_PREVALIDATE, $this);
		if (!$this->getForm()->isValid($this->data)) {
			return false;
		}
		$this->eventManager->trigger(self::EVENT_POSTVALIDATE, $this);
		$db = \Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('db');
		$this->eventManager->trigger(self::EVENT_PRESAVE, $this);
 
// save the data (redacted due to boredom)
 
		$this->eventManager->trigger(self::EVENT_POSTSAVE, $this);
		return $ret;
	}
}

Then in my Account model

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
class Application_Model_Account extends ModelAbstract
{
	protected $tmpPassword;
 
	public function init()
	{
		// Encrypt the password after validation if the password has changed
		$this->eventManager->attach(
			self::EVENT_POSTVALIDATE,
			function($event) {
				if ($event->getTarget()->passwordChanged()) {
					$event->getTarget()->encryptPassword();
				}
			}
		);
	}
 
	public function passwordChanged() // really annoying. Thanks PHP 5.3
	{
		return $this->tmpPassword == null;
	}
 
	public function encryptPassword()
	{
		if ($this->passwordChanged()) {
			$util = new Util();
			$this->password = $util->encryptPassword($password);
			$this->tmpPassword = null;
		}
	}
}

If the password is changed it sets both $this->password and $this->tmpPassword.  The hashing only occurs if the password has been set and there’s a value in tmpPassword.  (I guess I could have just used a boolean somewhere too).  So now, every time I save the account model the post validate event will be fired and the password will be encrypted.  (The comment on PHP 5.3 is because you cannot use $this in a closure in PHP 5.3.  That functionality will be there in 5.4.)

I believe that the method I used here allows for events to be fired for specific object instances, because each model has its own event manager.  But the event manager will also check for global events as well.  So, technically, the code that I have that does validation could be registered as a global observer to make sure that all models are properly validated prior to the save as well as having instance-specific (such as password changes) events attached to the same event name.

Pretty cool.

… waiting for Matthew to email me with corrections… :-)

[UPDATE]

For those who have complained that I didn’t use some additional kind of methodology or best practice know that the other five billion lines of code that had nothing whatsoever to do with the event manager has been removed… or read my response down below on “Methodology Jackassery”.

ZendCon 2010 Podcast – Unit Testing in Zend Framework 1.8

Speaker

Michelangelo van Dam

Abstract

Zend Framework 1.8 has improved and simplified how you can test your applications, providing you with excellent techniques to streamline your quality assurance processes and reduce your maintenance costs.

Licensing:

The ZendCon Sessions are distributed under a creative commons Attribution-Noncommercial-No Derivative Works 3.0 License, Please honor this license and the rights of our authors.


Slides

View Slides (SlideShare)

Podcast Download

Download as MP3

Play Inline

ZendCon 2010 Podcast – Introducing Zend Framework 2.0

Speaker

Ralph Schindler (Penn) and Matthew Weier O'Phinney (Teller)

Abstract

Zend Framework has grown tremendously since the first public preview release in March 2006. Originally a slim, MVC framework with a number of standalone components, it has grown to a codebase more than 2M lines of code. Work now turns to version 2, with goals of increased simplicity and advanced PHP 5.3 usage.

Licensing:

The ZendCon Sessions are distributed under a creative commons Attribution-Noncommercial-No Derivative Works 3.0 License, Please honor this license and the rights of our authors.


Slides

View Slides (SlideShare)

Podcast Download

Download as MP3

Play Inline

Subnet validation with Zend Framework

(Note – I accidentally gave conflicting instructions to the person who runs our newsletter.  If you are actually interested in the article I wrote about people being silly about dynamicly typed languages you can go here)

I saw on a StackOverflow posting, someone was asking to see how you could use a Zend Framework validator to tell if an IP address was between two addresses.  The individual was trying to use Zend_Validate_Between to do the checking.  However, IP addresses generally are not checked between two arbitrary addresses such as between 192.168.0.45 and 192.168.0.60.  Instead, the check is usually done to validate an IP address against a subnet.

So, assuming that the individual was actually asking about subnet validation, and seeing that I couldn’t find a subnet validator for Zend Framework, I wrote a quick one.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
class Esc_Validate_Subnet extends Zend_Validate_Abstract
{
    const NOT_ON_SUBNET = 'not-on-subnet';
    private $_subnet;
    private $_netmask;
    protected $_messageTemplates = array(
        self::NOT_ON_SUBNET => "'%value%' is not on the subnet"
    );
 
    public function __construct($subnet, $netmask = null)
    {
        if ($netmask === null && strpos($subnet, '/') === false) {
            throw new Zend_Validate_Exception(
                 'If the netmask is not specified then the CIDR (e.g. /24) must be provided');
        }
        if ($netmask === null) {
            $this->_subnet    = ip2long(substr($subnet, 0, strpos($subnet, '/')));
            $cidr = substr($subnet, strpos($subnet, '/') + 1);
            // /0 is used to denote the default route.  Since we're not doing routing,
            // we will use it for error checking
            if ($cidr < 1 || $cidr > 32) {
                throw new Zend_Validate_Exception('Invalid CIDR specified');
            }
            $this->_netmask = -1 << (32 - (int)$cidr);
 
        } else {
            $this->_subnet    = ip2long($subnet);
            $this->_netmask = ip2long($netmask);
        }
 
    }
 
    public function isValid ($value)
    {
        $this->_setValue($value);
        $host = ip2long($value);
 
        $check1 = $host & $this->_netmask;
        $check2 = $this->_subnet & $this->_netmask;
 
        if ($check1 == $check2) {
            return true;
        }
        $this->_error(self::NOT_ON_SUBNET);
        return false;
    }
}

 

Here is the Unit Test case

 

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
assertTrue($sn->isValid('192.168.0.2'));
    }
 
    public function testValidSubnetWithCIDR()
    {
        $sn = new Esc_Validate_Subnet('192.168.0.1/24');
        $this->assertTrue($sn->isValid('192.168.0.2'));
    }
 
    public function testInvalidSubnetAndNetmask()
    {
        $sn = new Esc_Validate_Subnet('192.168.1.1', '255.255.255.0');
        $this->assertFalse($sn->isValid('192.168.0.2'));
    }
 
    public function testInvalidSubnetWithCIDR()
    {
        $sn = new Esc_Validate_Subnet('192.168.1.1/24');
        $this->assertFalse($sn->isValid('192.168.0.2'));
    }
 
    /**
     * @expectedException Zend_Validate_Exception
     */
 
    public function testMalformedCIDRBadSeperator()
    {
        $sn = new Esc_Validate_Subnet('192.168.0.1_24');
        $this->assertTrue($sn->isValid('192.168.0.2'));
    }
 
    /**
     * @expectedException Zend_Validate_Exception
     */
 
    public function testMalformedCIDRNumericalCIDR()
    {
        $sn = new Esc_Validate_Subnet('192.168.0.1/abc');
        $this->assertTrue($sn->isValid('192.168.0.2'));
    }
 
    /**
     * @expectedException Zend_Validate_Exception
     */
 
    public function testMalformedCIDRLowCIDR()
    {
        $sn = new Esc_Validate_Subnet('192.168.0.1/-1');
        $this->assertTrue($sn->isValid('192.168.0.2'));
    }
    /**
     * @expectedException Zend_Validate_Exception
     */
 
    public function testMalformedCIDRHighCIDR()
    {
        $sn = new Esc_Validate_Subnet('192.168.0.1/33');
        $this->assertTrue($sn->isValid('192.168.0.2'));
    }
 
}

 

Feel free to peruse and see if I have made any mistakes.  I wrote it pretty quickly and so there is, in all likelihood, something that could be tweaked.

Amazon SQS and Zend Framework

I’m doing some work for a webinar and I figured out that if you want use AWS SQS in your app you need to specify not just the queue name, but the full URL.  What is the full URL?  It is what createQueue() returns.

For example, I was doing this to store the queue name in a task that was stored in the session so I could get it at a later point in time:

1
2
$this->_queueName = sha1('fileProcess-' . $this->_sourceId);
$queue->createQueue($this->_queueName);

What I needed to be doing instead was this

1
2
$this->_sourceId = sha1(uniqid(php_uname(), true));
$this->_queueName = $queue->createQueue(sha1('fileProcess-' . $this->_sourceId));

Additionally, you have no guarantee of order with SQS.  In other words, there is no guarantee that the messages you receive will be in the order that they were sent.  So you will need to build your own mechanism for handling that if you have a need for a FIFO MQ in AWS.

Encrypted session handler

[UPDATE]  Enrico Zimuel has a better version of this[/UPDATE]

A little while ago I had come upon the problem of having to store sensitive data in a user session.  The solution that I (and several others came upon) was creating a mechanism for storing encrypted data in a session.  But what we wanted to do was build something that didn’t have a single point of failure.  We also wanted to build something portable.  What we built was a simple Zend Framework session handler for storing sensitive data.

 

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class EncryptedSession extends Zend_Session_Namespace {
 
    const CIPHER = MCRYPT_3DES;
    const MODE = MCRYPT_MODE_CBC;
 
    private $_iv;
 
    public function __construct($namespace = 'Default', $singleInstance = false)
    {
        parent::__construct($namespace, $singleInstance); // Must be true because of iv
 
        $storeKey = __CLASS__ . '_Data' . '_' . $namespace;
 
        if (!isset($_COOKIE[$storeKey]) || !isset($this->secretKey)) {
                $this->unsetAll();
 
                $maxKeySize = mcrypt_get_key_size(self::CIPHER, self::MODE);
 
                $secretKey = '';
                while( strlen($secretKey)<$maxKeySize) {
                    $secretKey .= dechex(uniqid(mt_rand(), true);
                }
                $this->secretKey = substr($secretKey, 0, $maxKeySize);
 
                $iv_size = mcrypt_get_iv_size(self::CIPHER, self::MODE);
 
                $this->_iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
 
            $hmac = hash_hmac('md5', $this->_iv, $this->secretKey);
            $unique = base64_encode($this->_iv);
            // The cookie has the same parameters as the session cookie
            $cookie_param = session_get_cookie_params();
            setcookie(
                $storeKey,
                $hmac.$unique,
                $cookie_param['lifetime'],
                $cookie_param['path'],
                $cookie_param['domain'],
                   $cookie_param['secure'],
                   $cookie_param['httponly']
            );
        } else {
            $hmac = substr($_COOKIE[$storeKey],0,32);
            $unique = base64_decode(substr($_COOKIE[$storeKey],32));
            $check = hash_hmac('md5',$unique,$this->secretKey);
            if($hmac !== $check) {
                 throw new Zend_Session_Exception('Invalid Session Data');
            }
            $this->_iv = $unique;
 
        }
    }
 
    public function setEncrypted($key, $value)
    {
        $this->$key = bin2hex(
            mcrypt_encrypt(
                self::CIPHER,
                $this->secretKey,
                $value,
                self::MODE,
                $this->_iv
            )
        );
    }
 
    public function getEncrypted($key)
    {
        if (isset($this->$key)) {
            $decrypt = mcrypt_decrypt(
                self::CIPHER,
                $this->secretKey,
                pack(
                    'H*',
                    $this->$key
                ),
                self::MODE,
                $this->_iv
            );
            return rtrim($decrypt, "�"); // remove null characters off of the end
        }
        return null;
    }
}

 

 

What this does is allow you to transparently store encrypted data in a session.  Because it’s encrypted, someone hacking in to the server, say via an include vulnerability, would be able to read the session data, but not decrypt it.  That’s because the initialization vector is stored in a cookie on the browser.  So in order to decrypt the session an attacker would need to do both an XSS and a remote code injection attack.

Zend_Log with multiple writers

So I was sitting here thinking to myself “This is Friday and I’m not getting much of anything done.  Maybe I should write another Friday Framework Highlight.”  I figured that it was a good idea so I pondered what I should write.  I came up blank and so I asked Matthew Weier O’Phinney.  “Multiple writers for Zend_Log,” he said.  I agreed.

If you were not aware, Zend_Log provides facilities for writing to multiple logs through the same log instance.  Additionally, you can do this via configuration options when using a Zend_Applicatin resource plugin.  Together those make for very powerful logging mechanisms.  “How?” you ask?  It’s really easy.  Take your application.ini file, which you use to configure your Zend_Application instance, and make it look something like this.  I’ll highlight the pertinent parts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.frontController.params.displayExceptions = 0
resources.log.stream.writerName = "Stream"
resources.log.stream.writerParams.stream = APPLICATION_PATH "/logs/application.log"
resources.log.stream.writerParams.mode = "a"
resources.log.stream.filterName = "Priority"
resources.log.stream.filterParams.priority = 4
 
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
resources.log.stream.filterParams.priority = 7
resources.frontController.params.displayExceptions = 1
resources.log.firebug.writerName = "FireBug"
resources.log.firebug.filterName = "Priority"
resources.log.firebug.filterParams.priority = 7

 

What this does is say that “in production, log warnings and above to the log file, but in development, log debug to the log file AND send the log items to FirePHP.”  Then, in our index controller we put this:

1
2
3
4
5
6
7
8
9
class IndexController extends Zend_Controller_Action
{
 
    public function indexAction()
    {
        $this->getInvokeArg('bootstrap')->log->debug("I'm at indexAction");
    }
 
}

 

When we execute this code we get both the output in the application log

$ tail -f application.log
2010-09-10T16:27:25-05:00 DEBUG (7): I'm at indexAction

and in the Firebug log

X-Wf-Protocol-1            http://meta.wildfirehq.org/Protocol/JsonStream/0.2
X-Wf-1-Structure-1    http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1
X-Wf-1-Plugin-1            http://meta.firephp.org/Wildfire/Plugin/ZendFramework/FirePHP/1.6.2
X-Wf-1-1-1-1            122|[{"Type":"LOG","File":"C:\workspace\Test\application\controllers\IndexController.php","Line":8},"I'm at indexAction"]|

In production we wouldn’t get anything since this code would filter out debug logging, due to the resources.log.stream.filterParams.priority setting in the production section in application.ini.  Simple.  Done.

Zend_Server

Let’s take a quick look at something that’s kind of neat in Zend Framework.  I’ve been doing some work with Adobe on some articles and one of them was on working with mobile clients with Flash.  Well, me being the masochist I did more.  What I did was write an example that worked as a full website, an Ajax website, a Flash service and an XML-RPC service.

Setting the Stage

First I started with a Zend_Db row instance and it’s corresponding table instance.

1
2
3
4
5
6
7
8
9
10
11
12
// row
class Model_Peak extends Zend_Db_Table_Row_Abstract
{
/* plus a bunch of getters and setters */
}
 
// table
class Model_DbTable_Peak extends Zend_Db_Table_Abstract
{
    protected $_rowClass = 'Model_Peak';
    protected $_name = 'peaks_location';
}

 

Then I wrote a mapper, which accessed the DB models and exposed the functionality I wanted to provide for all of the different types of requests

1
2
3
4
5
6
7
8
9
class Mapper_Peaks
{
    public function getStates() { /* TODO */ }
 
    public function getRanges($state) { /* TODO */ }
 
    public function getMountains($state, $range) { /* TODO */ }   
 
}

 

After that I wrote a controller that handled the web and JSON-bootstrap pages.

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class IndexController extends Zend_Controller_Action
{
 
    public function indexAction()
    {
        $mapper = new Mapper_Peaks();
        $this->view->states = $mapper->getStates();
    }
 
    public function rangesAction()
    {
        $this->view->state = $this->_request->getParam('state');
        $mapper = new Mapper_Peaks();
        $this->view->ranges = $mapper->getRanges(
            $this->view->state
        );
    }
 
    public function mountainsAction()
    {
        $mapper = new Mapper_Peaks();
        $this->view->state = $this->_request->getParam('state');
        $this->view->range = $this->_request->getParam('range');
        $this->view->mountains = $mapper->getMountains(
            $this->view->state,
            $this->view->range
        );
    }
 
    public function jsonrpcAction() {}
 
    public function serviceAction()
    {
        $serviceHandler = $this->getRequest()->getParam('serviceHandler');
        if ($serviceHandler instanceof Zend_Server_Interface) {
            $smd = $this->_request->getParam('smd');
            if ($smd) {
                echo $smd;
                exit;
            }
 
            $out = $serviceHandler->handle();
            ob_clean();
            echo $out;
            exit;
        }
        throw new Zend_Controller_Response_Exception('Not Found', 404);
    }
}

 

Zend_Server

Now for the fun part.  Notice serviceAction()?  That’s where the Zend_Server_* stuff comes in.  In a plugin, which I will show you in a bit, I attached an instance of Zend_Server_Interface.  Then in the controller I simple told it to do its thing, with the exception of handing the Service Mapping Description for JSON-RPC ($smd).  That’s it.  Because Zend_Json_Server, Zend_XmlRpc_Server and Zend_Amf_Server all implement the Zend_Server_Interface interface that’s all I needed to do.

But since everything is going to one place you need to do a bunch of freaky logic to get the right one, right?  Nope.  It’s actually quite easy.  What I did was create a plugin that detected the request type, instantiated the required server instance and attached it to the request after setting the request object to execute the serviceAction() method instead of the requested one.  In other words, you could send your request to /34gas/q43ar/13/asdfg/as and it would still go to the serviceAction() method.  Here’s the code

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
33
34
35
36
37
class Ctx_Controller_Plugin_ServicePlugin extends Zend_Controller_Plugin_Abstract
{
    public function routeShutdown ( Zend_Controller_Request_Abstract $request)
    {
        $serviceHandler = null;
        if ($request->isXmlHttpRequest() && $request->isPost()) {
            $serviceHandler = new Zend_Json_Server();
            $serviceHandler->getServiceMap()->setDojoCompatible(true);
        } else if ($request->isFlashRequest()) {
            $serviceHandler = new Zend_Amf_Server();
 
        /* Note: This may be different depending on the SAPI you are using */
        } else if (strpos($request->CONTENT_TYPE, 'text/xml') === 0) {
            $serviceHandler = new Zend_XmlRpc_Server(
                'http://' .$_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']
            );
        } else if($request->isXmlHttpRequest()) {
            $serviceHandler = new Zend_Json_Server();
            $smd = $serviceHandler->getServiceMap();
            $smd->setDojoCompatible(true);
            $request->setParam('smd', $smd);
            $view = new Zend_View();
            $serviceHandler->setTarget($view->url())
                           ->setEnvelope(Zend_Json_Server_Smd::ENV_JSONRPC_2);
        }
        if ($serviceHandler) {
            foreach (glob(APPLICATION_PATH.'/mappers/*.php') as $dir) {
                $name = substr(basename($dir), 0, -4);
                $class = 'Mapper_' . $name;
                $serviceHandler->setClass($class, $name);
            }
            $request->setParam('serviceHandler', $serviceHandler);
            $request->setControllerName('index');
            $request->setActionName('service');
        }     
    }
}

Looks like a lot, right?  Actually there’s not much there.  Here’s the logic flow.

  • Is it an XMLHTTP Request and is it a POST? Create the Json server
  • Is it an AMF request? Create the AMF server
  • Is it an XmlRpc request? Create the XmlRpc server
  • Is it an XMLHTTP Request and is it a GET? Create the Service Map (for JSON-RPC 2.0)
  • If a service handler has been created add all of the application’s mappers, attach the service handler to the request and redirect to the service action.

And with that you have an application that can serve content for multiple different types of service with almost no effort on your part.  At least.. if you copy and paste this code.

Have a good Friday!!!

Post Navigation