Skip Navigation
Falvey Library
Advanced
You are exploring: Home > Blogs

A More Complex VuFind 2 Unit Test

Okay, much sooner than expected, I found a good candidate for some more complex unit testing: the VuFindCart object that we mocked in the previous post.

Preparing for Testing

As I mentioned in the previous post, sometimes a bit of work needs to be done before classes can be easily tested.

In the case of VuFindCart, the class reads and writes cookie values (since this is how VuFind tracks book bag contents).  This is problematic for testing — the class is dependent on web-specific elements in the environment.  The solution is easy enough, though: add some extra abstraction.

First, we add the ability to override $_COOKIE through an extra constructor parameter: diff.

Next, we create a wrapper function around setcookie(): diff.

Now we can test everything easily even if we don’t have real cookies.  This abstraction may also come in handy in the future if Zend Framework offers its own cookie handling utilities, since we have reduced the number of times that VuFind directly utilizes low-level PHP functionality.

Writing the Test

As of this writing, we don’t have 100% test coverage for VuFindCart, but the work-in-progress test demonstrates a few useful techniques.  You might want to open the full code in another tab so you can look at it as you read the notes below.

First of all, as discussed in the previous post, VuFindCart depends on VuFindRecordLoader.  I use a mock object to satisfy that dependency.  The mock object is created as a property of the test class — this way, if tests need to make assertions about how VuFindCart interacts with VuFindRecordLoader, these can easily be set up.  (I haven’t done this yet, but it will be necessary to get 100% coverage).

I have created a getCart() convenience method which returns a VuFindCart mock object.  This routine uses the second parameter of getMock(), which can be used to specify which methods are to be mocked out.  In this case, we are only mocking out the setCookie() wrapper function, since we don’t want our test to actually call PHP’s setcookie() function.  Everything else will be executing real code from the VuFindCart class.

Most of the actual test cases are simple low-hanging fruit — setting values in the constructor and making sure that the corresponding get methods return them correctly, etc.  It should be fairly self explanatory.

The testFullCart() method might be of some interest — this demonstrates using a series of assertions to confirm that an expected sequence of events happens in the appropriate order (in this case, we are making sure that the cart registers as being full at the right point in time).

The testCookieWrite() method further demonstrates the power of mock objects.  We want to test that adding an item to the cart writes the correct values to the cookie.  Since we have mocked out the setCookie() method, we can set up expectations that it will be called at particular times with particular values — this allows us to test that a call to $cart->addItem() results in the expected calls to $cart->setCookie(), without actually writing any real cookies.  The PHPUnit syntax can seem a bit weird at first, but it’s not really too bad.  In this example, $this->at(0) and $this->at(1) are being used to set up rules relating to the first and second calls to $cart->setCookie; the ->with() portion sets up expectations for particular parameters at each call.

Conclusions

I hope this further demonstrates how testing can be achieved.  As before, please feel free to ask questions!


Like
1 People Like This Post

A Simple VuFind 2 Unit Test

I have said on several occasions that test-writing might eventually become a good way for new developers to contribute to the VuFind project. By increasing our test coverage (and thus future code stability), new tests are a valuable contribution to the software… and the act of studying all the code paths in order to write a test is a good way to learn how things work.

Of course, the reality of the situation is not so straightforward. There is still a lot of code in VuFind that is inherently hard to test, usually due to complex dependencies. At this point in time, the process of writing tests is often also the process of refactoring code to reduce coupling, which in turn makes it more testable — and this often requires a deeper understanding of the system than a newcomer is likely to have. (See my previous blog post for an example of the sort of refactoring I mean).

So testing isn’t always easy…  but still, it can sometimes be straightforward.  Here’s a relatively simple example to demonstrate some of the principles.

Getting Set Up

This post assumes you have a clean copy of VuFind 2 checked out somewhere separate from your production instance, and that you have the necessary tools (PHPUnit, Phing, etc.) installed.  More background on the setup can be found in the VuFind wiki.

What We Are Testing

For this example, we are testing VuFind’s Cart view helper (VuFindViewHelperRootCart).  This is a very trivial piece of glue code whose purpose is to make VuFind’s cart object (which keeps track of user selections in the optional “book bag” feature) available for use in view scripts.  You don’t really gain much by writing a test for this class — it is unlikely to change much and it contains no complex logic — but since it is so simple, it’s a good candidate for this tutorial.  We can get to 100% coverage very quickly.

You can view the full code of the view helper in our Git repository.

There are only two methods: the constructor (which takes a VuFindCart object as a mandatory parameter) and __invoke (a PHP magic method which returns the Cart object when the helper is invoked as if it were a function).

Getting Started

By convention, unit tests (i.e. all tests that exercise VuFind functionality without relying on an active test instance) reside in module/VuFind/tests/unit-tests/src.  Within this directory, test classes are arranged and namespaced to correspond with the code that they test.

This means that we’re going to create a module/VuFind/tests/unit-tests/src/View/Helper/Root/CartTest.php, living in the VuFindTestViewHelperRoot namespace.

All PHPUnit test cases must be subclasses of the PHPUnit_Framework_TestCase class.  VuFind includes some base test classes with additional convenience methods (see the VuFindTest namespace), but this particular test is simple enough that we can simply extend the default base class:

class CartTest extends PHPUnit_Framework_TestCase

Writing the Test

One of the keys to writing an effective test is to avoid doing any work that is not related to the task at hand.  You don’t want your test to fail because of a problem in a different area of the code — this will make bugs harder to locate when something breaks.

We are trying to test something very simple here — essentially we want to be sure that when we put a cart object into the view helper’s constructor, we get the same object out when we invoke the helper.

This is where the “don’t do unrelated work” rule comes in.  We could construct a real VuFindCart object for testing purposes, but then if there was a bug in the VuFindCart constructor, that might cause our test to fail, even though that has nothing at all to do with our view helper.  Fortunately, we don’t have to construct a real VuFindCart object, thanks to PHPUnit’s mock object feature.

With mock objects, we can create objects that serve as placeholders for real classes in our code.  They accept the same method calls and pass the same instanceof tests as real objects, but they don’t actually do anything — unless we configure them to expect particular incoming data or simulate specific responses under specific circumstances.  These are a very valuable testing tool!

In our particular case, we don’t need to do anything fancy with mocks — we just need to call PHPUnit’s built-in $this->getMock() method to construct a fake Cart object.

There’s just one small issue.  If you just call:

$this->getMock(‘VuFindCart’);

You will get an error.  VuFindCart’s constructor expects a VuFindRecordLoader object, and we have to satisfy this dependency even when building a mock.  Fortunately, the third parameter of getMock() accepts constructor parameters for the new mock object, and nothing stops us from creating a mock VuFindRecordLoader to satisfy the dependency.  Thus, we end up with:

$cart = $this->getMock(
‘VuFindCart’, null, array($this->getMock(‘VuFindRecordLoader’))
);

Now that we have our fake cart, the rest is simple…  Just construct a view helper:

$helper = new VuFindViewHelperRootCart($cart);

…and then test that invoke works by making an assertion that invoking the helper will return the same object that we passed to the constructor:

$this->assertEquals($cart, $helper());

Assertions are the most important part of any test — these are what determine whether each test passes or fails.  Never write a test without any assertions!  PHPUnit includes a wide range of assertion methods, allowing you to express many different conditions.

Running the Test

Now that the code is written (full test class available here), it’s just a matter of using your VuFind test instance to run it, as described in the wiki.

After confirming that a new test passes on my local system, I push it to the Git master and then check the code coverage report in Jenkins after everything rebuilds.  In this case, I’m now seeing 100% coverage for the cart helper.

What Next?

I hope this has served as a helpful introduction to some fundamentals, but I realize that most real-life testing is significantly more complicated.  I may try to write an article describing a more difficult test in the future if time permits.  In the meantime, if you want to try your hand at test-writing, feel free to send me questions — I’ll be happy to recommend some areas that might be worth looking at, and I can help with any refactoring that may be necessary.


Like
1 People Like This Post

VuFind, Zend Framework 2, and Flash Messages

One minor weakness of VuFind 1.x is that it doesn’t always provide good feedback for user actions.  One of the advantages to switching to a standard framework is that we can leverage an existing “flash message” mechanism to display messages to users in a consistent way.  Zend Framework 2 includes a simple FlashMessenger helper, but it took a bit of learning to use it as effectively as possible.  I hope that a discussion of my solution to this problem will helpfully demonstrate some aspects of the framework and VuFind 2.x’s architecture.

The Problem

In Zend Framework, flash messages are stored in an object by the controller.  For example:

$this->flashMessenger()->setNamespace('info')
    ->addMessage('Email sent successfully.');

or

$this->flashMessenger()->setNamespace('error')
    ->addMessage('Operation failed.');

 

The view is then responsible for pulling information out of the object and formatting it for display.

I wanted VuFind to display messages in a consistent way, so it seemed to make the most sense to use a View Helper to render flash messages.  This helper could take care of pulling messages from the namespaces in the object, applying translation, and building <div> tags with appropriate classes. This way, all I would have to do is add this code to a template:

<?=$this->flashMessages()?>

 

and my flash messages would display.

There’s just one hard part: getting the object containing the messages from the controller to the view with a minimum of fuss.

Solution #1: Brute Force

My first solution was not hard to come up with, but I was never happy with it.

Controllers in Zend Framework 2 pass values to their corresponding views by returning a value (often a ZendViewModelViewModel object).  If we want to ensure that all of these objects contain a particular value, we can establish a convention where VuFind’s controllers never construct a ViewModel directly; instead, they always call a $this->createViewModel() method defined in an abstract base class.  This helper method is responsible for constructing a ViewModel and then attaching the flash messenger object to it.  Once the ViewModel has a flash messenger attached, the View Helper responsible for rendering the messages can simply pull the messenger from a known named property in the model.

Advantage: It works.

Disadvantage: It’s ugly.  It relies on a particular arbitrary value being in a particular arbitrary place.  It takes away some of the flexibility of the framework by forcing the user to always return ViewModels, and to construct them in a particular way.  It makes the user jump through a hoop (calling createViewModel to create a view model) and punishes them in an unintuitive way if they don’t comply (flash messages stop appearing).

Solution #2: Working with the Framework

Fortunately, there is a better way.

One of the nice features of ZF2 is its service manager system (described in more detail in this blog post).  Service managers are responsible for constructing and storing instances of important objects used by the application.  Ugly application-specific details can be pushed into service manager configuration so that the actual components of the application can be simpler and more reusable.

The flash messenger controller helper and our custom view helper are both constructed by service managers.  This means that, through proper service manager configuration, we can ensure that the flash messages view helper always has access to the flash messenger controller helper, with no need for the intervention of a nasty createViewModel() method.

This is achieved with the help of a factory in our view helper service manager configuration:

'flashmessages' => function ($sm) {
    $messenger = $sm->getServiceLocator()
        ->get('ControllerPluginManager')
        ->get('FlashMessenger');
    return new VuFindViewHelperRootFlashmessages(
        $messenger
    );
},

 

Here’s how it works: service manager factories are passed an instance of the service manager that is doing the object construction ($sm in the example above — an instance of the service manager devoted to view helpers). $sm->getServiceLocator() retrieves the service manager that constructed $sm — the top-level service manager that manages the whole VuFind application. From here, we can call ->get(‘ControllerPluginManager’) to obtain the service manager for controller plugins. We can then call ->get(‘FlashMessenger’) on the controller plugin service manager to get hold of the flash messenger. This object ($messenger) is then passed to the constructor of the new VuFindViewHelperRootFlashmessages helper. The object now has access to the flash messenger as soon as it is constructed. None of the other code needs to know about the details — it just works like magic.

Yes, this is a bit ugly. But the important thing is that the ugliness is now in the configuration of the application — part of the glue that holds everything together. It’s okay for this part of the code to be a bit convoluted and application-specific, because its whole purpose is to make the various parts of the application fit together. The beautiful thing is that those component parts don’t have to know anything about the details of how the glue works — as a whole, the application has become less coupled and more flexible. Compare this to solution #1, where the ugliness was more deeply embedded in the application’s controllers and in the behavior of the view helper itself.

The Code

If you want to see how VuFind transitioned from Solution #1 to Solution #2, take a look at these diffs.

The Bottom Line

There’s definitely a learning curve involved with ZF2, but I continue to feel that it is worth the effort.  I hope this example has shed a little light on some of the issues… but if anything is unclear (and I’m sure something is), feel free to ask questions.  I’m always happy to help, especially if it means that more people are potentially able to help with the process of improving the code.


Like

Moving VuFind to Zend Framework 2: Part 5 — The Dreaded forward() Bug

IMPORTANT UPDATE: The problem discussed in this post only applies to Zend Framework 2.0 RC4 and earlier. Starting with RC5, a fix was introduced which eliminates the double-rendering issue described below. With the fix in place, you can do the logical thing of returning the output of $this->forward()->dispatch() rather than having to return false. Thanks to Nicholas Calugar, Matthew Weier O’Phinney, and all the others on zf-contributors and #zftalk.2 who talked to me about this problem and helped me refine my pull request to fix it.


It’s a law of software development that if you can’t explain some weird behavior and you choose to ignore it, it will inevitably come back to bite you. I know this very well, but knowing it doesn’t prevent it from happening.

The Problem

When I first implemented VuFind’s Flash Messenger view helper (designed to display status messages stored in the session), I ran into a strange problem. The behavior of the helper was simple: check the session for messages, display them, then clear the session. The problem was that flash messages never displayed. Eventually I just commented out the “clear the session” line and everything started working. I didn’t have time to figure out what was really going on, so I just put a note in the code to check it later, and moved on to other things.

The Real Problem

I kept running into bizarre flash messenger behavior, so I decided I needed to solve this problem for real rather than just putting on a band-aid. After digging deep into ZF2 and putting logging lines all over the place, I figured out what was causing the problem: many of my view templates were getting rendered twice. Even though the templates displayed on screen just once, the code was executing twice. This was the reason for my flash messenger problem: the first render displayed the messages and cleared them. The second render replaced the first, and by this point, the messages were gone.

The Root Cause

After a lot more digging and some extremely valuable help from mpinkston on the #zftalk.2 IRC channel, I discovered the root cause. Sometimes you want to forward from one controller action to another. In ZF1, you could use “return $this->_forward(…)” to achieve the effect. In ZF2, there is a controller plug-in called forward() that does the same basic thing. I was using it like this:


return $this->forward()->dispatch('controllerName', array('action' => 'actionName'));

Seems harmless enough, but what actually happens here is that the forward() plug-in dispatches an action, which causes ZF2 to attach a view model to the top-level layout object. The forward() plug-in then returns that view model. When the calling controller returns it, ZF2 attaches a second copy to the layout object. And then both copies get rendered!

What I should have been doing was this:


$this->forward()->dispatch('controllerName', array('action' => 'actionName'));
return false;

A simple, honest mistake with evil, subtle consequences.

The Solution

I’ve already found a solution to my immediate problem, as described above. I won’t make this mistake again, and VuFind’s flash messenger now works correctly. I was concerned that the solution would only work when forwarding to actions that return a ViewModel, but it seems to work for all cases, even when a Response is returned instead (i.e. when redirecting to another page).

I remain concerned that this is a big potential pitfall for users of ZF2 in general. It’s an easy mistake to make, and the consequences are extremely non-obvious, especially since in many cases, the only result of the mistake is a slight performance hit caused by duplicate work being done.

Hopefully this will be addressed in the future either by notes in the documentation or by the introduction of a convenience method in the forward() plugin that returns false instead of the actual output of the dispatched action. If either of these things had existed, I probably wouldn’t have spent the better part of a work day chasing this problem. But I don’t mean to criticize the ZF2 team — I like the framework, and these are the perils of working with software that is still under development. I just point this out as something to watch out for that should be addressed, one way or another, before the final 2.0 official release.


Like

Moving VuFind to Zend Framework 2: Part 4 — Command Line Tools

UPDATE – July 26, 2012: The release of Zend Framework 2 RC 1 makes much of this obsolete since it includes better native console support. I’ll try to post again when I have time to rework everything to use the new functionality, but for now I’ve just patched in a workaround. See comments at the bottom of the article if you are interested.


VuFind is primarily a web application, but it also includes a number of command-line tools for performing various harvest, import and maintenance tasks.  It would be nice if these command-line tools could leverage the infrastructure of the web application so we don’t need to write redundant code for setting up autoloaders, configuring resources, etc.  However, we don’t want to accidentally expose command-line behaviors through the web interface.  Fortunately, Zend Framework 2’s module system makes this fairly easy to achieve.

The Goal

In order to achieve some level of granularity, it would be nice if, when you run any given command-line utility, VuFind routes your request to a special command-line controller whose name corresponds with the directory containing the tool, executing an action corresponding with the tool’s filename.  So, for example, running import/import-xsl.php would call importController::importXslAction().

Naming the Module

Zend Framework 2 modules correspond with PHP namespaces.  For example, the main VuFind module is located in module/VuFind/Module.php, and it defines a VuFind namespace.  All supplementary files living within the VuFind namespace are found under module/VuFind/src/VuFind, and Zend Framework knows how to access them based on the module’s configuration.

When creating the CLI module, we have two options:

1.) We can create a new namespace, such as VuFindCLI, and locate the module in module/VuFindCLI/Module.php with a structure totally parallel to the main VuFind module.  This results in the cleanest directory structure, but the namespacing isn’t very logical, since this is really a subset of VuFind functionality.

2.) We can create a sub-namespace, such as VuFindCLI, and locate the module in module/VuFind/CLI/Module.php.  Because this namespace is a subset of the main VuFind namespace, supplemental files will live in module/VuFind/src/VuFind/CLI rather than module/VuFind/CLI/src/VuFind/CLI — a potential source of some confusion.

I opted for approach #2 — I prefer having logical namespaces at the expense of a little directory irregularity.

Loading the Module

Having decided what to call the module, loading it is simply a matter of modifying the main application configuration (config/application.config.php) to load the CLI module when it detects that it is running in CLI mode:

$config = array(
    'modules' => array(
        'VuFind',
    ),
    /* ... trimmed for clarity ... */
);
if (PHP_SAPI == 'cli') {
    $config['modules'][] = 'VuFind\CLI';
}
return $config;

Creating the Module

The CLI module itself doesn’t need to contain very much content. We need a configuration to tell it how to load CLI-specific controllers (module/VuFind/CLI/config/module.config.php), and a module definition to set up custom routing (module/VuFind/CLI/Module.php).

The routing is a little bit complicated, so let’s look more closely at it. Here is the relevant code from Module.php:

    public function onBootstrap(MvcEvent $e)
    {
        $callback = function ($e) {
            // Get command line arguments and present working directory from
            // server superglobal:
            $server = $e->getApplication()->getRequest()->getServer();
            $args = $server->get('argv');
            $filename = $args[0];
            $pwd = $server->get('PWD', CLI_DIR);

            // Convert base filename (minus .php extension) and containing directory
            // name into action and controller, respectively:
            $baseFilename = basename($filename);
            $baseFilename = substr($baseFilename, 0, strlen($baseFilename) - 4);
            $baseDirname = basename(dirname(realpath($pwd . '/' . $filename)));
            $routeMatch = new RouteMatch(
                array('controller' => $baseDirname, 'action' => $baseFilename), 1
            );

            // Override standard routing:
            $routeMatch->setMatchedRouteName('default');
            $e->setRouteMatch($routeMatch);
        };
        $events = $e->getApplication()->getEventManager();
        $events->attach('route', $callback);
    }

The onBootstrap() method is called automatically on every module. Within this method, we are using the Zend Framework 2 event manager to associate a callback function with the route event. The callback function is defined as a closure.

Within the closure, we need to do two things:

1.) Figure out the directory and filename that were used to access VuFind (the idea here is that every CLI utility will simply be a wrapper that loads the core of Zend Framework with an include statement).

2.) Using this contextual information, force the router to load the appropriate controller by injecting a routeMatch object that matches the ‘default’ route defined by the core VuFind module.

As it turns out, step 1 was a little harder than anticipated. Figuring out the filename that was accessed is easy; PHP’s $_SERVER superglobal (accessible in Zend Framework through the getServer() call) contains an ‘argv’ element representing command-line parameters, and the first element of this array will always contain the base filename. The hard part is figuring out the containing directory. The __DIR__ magic constant is of no use to us, because it refers to the context of the currently-executing file, not the top-level script run by the user. Similarly, the getcwd() function is of no help, because part of the standard ZF2 initialization sets the current working directory to a fixed location.

Some versions of PHP come to the rescue with a $_SERVER element called ‘PWD’ which contains the directory from which the user executed PHP. The problem is that this is not present in every operating system (it is missing in Windows, for example). For lack of a more elegant solution, I eventually settled on defining a constant called CLI_DIR in my command-line scripts so that code deeper in the framework can figure out the context. Hence the code:

$pwd = $server->get('PWD', CLI_DIR);

This attempts to use the $_SERVER[‘PWD’] variable, but if it is not set, it fails over to the CLI_DIR constant. That way, the ugly workaround is only triggered when absolutely necessary.

Putting it All Together

As my first proof of concept, I decided to implement the import/import-xsl.php script. The code inside the script is very simple:

define('CLI_DIR', __DIR__);     // save directory name of current script
require_once __DIR__ . '/../public/index.php';

This just sets the CLI_DIR constant described above and loads the framework.

Now we just need to define a controller to respond to the request. I created a base controller with shared methods that are likely to be used by other CLI-oriented controllers (module/VuFind/src/VuFind/CLI/Controller/AbstractBase.php) and then extended that with the actual ImportController functionality (module/VuFind/src/VuFind/CLI/Controller/ImportController.php).

At this point, all the pieces are in place. When you run import-xsl.php, it loads Zend Framework. Zend Framework detects CLI mode and loads the CLI module. The CLI module overrides the router and directs the user to ImportController::importXslAction(). The controller is able to make use of all the same classes and resources as a web application, and no setup code has been duplicated anywhere.

The Rough Edges

There is one piece of the puzzle that I am not entirely happy about right now. Zend Framework 2 controllers work by building up a ViewModel or Response object and then returning that for further processing. This model does not work well in the CLI environment for two reasons:

1.) CLI tools often need to produce real-time output. Unlike a web request which gets built all at once, a CLI tool will often show incremental details as it works (“loading file 1, loading file 2, etc.”).

2.) CLI tools need to return an exit status to the operating system in order to indicate success or failure, which is critical for incorporating PHP tools into shell scripts and batch files.

Neither of these use cases are currently met through native framework features (at least as far as I can tell). For now, I am simply using “echo” and “exit” inside the controllers to achieve the desired effects, which is functional but less than ideal.

There is hope, though: the Zend Framework community is currently thinking about CLI integration, as evidenced by this Request For Comment. I’ll try to keep an eye on developments in this area, and once the framework has the capabilities we need, the existing code can be more tightly integrated into it.


Like
1 People Like This Post

Moving VuFind to Zend Framework 2: Part 3 — Theme Inheritance

One of VuFind’s most important features is its theme inheritance system, which allows users to customize the interface by creating sub-themes that only override the templates that need to be changed. This helps isolate user changes from the core code and simplifies upgrades.

The Zend Framework 1 Solution

Since theme inheritance is such a core feature of VuFind, it was the first challenge I tackled when adapting the code to Zend Framework. Fortunately, the list8d project had already solved the problem for me and documented it in a very helpful blog post, so I was able to implement the feature quickly. Although VuFind’s implementation adds some features and changes a few details, it hasn’t strayed too far from the original list8d code.

Differences from VuFind 1.x

The biggest difference between VuFind 1.x themes and the list8d solution is that in VuFind 1.x, you had to create a comma-separated list of themes in the configuration file to specify how inheritance worked. In VuFind 2, with the list8d-inspired system, inheritance is controlled by a “theme.ini” file within each theme which tells VuFind whether or not the theme has a parent. The VuFind 2 approach is preferable for two reasons: it makes the config file more concise and easier to understand, and it prevents users from creating illegal inheritance chains by entering invalid comma-separated sequences.

Moving to Zend Framework 2

Now that I am moving from Zend Framework 1 to Zend Framework 2, I have again started by tackling the theme problem. Fortunately, the list8d solution still works, though it requires a few significant adaptations. The remainder of the article will highlight key changes. All of my code is available through Git on VuFind’s Sourceforge project; feel free to borrow anything that you find useful.

Change 1: Exposing Public Resources

The list8d article talks about creating a link under the public webroot to expose theme resources. I instead opted to handle this through Apache configuration:

AliasMatch ^/vufind/themes/([0-9a-zA-Z-_]*)/css/(.*)$
  /usr/local/vufind/themes/vufind/$1/css/$2
AliasMatch ^/vufind/themes/([0-9a-zA-Z-_]*)/images/(.*)$
  /usr/local/vufind/themes/vufind/$1/images/$2
AliasMatch ^/vufind/themes/([0-9a-zA-Z-_]*)/js/(.*)$ 
  /usr/local/vufind/themes/vufind/$1/js/$2

Order allow,deny
allow from all
AllowOverride All

(Some lines wrapped and indented for clearer display)

Through the magic of regular expressions, this ensures that only Javascript, CSS and images are exposed to the public, while other theme elements (like PHP templates) remain private. So, for example, the styles.css stylesheet in /usr/local/vufind/themes/vufind/blueprint/css becomes visible at the URL http://[your-server]/vufind/themes/blueprint/css/styles.css.

Note that the actual file path to the themes may be subject to change. I’m still debating whether themes belong inside or outside the VuFind-specific module (right now I chose outside, since this allows multiple modules to share the same Apache mappings) and whether or not the themes folder needs to be broken into subdirectories for disambiguation (that accounts for the current redundant “vufind” in the path, but I might decide to eliminate it for simplicity at risk of clashing with other modules). Feedback is welcome.

Also note that VuFind comes with an install script that automatically customizes the Apache configuration to adjust VuFind’s base URL and installed path, so you don’t actually have to edit all of this stuff by hand if you use non-default settings.

Change 2: Initializing Themes

The list8d solution proposes setting up themes by implementing a base controller that all other controllers inherit from. This controller’s init() method is then responsible for reading in the theme.ini file and setting everything up (which mostly consists of manipulating the framework’s search paths so it finds the appropriate templates and helpers in the appropriate places).

When I adapted this for my initial ZF1-based VuFind prototype, I tried to make it more stand-alone by creating a Zend Controller Plug-in to do the work rather than embedding it in a base class… but this didn’t really change anything significantly; it just moved the logic from one somewhat obscure place to a different somewhat obscure place.

Fortunately, Zend Framework 2 has a more comprehensible event-driven architecture for plugging things into the workflow. Rather than using base classes or weird plug-ins, you can hook events from a module’s bootstrap method. This allows much better separation of concerns: I was able to create a VuFindThemeInitializer class which does the actual theme startup, and then I attach different methods of the initializer to appropriate events as part of VuFind’s bootstrapping process.

Change 3: Custom Template Injector

One of the features of Zend Framework 2 is that, if no template is explicitly specified, the framework injects a default template name into the view model. This default template name is the namespace of the module containing the controller, then the name of the controller, then the name of the action. That interferes with the theme system — we don’t want the namespace on the template name. I created a custom template injector that eliminates the namespace and (due to my own personal preference) also makes sure that URLs are case-insensitive by stripping out dashes caused by camelCase action/controller names. This is set up as part of the theme initialization routine (see the configureTemplateInjection method).

Change 4: View Helper Loading

The original list8d theme solution simply injects helper paths into the helper broker. The framework then searches up the theme inheritance tree until it finds a matching helper. This is easy (no configuration necessary) but it is also slow (every helper initialization requires a search of the file system). Because ZF2 deals with helpers a little differently, I decided to make helper configuration more explicit. Each theme.ini file now includes a helper_namespace setting which specifies where helpers live, and a helpers_to_register[] array which lists all of the helpers that need to be made available. This explicit configuration is obviously less convenient than “magic” auto-loading, but since adding helpers is a relatively infrequent task, the performance benefits seem to justify the change.

I initially set things up so that themes had their own unique namespaces and the theme initializer found the active Zend Autoloader and informed it how to find the helpers in that namespace under the themes directory. I eventually decided this was unnecessarily overcomplicated and scrapped it — now all of VuFind’s view helpers live in the namespace VuFind/Theme/[theme_name]/Helper (which means their code is inside the VuFind module rather than under the theme directories) and take advantage of the default autoloader settings. I’m reasonably happy with this solution, but it’s not hard to change if a better layout is determined in the future.

Change 5: The Tools Class

As I already mentioned, I set up a VuFindThemeInitializer class to do the work of setting up themes. The initializer in turn needs to know a few things: for example, the base path of the application and the place in the session to persist theme settings (to reduce redundant file accesses). Rather than hard-coding these details into the Initializer, I created a VuFindThemeTools class which provides these details to the Initializer’s constructor. This provides an opportunity for using dependency injection to change default behavior and implement unit tests. It also reduces redundancy, since other classes that need access to the same resources (i.e. theme-aware view helpers) can pull data from the Tools class rather than duplicating the dependency initialization.

Ideally, I should probably define a ToolsInterface to guide implementation of alternate tools classes. It may also make sense to split this class into separate pieces to handle different sets of functionality. For now, I’m just using a single catch-all Tools class to keep things simple; it’s always possible to refactor when all the use cases become more clear.

Change 6: The ResourceContainer Class

In the list8d implementation of themes, you can specify CSS and JS files in your theme.ini file to ensure that they are loaded on every page within a given theme. The VuFind implementation extends this to support favicons as well. In the Zend Framework 1 version of all of this code, the theme initialization not only loads these settings from the configuration file but also parses it and configures the framework appropriately.

This is potentially inefficient — some controller actions won’t ever render a page; others will forward from action to action, causing redundant work to be performed.

When I reimplemented this in ZF2, I added an extra layer. The VuFindThemeInitializer loads the settings from the configuration file into a VuFindThemeResourceContainer object provided by the VuFindThemeTools object. The settings are not actually processed until it is actually time to render a page. At that point, a call to a new HeadThemeResources view helper causes the files to get loaded. As with the changes to helper loading, this extra explicit step is slightly inconvenient, but the performance benefits should outweigh that disadvantage — creating a new layout is an uncommon activity, so adding one step to that process shouldn’t inconvenience anyone too severely as long as the process is well documented.

Change 7: View Helpers

The list8d solution provides a custom HeadLink helper that searches the themes in inheritance order to find the best matching CSS file. VuFind’s solution adds a similar custom HeadScript helper and also adds an ImageLink helper which finds the most appropriate image file. Since all of these helpers use similar logic to locate files, they rely on a shared method in VuFindThemeTools to do the bulk of their work.

Future Work

This is a very young solution, and it’s entirely possible that I’ll run into problems that will require some changes and refactoring. I’m also aware that my class names and file locations may not be ideal, and I’m open to feedback on possible improvements there. Finally, it may eventually make sense to build a stand-alone ZF2 ThemeInheritance module and further separate VuFind-specific behavior from the generic theme-related tools. I don’t think this would actually be a huge amount of work, though for now my first priority is to finish the VuFind 2.0beta prototype. Once that is done and the code has been more thoroughly exercised, it may be worth revisiting whether it can be further modularized for better sharing.

Conclusions

I’m grateful to the list8d team for sharing my work, and I hope that my additions and changes will be of use to others. This has been another long, rambling post, and I’m sure there are some details I failed to touch on. Let me know if you have questions about anything.


Like

Moving VuFind to Zend Framework 2: Part 2 — First Impressions

I’ve now been working with Zend Framework 2 for a few days, and I’m starting to get a feel for how it works. It seemed worth sharing a few initial thoughts.

Namespacing

The use of PHP namespaces is a big change, mostly for the better. VuFind 1.x suffered from a total lack of namespacing. This resulted in haphazard include/require statements at the tops of most files and occasional naming conflicts between VuFind classes and external libraries. When I began adapting the code to Zend Framework 1, I used Zend’s autoloader to eliminate all of the includes and requires and prefixed all classes with “VF_” to achieve a sort of namespacing. Using namespaces in ZF2 requires namespace declarations at the tops of all the files, which brings back some of the noise of the include/require statements. However, unlike includes/requires, namespace declarations do not cause a significant performance hit, and they also are more informative to the reader about the dependency structure of the code. Because namespacing allows you to include classes under aliased names, it also means the code that follows the namespace declarations can be made much more readable than the ZF1-style prefix-heavy code. On balance, I think ZF2 has it right, though some small part of me wishes that some of the repetition of the namespace declarations could be avoided somehow.

Dependency Injection

Dependency Injection is one of the the leading buzz-phrases of Zend Framework 2 (and the PHP development world in general). It’s a simple enough concept: instead of having your classes forge their own connections to their dependencies (i.e. using global variables, calling factories/constructors), you instead “inject” those dependencies through constructor parameters and/or setters. This makes your code more flexible, since you can inject different versions of the dependencies in different situations to change behavior. It also makes testing easier, since you can inject dummy objects to test particular classes without having to worry about lots of external details.

Obviously, the problem with dependency injection is that it makes external code responsible for feeding dependencies to classes, which can lead to lengthy and repetitive code. This can become a burden. That’s where Dependency Injection Containers come in — classes which automatically build sets of related classes with all the dependencies properly injected. And that’s where things can start to get complicated and confusing. I don’t claim to have dug into this topic too deeply yet, though I’m sure I’ll gain familiarity as I work with ZF2, since it uses a Dependency Injection Container for much of its internal functionality.

As far as VuFind is concerned, I don’t plan to go crazy with Dependency Injection. More specifically, I don’t plan on using a Dependency Injection Container unless there is a very strong justification for doing so. Unfortunately, PHP as a language is not ideally suited for DI, so there are trade-offs in terms of complexity and/or performance when trying to automate DI through a container. However, I do plan on investigating places where the general idea of DI (minus the automation of a container) can be used to make local customizations and testing easier. One of the beauties of DI is that you can have the best of both worlds: you can build classes that allow injection of dependencies but also auto-generate those dependencies if none are provided. This is probably the direction the code will take, at least for the first iteration.

Configuration vs. Magic

One of the balancing acts in any framework involves the trade-offs between configuration and “magic.” How much functionality just works by convention, and how much does the programmer have to explicitly set up? Too much magic and you end up with code that’s very difficult to debug, since it’s hard to trace where and why things are happening. Too much configuration and simple tasks become a burden to achieve.

ZF2 seems to have a heavier emphasis on explicit configuration than ZF1 did — a lot of this has to do with the fact that some of ZF1’s “magic” led to performance-related problems; for example, auto-loading from a lengthy search path requires a lot of extra file accesses, which take significant time. I admit that I’ve been spoiled by some of ZF1’s magic features. Obviously, ZF2 doesn’t actually prevent you from using some of the same techniques as ZF1, but I would like to use best practices, and I’m going to try to recalibrate the code to use more configuration as I move forward. That being said, one of my highest priorities is making local customization simple, and if that requires a bit of magic, I’m willing to sacrifice a little performance.

The New View Layer

One of the big changes in ZF2 is a major refactoring of the view layer (and MVC in general). ZF1’s view was a bit of a monolithic beast. ZF2 has broken this into smaller chunks that interact with each other. There are now a lot of moving parts to keep track of, but each part is simpler to understand and individually modify than the previous whole. In any case, I don’t expect that the internals of the view layer will have too much impact on my design for VuFind, and the good news is that the actual templates themselves haven’t changed too much. The biggest non-backward-compatible change I’ve run into is the fact that the URL view helper has changed its parameter list, and this is actually a very good thing: the absolute worst feature of ZF1 views was the fact that generating URLs required an unwieldy call to the URL view helper. In ZF2, these calls are now much more concise and understandable — provided that you create a comprehensive router configuration, which I plan to do.

Conclusions

Obviously, my relationship with ZF2 is still very young, but so far my impression is largely positive, and I’m enjoying the process of moving forward. The biggest downside I can see so far is a general increase in verbosity (through namespacing and explicit configuration) which may slightly steepen the learning curve for working with VuFind. However, I think the benefits of these costs (more readable code, better performance, more reliable extensibility) make it worthwhile, and I hope to compensate for complexity through tutorials in the wiki once the design is a bit more stable.

I hope these ramblings have been informative. Comments are welcome if you feel I have misrepresented anything. If you have any questions, feel free to either post them here or take them to the vufind-tech list.


Like

Moving VuFind to Zend Framework 2: Part 1 – Orientation

As you may already know, I’ve been hard at work on updating VuFind‘s architecture in preparation for the forthcoming 2.0 release. The Why VuFind 2.0? page in the wiki describes some of the reasons for this change. So far, I have been extremely happy with the improvements I have been able to make. I feel that the code is now more concise and readable while offering broader and more consistent features. An alpha release is on schedule for early July.

Of course, life with software is never simple. A key decision of the redesign was to use Zend Framework as VuFind’s foundation, on the theory that a widely-used, well-understood, well-documented framework would make the codebase more accessible and standards-driven. I still think this is a wise choice. However, the timing is unfortunate, because the Zend Framework community is just about to release version 2.0 of their software, which breaks backward compatibility in the name of progress. VuFind 2.0 has so far been built on Zend Framework 1.x. Since I don’t want the official finished release of VuFind 2.0 to be built on deprecated architecture, I have some work to do.

I will still release the ZF1-based 2.0alpha in July. It provides a working demonstration of how VuFind 2.0 will look from the outside. However, I hope to follow that as quickly as I can with a ZF2-based 2.0beta so that the final shape of VuFind’s new architecture will also be available for testing.

The problem with moving from ZF1 to ZF2 is that the new version is still under active development (although it’s getting pretty close to stability by now) and the documentation is still fairly incomplete. There is no simple “translate this to that” guide for moving from ZF1. There are also some new techniques (namespaces and dependency injection, with a touch of event-driven programming) that need to be digested in order to understand the new framework. In all, it’s a bit intimidating.

Thus this series of blog posts: as I work through problems, I’ll try to write about them here in case anyone else is struggling with the same things. For now I’m just providing a little background; things should get more interesting once I get deeper into the code. If you want to learn more, check out the resource links on the ZF1 vs. ZF2 page in the VuFind wiki. The Zend webinars in particular are a really helpful way to start learning about what’s going on in the new framework, whether or not you are familiar with ZF1 — I recommend watching a few if you want to be prepared for what’s coming.

Now I’d better get back to digging into code — wish me luck!


Like

Separating Local Code Customizations in PHP

Background

For the past few months, I have been working on a prototype of VuFind 2.0, a reimplementation of the software based on the Zend Framework. I’m very proud of the 1.x series of VuFind releases, and I think they stand pretty well on their own, but the software has been around long enough to begin outgrowing its initial architecture. This reimplementation is designed to clean up some long-standing messes and make the package even more developer-friendly.

One of the big issues for any open source project is figuring out how to deal with local code customizations. A major benefit of open source is that anyone can change it… but changes can come back to bite you when it comes time to upgrade. There are two main strategies that can help alleviate this problem: use a version control system (i.e. Subversion or Git) and try to isolate your changes to separate files rather than changing core files whenever possible. Isolating changes is useful since, even if an upgrade breaks something, it helps you remember exactly what you customized. Version control is of obvious value — if you do have to resort to changing core modules, it helps you keep track of what you did and merge it with future developments.

VuFind already has some powerful mechanisms for isolating local changes from the core — theme inheritance makes user interface customization cleaner, a wealth of configuration file options reduces the need to change core code in many cases, and plug-in mechanisms like record drivers and recommendation modules offer hooks for inserting locally-built code. However, if you need to change some aspect of a core library class, you may still need to resort to editing core code.

The Goal

I have seen packages where you can override classes by copying a core PHP module, pasting it into a different directory, and making your changes to the copy. By taking advantage of a PHP search path that checks the “local” area prior to the “core” area, the package will then load your copy of the file in preference to the core version, allowing you to override the class. While this solution is a step in the right direction as far as avoiding the need to edit core files, it has significant drawbacks — you have to copy an entire class in order to change any one element of it, and when upgrade time comes around, chances are that you’re still going to have to do a significant amount of work to reconcile your locally-copied files with the new core. In fact, I would argue that this solution is actually worse than simply editing the core, since it makes it harder to effectively use version control software to merge changes.

As I see it, a better solution would be to find a way to extend core classes without completely overriding them — i.e. to create a child class that adjusts only the method or methods you need to change, without replacing the entire class. This would encapsulate your local changes in the most concise form possible, and while you still might have to do some reconciliation at upgrade time, good use of object-oriented principles combined with a stable application design could keep problems to a manageable minimum.

The biggest challenge to implementing this is that you run into naming problems. For obvious reasons, PHP doesn’t let you have two classes with the same name. If your core code refers to a class called VF_Search_Object and you want to change the behavior of the getResults() method without editing any other code, how can you do that? Fortunately, there is a way — it’s just a bit tricky.

The Solution

The answer to this problem relies on two key characteristics of PHP: class autoloading and dynamic code generation. With autoloading, PHP has the ability to call a function whenever you attempt to instantiate a class which does not exist. With dynamic code generation, PHP can actually create classes on the fly based on the contents of variables. The trick is to build an autoloader that detects whether or not local customizations have been made and to dynamically generate a new class that derives from either the locally customized version or the original core version as needed.

Still sounds complicated? Fortunately, Zend Framework makes it easier with its powerful autoloader module. The Zend Autoloader gives you a great deal of control over how classes get autoloaded. It can be configured to look at different class name prefixes and load those classes from different directories… or even call different custom autoloader functions. To solve our problem, we need to set up three different class name prefixes:

Core – Any class that begins with “Core_” is core code. Users would never want to directly edit any of these files.

Local – Any class that begins with “Local_” is localized code. Normally these would only exist when a user wanted to customize some piece of functionality, and they would extend a Core_ class with the same name suffix (i.e. the “Local_Example” class would extend the “Core_Example” class).

Extensible – Whenever any code instantiates a class, it will use the “Extensible” prefix instead of “Core” or “Local” — this is how the magic happens, since there should be no classes in PHP files on disk whose name begins with “Extensible_” — instead, the classes will be created dynamically as needed.

Here’s the code that sets this all up using the Zend Autoloader:

$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('Core_');
$autoloader->registerNamespace('Local_');
$autoloader->pushAutoloader('extensibleAutoloader', 'Extensible_');

Very simple — “Core_” and “Local_” are registered as standard namespaces within the autoloader, which means that they will be searched for on disk. “Extensible_” is registered as a special namespace that needs to trigger a custom autoloader called extensibleAutoloader. Here’s the code for that function:

/**
 * Autoloader that allows optional local classes to extend required core classes
 * seamlessly with the help of a particular namespace.
 *
 * @param string $class  Name of class to load
 * @param string $prefix Class namespace prefix
 *
 * @return void
 */
function extensibleAutoloader($class, $prefix = 'Extensible_')
{
    // Strip the class prefix off:
    $suffix = substr($class, strlen($prefix));

    // Check if a locally modified class exists; if that's not found, try to load
    // the core version.  If nothing is found, throw an exception.
    if (@class_exists('Local_' . $suffix)) {
        $base = 'Local_' . $suffix;
    } else if (@class_exists('Core_' . $suffix)) {
        $base = 'Core_' . $suffix;
    } else {
        throw new Exception('Cannot load class: ' . $class);
    }

    // Safety check -- make sure no crazy code has been injected; these have to be
    // simple class names:
    $base = preg_replace('/[^A-Za-z0-9_]/', '', $base);
    $class = preg_replace('/[^A-Za-z0-9_]/', '', $class);

    // Dynamically generate the requested class:
    eval("class $class extends $base { }");
}

As you can see, it’s actually pretty simple — extensibleAutoloader() takes advantage of the regular autoloader in combination with “class_exists” to check whether or not localized versions are available. This tells it which base class needs to be extended in order to generate the requested Extensible_ class… then it uses the eval() function to dynamically create the class.

So imagine you run this code:

$z = new Extensible_Sample();

If you haven’t created a Core_Sample or Local_Sample class, you’ll get an exception. But suppose you put this Core_Sample class into your library:

class Core_Sample
{
    public function __construct()
    {
        echo 'I am a rock.';
    }
}

Now instantiating the Extensible_Sample object will display “I am a rock.” on screen — the autoloader will find and load Core_Sample but name it Extensible_Sample.

Let’s take it a step further and create a Local_Sample that extends Core_Sample:

class Local_Sample extends Core_Sample
{
    public function __construct()
    {
        echo 'Some people may think that ';
        parent::__construct();
    }
}

Now the Extensible_Sample object will display “Some people may think that I am a rock.” Magic!

Conclusions

I’m very happy to see that it is actually possible to achieve this effect — it’s something that I’ve been thinking about for a long time, and I’m happy I was able to make it work. That being said, I’m not sure if it’s worth the effort. I see three major drawbacks:

– This is a powerful mechanism for extending code IF YOU UNDERSTAND IT. But it increases the learning curve for getting into the codebase, since at a glance it will be very confusing to see all these references to Extensible_* classes that don’t actually exist on disk.
– All of the autoloading involved in the solution adds some overhead to the code. I haven’t done testing to see how significant the overhead actually is… but without some kind of caching or PHP acceleration, I have a feeling it might turn out to be somewhat expensive.
– The eval() function is one of the most dangerous features in PHP, since it provides an opportunity for attackers to execute arbitrary code. I believe that the way I’m using it here is safe (especially with the extra regex cleanup I’ve added), but it nonetheless makes me a little nervous.

I would love to hear what other people think of this — is the solution technically sound? Is the benefit worth the cost? At this point, I’m not necessarily committed to implementing this as part of VuFind 2.0 (and obviously the namespaces won’t be “Core_”, “Local_” and “Extensible_” if I eventually do). It could be done, though, and I think it’s worth considering. All feedback is welcome!


Like

Expanded ILS Functionality in VuFind

VuFind uses simple PHP classes called ILS drivers to communicate with external integrated library systems in order to obtain information and perform actions that are outside the scope of its own index and database. This includes things like listing a patron’s checked out items or determining whether books are currently on the shelf. In the past, VuFind’s drivers have been fairly week with regard to important patron activities like placing holds and renewing books. Several libraries have implemented local customizations to support these features, but the native support involved, at best, linking off to a page in a third-party OPAC.

With the forthcoming VuFind 1.2 release (date not yet determined, but probably late summer or early fall), all that will change. The VuFind driver model has been updated with robust support for expanded patron functionality (thanks largely to the tireless efforts of Luke O’Sullivan, who has been collaborating with me for months on this problem). The ILS Driver Specification has already been updated to reflect the new features, but since this is somewhat complicated, I thought a more narrative explanation of how the new features work might be beneficial.

This article is designed to explain exactly what you need to do to add hold, recall and renewal functionality to your ILS driver. It will also touch on some of the infrastructure changes in VuFind needed to support these new features, and some general best practices for extending drivers. As always, if you want more detail on anything, you are free to contact me through comments on the blog or the VuFind mailing lists.

Basic Principles

One of the complicated things about implementing a generic system for dealing with things like holds and renewals is that different systems have different capabilities and rely on different data in order to achieve these actions. Our design tries to keep as much logic inside the ILS driver as possible. VuFind interacts with the driver in two key ways:

• It queries the driver (by checking for the existence of certain methods and/or using the getConfig method) to determine which features are available. Unsupported capabilities will simply be hidden from the end user.
• It tries to feed the driver with its own data as much as possible. In many cases, the inputs to some methods are outputs from other methods. VuFind makes no assumptions about the contents of the data — it just pushes it to the appropriate places. Associative arrays and delimited strings are the driver author’s friends — these can be used to encapsulate whatever data the driver needs, and VuFind will make sure they end up in the right places. This should all become more clear when you see some examples below!

The Least Common Denominator

As mentioned earlier, the simplest way to support advanced ILS features is to simply link to the ILS’ native OPAC. This does not generally provide a good user experience, but sometimes it is the only option. There are several methods you can implement if you want (or need) to settle for this minimal level of functionality:

getCancelHoldLink
getHoldLink
getRenewLink

While getHoldLink has been around for a long time, the other two methods are new… and both of them demonstrate the “driver using its own data” principle discussed above. getCancelHoldLink is fed with an entry from the array returned by getMyHolds, while getRenewLink is similarly fed from getMyTransactions. This is very convenient: when you’re retrieving information from the ILS about current holds or checkouts, it’s easy enough to pull whatever details are needed to link to the system’s OPAC… then you simply assemble it into a URL in the getLink method and you’re done!

Placing Holds Inside VuFind

Obviously, the ideal solution is not linking to a legacy system; it’s filling out a form within VuFind itself. Fortunately, this is now achievable. It requires a few methods to be implemented:

getConfig – Before offering holds functionality, VuFind will call the getConfig method with a parameter of “Holds”. As the driver spec describes in more detail, the method needs to return an associative array containing entries VuFind uses to render the hold form correctly. It is up to you whether to hard-code these values in your ILS driver or pass them along from the driver’s .ini file. The most critical key is the “HMACKeys” value, which tells VuFind which form fields to use in generating an HMAC message authentication code that helps prevent users from placing holds on items that they are not supposed to. If you omit HMACKeys, VuFind will assume that native holds are disabled and will fail over to the getHoldLink approach.
getHolding – Chances are you already have a getHolding method in your driver, but you may need to augment it with some extra fields in the return array if you need extra data to place holds (for example, a “hold” vs. “recall” status, or an item ID in place of a bib ID). Fortunately, you can include any field of the getHolding return array as part of getConfig’s HMACKeys list in order to ensure that it is passed along to the placeHold method below. This allows you to pass any or all necessary data without VuFind having to know exactly what is needed! If possible, you should also make sure that your getHolding array includes the “addlink” key indicating whether or not the current user is allowed to place a hold on the current item — this key makes it possible to use the “driver” option in config.ini’s Catalog:holds_mode setting, which is usually the smartest way for VuFind to present links.
placeHold – This method receives an associative array containing patron information from patronLogin along with whatever hold form fields were activated through the settings returned by getConfig. It is responsible for actually placing the hold and then returning a success or failure status.

There are quite a few small details to line up here, but the important thing is that the driver specifies what data is needed, provides all of that data, and then uses it to place the hold. All VuFind does is pass the messages from one place to another!

…and the rest

If you understand how holds work, the other new features are very similar, only slightly less complicated. A quick summary:

• To cancel holds, implement getCancelHoldDetails (which generates an identifier string using data passed to it from getMyHolds) and cancelHolds (which actually cancels holds based on patron data and an array of strings generated by getCancelHoldDetails).
• To renew items, implement getRenewDetails (which generates an identifier string using data passed to it from getMyTransactions) and renewMyItems (which actually renews items based on patron data and an array of strings generated by getRenewDetails). Also be sure that getMyTransactions includes an appropriate “renewable” key in its return array.

A Final Word on Object Orientation

That covers how to make holds work… but there’s one more detail that may affect driver authors. It is often the case that an ILS requires a version upgrade or a for-pay API plug-in to support these advanced features. In these situations, some users may want the full functionality, while others may require a more stripped-down version that only supports basic features. This is certainly the case for Voyager, where Voyager 6 users will have to settle for the old getHoldLink functionality while Voyager 7 users may have access to a RESTful API that allows every imaginable bell and whistle. Fortunately, PHP’s object-oriented model offers a simple solution: implement minimal functionality as a base class, then override and add methods in a child class to expand functionality.

The Voyager.php and VoyagerRestful.php drivers are an example of this technique in action. Similar work has been done for Horizon users with and without access to its XML API.

One useful design pattern you may notice if you look at the code for these existing drivers is that large chunks of key methods have been broken out into support methods: one that generates SQL in an abstracted associative array format and one that processes the database response. This makes it relatively easy for a child class to inject a couple of new fields into a query or process data slightly differently without having to copy and paste a large, complex method from the parent class. This design pattern is not only useful for implementing holds functionality; it’s also very handy for making minor local customizations to drivers.


Like
1 People Like This Post

« Previous PageNext Page »

 


Last Modified: June 2, 2011

Ask Us: Live Chat
Back to Top