Gerrymandered Code: Extending Zend_Registry to globalize session vars

by Chris Renner on 21/01/2010

In my most recent post I talked about putting my toes in the Zend Framework water with stand-alone usage of Zend_Cache. This has been a great experience, and since writing the post I’ve been updating my main application with more caching anywhere I can see a benefit.

In the past couple of days I waded a little deeper into ZF, installing Zend_Registry (also stand-alone) and using it to globalize configuration variables that I was previously loading into $_SESSION in order to access throughout my scripts and inside classes without having to pass them explicitly.

I’ve been using $_SESSION like this for a couple of years, and I’ve known the whole time it was a band-aid approach to solve the problem. Such an approach is not an ideal “design pattern.” Though it passes my baseline rule–it works–there are downsides. The two most obvious are that 1) there’s nothing to stop me from accidentally overwriting a session var inside some script or method, and 2) there’s no sense loading up the session handler with stuff that really shouldn’t be in there.

Zend_Registry is a very lightweight module (one file), that is actually an extension to the native ArrayObject PHP5 class. Any variable, object, or scalar you load into Zend_Registry instantly becomes available globally throughout your script. Let me prevent confusion by saying there is no persistence across page loads–this is just for within each request–but the upside is any scope issues are swept away in a very elegant manner. Hint: Combine Zend_Registry with Zend_Cache to achieve persistence.

The Registry design pattern is not unique to ZF, you can even write your own Registry class if you care to.  I’m still not fluent in OOP-speak, but I understand Zend_Registry is implemented by default as a Singleton.  I know there’s a lot of trashing of the Singleton pattern out there in programmer land.  However, I don’t get too hung up on such metaphysical debates…not to say there isn’t merit in the argument, but for me and my work it is not a practical concern.

I refactoring my main app with Zend_Registry in place of my rat’s nest of $_SESSION['config']['foo'] and such…which is mostly just things like email addresses for the system admin (me) and other global variables that may come in handy in any script or method.

As with my previous ZF article, I’ll leave the details to the fine folks at Zend (see Zend_Registry documentation here), but the implementation of Zend_Registry in this fashion can be exactly this simple:

Zend_Registry::set('admin_email', 'chris.renner@company.net ' );

And then later in your script (or function or object method or wherever) you return the value by calling:

Zend_Registry::get('admin_email' );

Wow, how elegant! You can also access the registry as an object or as an array. For my needs, the static setter/getter methods are perfect.

Netbeans’ Find and Replace features made it relatively simple to update my code with the new Zend_Registry calls.  I guess the “web developer” jargon for that would be “I refactored my code base.”

Problem solved, right?  Well yes and no.  The pesky need for globalized config vars has been solved, but there is a related problem.  How did I ever get the idea to store config variables in $_SESSION?  Probably the same way most of you reading this did in your newbie days:  I was already using $_SESSION to store actual, legitimate session variables at user login, and then access those variables through my code.

gerrymanderIn addition to being a code jockey, I am a politics junkee and also the owner of a second major in Political Science (I know, its so rare to find someone working in a field that is wholly unrelated to their degree…).  It occurred to me there is an analogy to be made between a  Gerrymandered congressional district and what the pros call “spaghetti code.”  I think my amateur-ish use of $_SESSION as a global band-aid amounts to what one might call “Gerrymandered code.”  I am hereby coining the phrase!

The obvious solution to replace all the $_SESSION tags littered throughout my code is to simply load $_SESSION into Zend Registry like so:

Zend_Registry::set('session', $_SESSION);

This works, but retrieving session vars proves to be a bit clunky. My reading of the ZF documentation suggests there isn’t a way to access the value of an array element in the Registry in a direct fashion. You therefore have to instantiate Zend_Registry every time you need access to a session var. For example…

$registry = new Zend_Registry();
echo $registry['session']['userID'];

I find this a bit clunky to do every time I need to access my session. I also don’t care for all the extra overhead caused by generating an object for each one of these cases.

My solution was to extend Zend_Registry with another class that can access an array element inside the registry and return it directly. Here’s what I came up with:

/**
 * A simple extension to Zend_Registry to allow direct access to array objects
 * and object properties inside the registry
 * @author Chris Renner http://www.chrisrenner.com
 */
class App_Registry extends Zend_Registry {
 
    /**
     * Constructs a parent Zend_Registry with default
     * ARRAY_AS_PROPS to allow access as an object
     *
     * @param array $array data array
     * @param integer $flags ArrayObject flags
     */
    public function __construct($array = array(), $flags = parent::ARRAY_AS_PROPS) {
        parent::__construct($array, $flags);
    }
 
    /**
     * getter method for directly accessing value of an array element stored in a registry index
     *
     * @param string $index - the array we're accessing
     * @param string $key - the element we want to access
     * @return mixed
     */
    public static function getFromArray($index, $key) {
 
        $array = self::get($index);
 
        if(is_object($array)) {
            $var = $array->$key;
        } elseif(is_array($array)) {
            $var = $array[$key];
        }
 
        return $var;
    }
 
    /**
     * setter method for directly modifying value of an array element stored in a registry index
     *
     * @param string $index - the array we're accessing
     * @param string $key - the element we want to access
     * @param string $val - the value we want to assign
     * @return mixed
     */
    public static function setInArray($index, $key, $val) {
 
        $registry = self::getInstance();
 
        if(is_object($registry[$index])) {
            $registry[$index]->$key = $val;
        } elseif(is_array($registry[$index])) {
            $registry[$index][$key] = $val;
        }
 
    }
 
}

As you can see, there are just two methods. getFromArray returns the value of an array element or object property from the registry. You just need to supply the Zend_Registry index and the key/property name you want back.

setInArray is used to update an existing property or array element. Supply the Zend_Registry index, the property/key name, and the value you want to assign.

So now, if I want to get the userID from the session (We still set the session array into the registry using Zend_Registry::set() as above), I can call a new static method like this:

echo App_Registry::getFromArray('session', 'userID');

…and to change the value I simply call this:

App_Registry::setInArray('session', 'userID', $newValue);

Obviously this is a very rough class.  It could use more polish, like error handling in case we’re trying to access something that is neither an object or an array.  This can be used to access elements/properties for any array or object you might pass into the registry (not just session vars).

You might ask why not just use constants to globalize config vars?  You certainly could in a simple application.  If you want to change those vars in the script or store an array, you’re out of luck.

I’m curious to see what readers of this post might think, if you have suggestions for improvements or better ways of doing the same thing.  Thanks for reading!

There are 14 comments in this article:

  1. 22/01/2010CD says:

    I’m sure I’m just missing over something obvious here, but are you simply moving your entire session out of $_SESSION and into the registry… in a session array? Or are you splitting your config and standard session into two separate data sets in the registry?

  2. 22/01/2010Chris Renner says:

    Two separate data sets is the short answer.

    The long answer is that for my config vars I’m setting them directly into the registy, each as its own registry index. For the session vars, I’m just pushing the whole array in as a single registry index, then using my wrapper class to return specific values.

    Thanks for reading.

  3. 22/01/2010Derek M says:

    Well, one quick thing I saw… why have a function getFromArray() and setInArray()…. that implies that you need to know you are accessing an array datatype prior to calling the function. You could simply overload the parent function:

    public static function get($index, $key = null) {

    $var = parent::get($index);

    if(!is_null($key)) {
    if(is_object($array)) {
    $var = $var->$key;
    } elseif(is_array($array)) {
    $var = $var[$key];
    }
    }

    return $var;
    }

    Now you always use the same getters and setters…

  4. 22/01/2010Chris Renner says:

    Good point, and that would simplify the calls even further. I think I was worried that some time in the future I may have a need to call the parent getter and setter from within the wrapper class. I’m not sure why that would ever be necessary, however.

  5. 25/01/2010Chris Renner says:

    Derek, I implemented your suggestion this morning. It works fine, if you remember to change the references to self::get and self::set in the two methods to Zend_Registry::get and Zend_Registry::set, respectively. Otherwise the methods just loop back on themselves. I’m not sure there’s a “better” way between the two styles, just coder preference from where I stand. Nevertheless, I am going to keep it the way you suggested simply to shorten my calls. Since the whole point of the post was simplifying my code, it just makes sense.

  6. 26/01/2010Wil Moore III says:

    Interesting article…thanks for sharing. I do have one comment and a question respectively:

    1. You mentioned the trashing of the “singleton” pattern. Sometimes those that make arguments do it out of context. There really is no problem with the singleton pattern per-se; however, the problem is with developer mis-use/over-use of the pattern. The registry pattern is one good way to apply the singleton pattern. Another good use of the singleton pattern is with having a single entrypoint to a database connection object.

    2. I have to ask: why did you chose the solution written about over simply using Zend_Session/Zend_Session_Namespace? I could be missing something but I’m not sure I understand the merit.

    Again, thanks for sharing and your article was well-written.

    -Wil Moore III

  7. 26/01/2010Chris Renner says:

    Hi Wil and thanks for the comments.

    Interesting question you ask re: Zend_Session_Namespace. I am actually using it too, but for simplicity didn’t discuss it in my post. I’m handling all session vars with Zend_Session_Namespace and then loading the namespace into Zend_Registry as an object.

    I chose this approach because my wrapper class still gives me a one line, one statement way to access the namespace properties. To access using Zend_Session, I have to intantiate the namespace first, which creates a second (unnecessary) line of code in every place I need to access the session.

    I realize my approach is overkill, but so far I’m pleased with the result.

  8. 26/01/2010thinsoldier says:

    http://php.net/manual/en/reserved.variables.globals.php

    If you needed config vars defined in global scope to be available within functions/class methods then all you had to do was use $GLOBALS['varname']; or the global keyword (http://php.net/manual/en/language.variables.scope.php)

    I used to keep all my config vars in a globally defined $CONFIG array and access them like:
    $conf = getConf();
    echo $conf['dev']['email']

    Or write all your classes with dependency injection in mind and make sure you pass $CONFIG to the constructor for classes that need it when you instantiate objects from them.

    I also recently switched to using a singleton config class but I still access it through a function that returns the reference to the object.
    echo Config()->Admin['email'];
    Config()->NewsletterUrl = ‘foo.com/newsletter’;
    // no, a new object isn’t created every time the config function is called

    Mine still has issues tho. I’ve run into problems getting it to work with static calls (Registry::get(‘foo’)) and trying to make a class that extends it still returns the base registry class instead of the child class…

  9. 26/01/2010Chris Renner says:

    Thanks for adding your thoughts Thinsolder. I considered going the dependency injection route. I already have that with my db connection object being passed to most of my classes. I just find that approach messy and it adds properties to the objects that don’t really need to be there all the time and in every instance. Particularly if it is a value that is only needed in one method, and/or if it will be called statically, I like my approach better.

  10. 4/05/2010latest gadget says:

    in the new zend
    Zend_Session_Namespace is completly changed

    Any idea how it will work

  11. 4/05/2010Chris Renner says:

    No but I’ll work with it and let you know.

  12. 5/05/2010Chris Renner says:

    I had to go back and check what ZF version I’m running…and it happens to be 1.10.3

    Latest is 1.10.4 but it doesn’t look like there were any changes to Zend_Session on the latest release.

    So far there’s no issues between my extension class and ZF 1.10

  13. 3/06/2010XPerez says:

    Hi Chris.

    Recently, I have created a new singleton class (datastorage), similar as Zend_Registry, but with namespaces and persistence included.

    Would be interesting in extends Zend_Registry to make the same as my datastorage class ? Can be done ?

    Thanks,

    Xavier Pérez

  14. 3/06/2010XPerez says:

    Sorry, I forgot to include the url for datastorage clas:

    http://code.google.com/p/datastorage/

    Xavier Pérez

Write a comment: