FALVEY MEMORIAL LIBRARY



You are exploring: VU > Library > Blogs > Library Technology Development

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 VuFind\Theme\Initializer 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 VuFind\Theme\Initializer 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 VuFind\Theme\Tools 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 VuFind\Theme\Initializer loads the settings from the configuration file into a VuFind\Theme\ResourceContainer object provided by the VuFind\Theme\Tools 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 VuFind\Theme\Tools 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

 


Last Modified: June 20, 2012