First Look at Themosis, a Framework for WordPress Developers

Bruno Skvorc
Share

My dislike of WordPress is no secret. I look down on its mess of a code base, and advise anyone with any technical knowhow whatsoever against using it. But I don’t just bash it for the sake of bashing, I bash it in the hopes that someone who knows what they’re doing will pick up the baton one day and continue the popularity race in a more logical, sensible pace. Any attempt to fix the mess WP presents us with day in day out is, in my book, a good one.

Not too long ago, we heard of a new project on the horizon called Themosis, a “framework for WordPress developers”. In this piece, we’ll try and see what Themosis is, how to get started using it, and we’ll take a look at an example project built with it – the example provided by Themosis themselves.

What is Themosis

To anyone familiar with the word “framework”, especially in the context of PHP, this might sound confusing. Isn’t WP already packed full with stuff we don’t need? Aren’t frameworks usually built on a minimalistic set of principles and decoupled components, and complex WordPress-like apps built on top of them, instead of the other way around? Well, yes.

Themosis isn’t a framework in the full sense of the word as you know it. Instead, it’s an API that ties into WP on a level required to make it easier to develop in – but doesn’t necessarily make it lighter. Themosis is a set of APIs that you use to create WordPress components in a modern-PHP format with namespaces, classes, anonymous functions and Composer support.

Themosis is, quite literally, an MVC powered Laravel-ish WP plugin itself that’s used to write other plugins. It also has its own router so you can define routes Laravel-style, and its own templating engine – Scout – that’s similar to what we’re used to in Laravel and Phalcon – with some added extras for built in WordPress support. For example, the template loop:

@loop(array('post_type' => 'post', 'posts_per_page' => -1))

    <h1>{{ Loop::title() }}</h1>
    <div>
        {{ Loop::content() }}
    </div>

@endloop

uses WP API to get to the data it requires, and the Loop class is designed only to be used with loops for underlying WP content.

Installing

You install Themosis by using Composer and having the WP command line tool installed. Then, when a Composer project is created for Themosis, the latest version of WP is pulled alongside it, so all you have to do is run the standard install script for WP. After that, you can begin developing with Themosis.

You can follow their installation instructions to get the first instance up and running, though perhaps it’s best if you experiment on the example project first (see below).

Example project

Themosis offer an example of an app on their website – a bookstore app.

Trying it out

To try it out, you can use this Quick Tip to get a Homestead Improved box up and running. Then just register a new site with:

- map: bookstore.local
      to: /home/vagrant/Code/themosis_example

Of course, don’t forget to add the bookstore.local alias to your host’s hosts file as described in the Homestead Improved Quick Tip.

Clone the repository with git clone https://github.com/themosis/bookstore themosis_example, and visit http://bookstore.local:8000 in your host’s browser to get to the installation procedure.

Go through it, and follow the rest of the instructions here. Now you can play around as much as you want without worrying about breaking something – should something go wrong, we can just rebuild the VM and repeat the process.

Once installed as per instructions, you can give the app a go.

At this point, this is nothing impressive – any WP site can be configured to look and work like this. However, I noticed two key points:

  1. We got it to run fairly quickly, it was a real no-hassle job.
  2. It’s pretty fast. In fact, it doesn’t perform nearly as slow as I would have expected, Themosis having an extra layer or two to go through to get to the underlying WP API.

What about the code, though? What does that look like?

The Code

You can check out the code on the bookstore’s repository: https://github.com/themosis/bookstore. Let’s see what we can discern. Like I said above, Themosis is actually a plugin, so we need to look for the bookstore’s code among the plugins:

Opening this, we can find three main subfolders. app contains the logic of our Themosis-powered WP layer, src contains the sources of Themosis itself (which, if you look through them, you’ll notice bear a striking similarity to class names and folder structures of other, more popular frameworks), and we all know vendor – the place where everything Composer-fetched goes.

Regarding the actual quality of the code, while certain aspects of it are, most definitely, modern and up to today’s standards (namespaces, classes) and all of it is beautifully documented with return types, parameters and the whole nine yards, there are (and I say this fully aware that I’m nitpicking) some primitive relics as well, like the <?php defined('DS') or die('No direct script access.'); line used for preventing direct access to PHP scripts, or closing PHP tags at the end of PHP files (unnecessary).

However – this isn’t the only place where our code lies. Themosis is fragmented across two locations – everything related to routing and presentation goes into the theme, while everything to do with our application logic is placed in the plugin’s folder.

MVC

Looking at the MVC structure, I can say it’s well wrapped. Sure, it would be nice if it weren’t this fragmented (across two parent folders), but the structure is there. The routes folder is in the app folder, as in Laravel, and accepts similar syntax for routing – under the hood, it’s just an enhanced conditional tag. The controllers are there, and neatly separated from their views in true MVC fashion, whereas models are used from the controller and their returned values passed to views. From home.controller.php:

return View::make('pages.home')-&amp;gt;with(array(

			'promo' 	=&amp;gt; Books::getPromoBook($this-&amp;gt;page),
			'books' 	=&amp;gt; Books::getPopularBooks($this-&amp;gt;bookId),
			'news'		=&amp;gt; News::get(),
			'newspage'	=&amp;gt; get_page_by_path('news')

		));

As simple as it gets. Views are traversed with the dot-notation, folder-wise, and their scout suffix is ignored but required, so pages.home leads to app/views/pages/home.scout.php – all quite straightforward.

Routing with params

Looking at some of the routes, the simplicity is immediately apparent. For example, let’s take the Search page. This is a page so simple, it literally needs only to call WP’s underlying search mechanism, so no controller is necessary:

// Search page
Route::is('search', function(){
	return View::make('search', array('search' =&amp;gt; $_GET['s']));
});

The root view search is rendered, the search GET parameter is passed in (though it should probably be filtered), which then references the results returned by the search function automatically, and loops them with the Loop class mentioned above – the Loop is a simplified, more approachable Loop.

I like this due to not only simplicity, but also the fact that it abstracted WP’s search function which is, by default, abysmally awful. This makes replacing it much easier.

You can see more info on routing here, or just look at the examples.

Defining a Custom Page with Controller + View

Let’s see if creating a custom page with a view-controller combination is as easy as advertised.

First, we’ll put a new route into routes.php:

Route::only('page', 'test', 'test@index');

Then, we create a new view under pages called test.scout.php:

@include('header')

Hello World

@include('footer')

Finally, we add a new controller into the controllers folder:

&amp;lt;?php

class Test_Controller{

	/**
	 * Responsible of rendering the home page.
	 *
	 * @return object.
	*/
	public function index(){

		return View::make('pages.test');

	}

}

In essence, this would render “hello world” for the /test route in any framework, but since Themosis relies on WP’s data in the background (it is an MVC API used for extending WP, after all), we need to do one more thing. We need to actually add the page to the system, because of the way routing works.

After reloading, our Hello World message renders.

But we saved “Is anyone there?” in the content, not Hello World – Hello World is in the view – what gives? Well, to actually include the content of the page in the view, we need to access the $post global variable in our controller and pass it into the view, so we can consume its properties. Unfortunately, there is little Themosis can do to alleviate the global pollution WP is famous for.

If we change our controller to actually fetch the page in question:

&amp;lt;?php

class Test_Controller{

	/**
	 * Responsible of rendering the home page.
	 *
	 * @return object.
	*/
	public function index(){

        global $post;

		return View::make('pages.test')-&amp;gt;with(array(
            'post' 	=&amp;gt; $post
        ));

	}

}

and then add the necessary logic to the view:

@include('header')

&amp;lt;h1&amp;gt;Page Content: {{ $post-&amp;gt;post_content;}} &amp;lt;/h1&amp;gt;

@include('footer')

…we can get our page rendered as expected:

Convoluted? Maybe a little, but I assume one gets used to it. What’s important here is that we were given powerful wrappers for rendering all the usually accessible WP content, which provides us with new structure and clarity, giving some rigidity to WP where there was none before.

Conclusion

Think of developing with Themosis as Laravel powered WordPress plugin development – if the core of WP is that hopeless, there’s no need for the plugins to be. Themosis brings much needed structure and encapsulation to the otherwise chaotic and unmaintainable plugin architecture you may have gotten used to.

Themosis is still in its infancy, being 0.8, but version 1.0 is around the corner and we wish the developer much luck and success in this endeavor – good design practices need to be spread around the PHP world like a vaccine, and if one way of doing that is through a good-practice powered plugin framework on a bad-practice core, so be it.