Hoppa till innehåll

Streamlining, accelerating and encapsulating Osclass plugin development

In our previous post we looked at the basics of building a plugin for Osclass. There are however quite a few issues that I have observed in almost every plugin I have looked at that I felt I wanted to do differently.

Issues I saw with plugins

  • index.php often becomes bloated with functions that handle every aspect of the plugin. At rare occasions are classes that separate logic and presentation introduced (I did this myself when usign the official way of constructing plugins), but more often than not index.php will just consist of a myriad of function mixing logic and presentation, acting as functions for hooks and so on and so fourth.
  • All functions in index.php are publicly available. That means that for plugins that ended up having all their function in index.php, any one of these functions would be publicly available to call from any other plugin.
  • More often than not hooks will be riddled with html codejavascript together with switch and if statements.
  • Plugins are not naturaly object oriented.

Now, you can solve a lot of these issues on your own and some things are not even problems to some people. Object Oriented development might not be your cup of tea and in that case you will not have the same issues I have with this. But to me Object Oriented development is a great way to encapsulate functionality. For instance for a Plugin.

I have for example introduced classes that actually handle my logic and presentation that were basically just called from the hooks in my plugin. But as time went on and I noticed I did more than the few plugins I originally thought I would be doing I felt I wanted some base for my plugins and not reinvent the wheel every time.

Streamlining, accelerating and encapsulating Osclass plugin development

I decided to build myself a base library that I could use to quickly create new plugins. I created dliCore which consists of two components. It’s all just a few days old so things could change, but here’s some of the basics of what I want to achieve.

dliCore

dliCore is a plugin in itself. It provides admin interfaces to configure common things that all plugins based on dliCore can take advantage off.

  • Configuring things like caching manager
  • Presents common place for support requests regarding plugins built using dliCore
  • Contains dliLib and makes sure that is available to plugins

dliLib

  • Contains a myriad of classes to support development of plugins

Together they present developers with a base that accelerates development of plugins and keep them organized even when they grow to large proportions. Do note that any part of it is optional. There are functionality to link objects to specific database tables, but a developer is free to create an Osclass DAO object and work with that if they want to.

This was primarily meant for my own plugins and could function as a catalyst for others to create their own solutions. But if there is a desire for others to use this I’m open to a public supported release to help all of my fellow plugin developers.

Building our Osclass Hello World plugin using dliCore

With dliCore I wanted to take a more object oriented approach and encapsulate plugins in a more clear manner. Instead of having your index.php file contain a bunch of public functions, all which are loaded, mixed logic and presentation within these functions etc. dliCore allow you to encapsulate all of your plugin functionality within a Plugin object. This makes the index.php file look a bit different.

<?php
namespace dliTest;
/*
Plugin Name: dliTest
Plugin URI: http://www.osclass.org/
Description: Test plugin to test functionality of dliCore
Version: 0.1.0
Author: Daniel Liljeberg
Author URI: http://www.danielliljeberg.se/
Short Name: dliTest
Plugin update URI: dliTest
*/
use dliLib\Plugin\Manager\PluginManager;
use dliTest\dliTestPlugin;
 
if(osc_plugin_is_enabled('dliCore/index.php')) {
  require_once(osc_plugins_path() . 'dliCore/index.php');
  \dliCore\dliCore_createAutoloader(__NAMESPACE__, osc_plugins_path());
  $plugin = new dliTestPlugin();
  PluginManager::getInstance()->registerPlugin($plugin);
}
else {
  function dliTest_install() {
    die("This plugin requires dliCore to function!");
  }
  osc_register_plugin(osc_plugin_path(__FILE__) . '_install', 'dliTest\dliTest_install');
}

First we define a namespace for our plugin. These further help us keep our plugin specific code isolated.

namespace dliTest;

A good convention is to have the name of your plugins folder, the namespace and the name of the plugin be the same.

After that we have our normal info block.

/*
Plugin Name: dliTest
Plugin URI: http://www.osclass.org/
Description: Test plugin to test functionality of dliCore
Version: 0.1.0
Author: Daniel Liljeberg
Author URI: http://www.danielliljeberg.se/
Short Name: dliTest
Plugin update URI: dliTest
*/

I would have liked to be able to include this in the plugin class itself, but due to how Osclass reads that data it’s not possible. I wanted to make sure no alterations were needed to Osclass in order for dliCore to work.

Next we declare that we will be using the PluginManager and our plugin dliTestPlugin.

use dliLib\Plugin\Manager\PluginManager;
use dliTest\dliTestPlugin;

After that we check that dliCore is enabled. If it is we make sure that the index.php file of dliCore is loaded by a require_once (since we can’t force Osclass to load that before any other plugin) create an instance of our plugin and register it with the PluginManager. If dliCore is not enable we register a install hook that will just tell the user that dliCore is needed for this plugin to work.

if(osc_plugin_is_enabled('dliCore/index.php')) {

require_once(osc_plugins_path() . 'dliCore/index.php');
\dliCore\dliCore_createAutoloader(__NAMESPACE__, osc_plugins_path());
$plugin = new dliTestPlugin();
PluginManager::getInstance()->registerPlugin($plugin);
}
else {
function dliTest_install() {
die("This plugin requires dliCore to function!");
}
osc_register_plugin(osc_plugin_path(__FILE__) . '_install', 'dliTest\dliTest_install');
}

Now you might look at this and go

-“But hey, if dliCore is enabled we don’t register any install hook. So we won’t be able to install our plugin… and there’s no uninstall hook either… and…”

Easy there. As I said, everything is encapsulated. We do have install functions and uninstall functions and all the other stuff to. They are just not globally available.

dliTestPlugin

Let’s take a look at our plugin class

<?php
namespace dliTest;

use dliLib\Plugin\Plugin;

class dliTestPlugin extends Plugin
{
 protected function _init() {
 
 }

 public function getSupportEmail() {
 return '[email protected]';
 }

 public function installHook() {

 }

 public function uninstallHook() {

 }
}

Here you can see that our plugin contains two public function. installHook and uninstallHook. These are the functions that will run when the plugin is installed or uninstalled. It actually also automatically handles a lot of other things in the background during installation and installation as well, but we’ll look at that later.

Hooks using dliCore

dliCore makes it super easy for you to add a hook to your plugin. Simply create a public function, name it correctly and you have a working hook.

Registering a hook for the item_detail hook

public function itemDetailHook() {
}

Registering a hook for the before_html hook

public function beforeHtmlHook() {
}

As you can see, you use the normal hook name you want to hook against and write that name in camel case instead of using underscores and then you end the name with Hook.

The exact same behavior  is available for filters, just en the function name in Filter.

How do we handle that routing thingy to show our file helloWorld file?

Using dliCore you are free to setup routes like normal in for instance the _init function of your plugin or even the index.php file. But that would make me a bit sad.

Instead you should use Controllers. Create a subfolder under your plugin and name it Controllers. Insede we create a file HelloWorldController.php.

<?php
namespace dliTest\Controllers;

use dliLib\Plugin\Controller\BaseController;

class HelloWorldController extends BaseController
{
 public function helloWorldAction() {

 }
}
?>

This controller will handle our logic. That way we can easily keep it separated from the presentation.

By adding an Action function to your controller, dliCore will automatically create the needed routes for it. For instance the route dliTest/HelloWorld/helloWorld will run our helloWorldAction function in our HelloWorldController. All that is done for you.

But we passed a message parameter last time. How do we get the route to understand that it should accept a parameter named message? Well, you simply add it as a paramater to the action function.

public function helloWorldAction($message) {

}

This will automatically add to the route that a parameter called message is accepted by the route and will automatically provide your function with the result of that. But how do we make sure the parameter fulfills a specific regexp like we could when we manually crafted our routes? Well, I could have used some array to map function parameters to their regexps etc, but I wanted to keep it all in one place. One place to update if you wanted to add or remove a parameter. At first I was thinking about letting the user provide the regexp in a comment block. But I didn’t really like that. After scratching my head a bit I came to think about the fact that since actions are automatically called I could hijack their default value and use that to convey their regular expressions.

So in order to make sure it’s only valid to pass the characters a-Z to the function, we update it like this.

public function helloWorldAction($message = '([a-zA-Z]+)') {

}

This means that adding and removing parameters from actions is extremely fast and easy and you only have to alter code in one place. Instead of having to change your function, update your route etc.

Displaying our message

You could very well just echo your message in the action.

public function helloWorldAction($message = '([a-zA-Z]+)') {
  echo $message;
}

That would print our message in a otherwise empty page. This works alright if the response is some JSON etc. But for presentation of normal pages that’s not how I like to do it.

Instead we create a view file. This is our graphical representation file from a request to our given function. So for dliTest/HelloWorld/helloWorld, which ends up running the helloWorldAction function of the HelloWorldController object we create a file named helloWorld.php.

Now we place this file in a sub folder of our plugin views\HelloWorld\helloWorld.php.

The views folder is the top level folder for our views files. HelloWorld is the name of the controller so that all files owned by a given controller can reside in the same place. The helloWorld part of our files name is the name of the action function minus the Action part. That way it’s easy to quickly find a given view file for any action in any controller.

To make it possible to access our message parameter in the view file we export it to the  view.

public function helloWorldAction($message = '([a-zA-Z]+)') {
  $_this->_exportVariableToView('message', $message);
}

Then in our view file we write

echo \View::newInstance()->_get('message');

This will echo our message parameter.

In your view file you could now easily add html markup to style your presentation, without having it mixed with all your logic.

Theme specific views

It’s also possible for you to create theme specific versions of your views. This allows a developer to add support for specific themes or for users to add alterations to specific themes themselves.

To add a theme specific version of a view file, add a folder under the views folder and name it according to the themes name and place your views under that. For instance

views/cooltheme/HelloWorld/helloWorld.php

Any file placed in the sub folder of an active theme that matches a given route will be used before the default view is used.

The default views can be copied and used as a starting point.

Extra features

dliCore contains a myriad of extra functionality connected to database table management, memory usage, caching, handling objects, managing and sending emails etc. More on this in future posts.

Availability

dliCore will be uploaded to the Osclass market so it can be easily installed for any plugin requiring it.

Kommentarer

Dela i ditt aktivitets flöde

Powered by WP LinkPress

Kommentarer

Dela i ditt aktivitets flöde

Powered by WP LinkPress