12/13/2013 Update: I’ve added a first run at a feature plugin for the idea proposed below to GitHub. Feel free to play with the code, make improvements, or suggest alternatives!
I love WordPress. I also love MVC design fundamentals. Unfortunately, these two loves of mine are at odds with one another, and that makes me sad.
WordPress, at its core, is a procedural, template-driven framework. This is a good thing, as procedural code is easier for many to learn than object-oriented code. The templatey goodness of the framework helps contribute to a very DRY front-end without forcing developers to learn a convoluted[ref]This is a dangerous statement as many consider the WordPress theme structure to be fairly convoluted. If you start small, though, you can get away with a single template file and abstract from there. This option is, in my view at least, far easier to work with than many others.[/ref] framework. At the same time, it introduces a bad practice into theming by way of global variables.
My suggestion is to rewrite [cci]get_template_part()[/cci] just a bit. I’ve actually done this already for a client project, and the new approach works wonders!
An Alternative
Right now, [cci]get_template_part()[/cci] is very simple. It scans the active theme (and parent theme, if applicable) for a specifically-named template file and, when found, [cci]include()[/cci]s it in the site’s execution. Simple, straight-forward, easy-to-use.
Just not very MVC-like.
The alternative I built for my client used a namespace[ref]This is a PHP 5.3 feature and, so long as WordPress’ minimum requirement is PHP 5.2, I can’t just roll my client’s code into WordPress core. No shortcuts. Sorry.[/ref] to redefine the function within the scope of the theme alone – in other words, I replaced [cci]get_template_part()[/cci] without hacking core.
This new function still matched the standard WordPress function’s behavior; it also augmented it in a way.
WordPress would still scan the theme (and parent, if applicable) to find the appropriately-named template file. Rather than include the file directly, the new function wraps the template file in a [cci]Template[/cci] object. This object’s [cci]->render()[/cci] method is then invoked, which in turn includes the template as before.
Why is this important exactly?
The [cci]Template[/cci] object defines two protected fields, [cci]$context[/cci] and [cci]$model[/cci]. I’ll get back to the context parameter in a moment.
The Model
The template’s [cci]$model[/cci] property is typically null. However, the new [cci]get_template_part()[/cci] function accepts an optional third parameter of any type. You can pass any object or variable you want as this argument and it automatically becomes available as [cci]$this->model[/cci] within the scope of your template file.
You can explicitly pass a data model to the template file, your “view,” without the need to use an ugly global variable.
The Context
The [cci]$context[/cci] object is an instance of a special [cci]TemplateContext[/cci] object. This object includes a few helpful things:
- A reference to the current [cci]WP_Query[/cci] instance
- A reference to the [cci]$_REQUEST[/cci] supergobal
- A special, user-defined [cci]$temp_data[/cci] array
Not only can you reference your no-longer-global data model, you can now reference [cci]$this->context->query[/cci] rather than a global [cci]$query[/cci] variable!
The temporary data array can contain anything you want. Think of it as a custom go-between for additional contextual data you might need in a view that’s not part of the model. The sky’s the limit.
My Dilemma
Anyone who’s worked with an MVC framework can likely see the benefit this kind of change would have for WordPress. At the same time, it’s drawbacks would be minimal. The new function signature is essentially identical to the old one – the two new parameters it takes for the model and temporary data array are optional – so the new logic is a drop-in replacement for the old.
Your themes will still work. Your plugins will still work. Anything hooking on to the [cci]get_template_part_{slug}[/cci] action will still work.
My dilemma isn’t backwards compatibility – my dilemma is how to get this code into your hands for testing.
WordPress’ new rapid release pattern encourages introducing new features as plugins. I want to, but I’m not quite sure how to make that work …
In my client’s site, I replaced [cci]get_template_part()[/cci] by redefining it in a proprietary namespace – this isn’t an option for a general WordPress release. The structure of the [cci]general-template.php[/cci] file also makes it hard to hook in and replace the functionality of the function without also breaking any plugins (or themes) that also hook in there.
Alternatively, I could just ship this as a core patch, but that makes testing more difficult for developers now accustomed to the plugins-as-features approach, meaning the patch will likely grow stale on Trac.
So now I reach out to you: How can I replace [cci]get_template_part()[/cci] with a plugin so you can test the new functionality without hacking core? Alternatively, would you be willing to test this functionality via a patch instead?