Wednesday, June 25, 2008

Do It Yourself - Plugins (PHP Tutorial)

Today I would like to share with you a quick way to have a plug-in engine running at your site. I came up with this concept quickly a few days ago when I needed to make an extensible layer for one of our projects.

Today we are going to achieve some fundamental functionality of a plug-in system. Please note that any of the external classes used in this tutorial's code can be replaced with only a few lines of code. I was too lazy to do so while copy-pasting my code. And of course you can comment and ask for help.

First of all, let's define the functionality we expect from our system:

  • The plugin creator shouldn't have to touch any code other than his plugin
  • There should be minimal installation needed
  • The plugin should be standalone and deployable to any system using our framework without any hassle


Sounds hard? It isn't. First of all, let me walk you through a few key concepts we are going to exercise while creating this system. I am basing all of this on the presumption that you already are familiar with object oriented programming, if you are not, I advise you to fill in the gaps

Static Keyword


So, you are probably familiar with classes and objects, and you know that to use a class, you first must instantiate it using the new keyword.

You also probably know that every time you create an object of the class' type, you are creating all the methods and properties, and they belong to that specific object.

Basically, when you put the keyword static before a property or method definition, you are declaring that this method or property will belong to the class type itself, rather than instances of it (objects), and therefore be common to all the instances of the same class, and be callable without even creating an instance of a class

Let me give you a quick example.

class className{
static private $variable = 25;
function get_variable(){
return className::$variable;
}

function __construct( $inputNumber = null ){

if(!is_null( $inputNumber )){
className::$variable = $inputNumber;
}
}
}

// We create the first instance. Since we don't give a new value, it stays 25, like
// in the initialization
$firstInstance = new className();
// We create a second instance, this time we change the variable to 45
$secondInstance = new className(45);
// We create a third instance
$thirdInstance = new className();


// Since the variable is static, the change we did in the second function affects
// all of them, because the variable belongs to the class itslef, and not a separate
// instance of it
// All the three functions will output: 45
echo $firstInstance->get_variable();
echo $secondInstance->get_variable();
echo $thirdInstance->get_variable();


Now that you know what the static keyword is, let us move to the next thing you need to know before attempting to write a plugin engine.

Dynamic Access & Creation


This is a very controversial feature in PHP. And some will argue (and will be right in a way) that it shouldn't be there (its just like the argument about strongly typed and weakly typed languages). But since the feature is there, why not make use of it?

Simply enough, PHP lets you call functions and classes dynamically. For instance, you can dynamically decide which class to create.

$variable = 'myClass';
$myObject = new $variable();

The code above will create an instance of the class 'myClass'.

Also, PHP lets us call functions and methods dynamically.

$functionName = 'multiply';
// Same as doing: multiply( 5, 2 );
$functionName( 5, 2 );

// Same as doing: multiply( 5, 2 );
call_user_func( 'multiply', 5, 2 );

// Same as doing: $myObject->myMethod( 'some text' );
call_user_func( array( 'myObject', 'myMethod' ), 'some text' );

// Same as doing: myStaticClass::myMethod( 'some text' );
call_user_func( array( 'myStaticClass', 'myMethod' ), 'some text' );


Let's Get Started


Oh, I almost forgot, we were going to build a plugin engine... Well, let's get started.

First of all, before writing any code, I always figure out what I'm going to make. Then we figure out how, and then after I have everything in mind, I start writing some actual code.

The first planning stage is pretending to be a user of our to-be-written module, and making a quick list of how the final product will act.

So for our plugin system it will be:

  • All the plugins reside in a plugins/ subdirectory of our website. Each in it's own directory.
  • There will be a class that handles all the plugins, and registers them to the system.
  • A plugin can be turned off.
  • The plugin installation should be as simple as copying it to the plugins/ directory, and turning it on.


Then, after some thinking, I decided that I will go with this kind of code architecture:

  • Plugin engine is a fake-singleton class (Read some stuff about actual singleton classes. By fake-singleton, I mean that it is an absolutely static class of which you can't create instances, but it isn't a traditional singleton class (which actually has one instance of a class)
  • Each plugin is also a static class. Why did I choose static classes? Code readability and aesthetics mainly. As well as ease of implementation.
  • Each class has a corresponding table entry in MySQL, that tells us if we enabled the plugin or not.
  • There are multiple "hook checkpoints" where plugins can hook in their code.


Plugin Engine


Basically, what I'll do next here is give the final code, copy-pasted right off my application, and then explain what needs explanation. Please note that you can't just copy-paste my code as it won't work right away. You will need to replace a few lines of code. Nothing too bad.

plugin.class.php
This is the heart of the plugin system. It checks the plugins directory for any plugins, validates them with our database, and then registers them to be ran at the hook checkpoints.

// This bit checks if we are currently
// running inside our website. It prevents
// from overly curious people to launch
// this php file from outside of our script
if( !defined( "INPROCESS" ) ){
header("HTTP/1.0 403 Forbidden");
die();
}

// Those two are classes that I wrote
// to simplify access to mysql and other
// databases. You will have to delete those
// two lines, and write your own code
// to access mysql
require_once( 'data.class.php' );
require_once( 'data.mysql.class.php' );


// This is the parent class for all plugins
// It contains a private constructor, that
// basically makes sure we won't have any
// instances of our static classes. Making
// them completely static.
class plugin{
private function __construct(){}
}

// This is the actual plugin class.
class pluginClass{
// This will be the list of active plugins
static private $plugins = array();

// Again, we don't want any instances
// of our static class.
private function __construct(){}

static function initialize(){
// I have those variables elsewhere in
// a config.php file. you can replace
// those with your own
global $config_fullpath;
global $config_username;
global $config_password;
global $config_server;
global $config_database;

$list = array();
// Populate the list of directories to check against
if ( ($directoryHandle = opendir( $config_fullpath . '/plugins/' )) == true ) {
while (($file = readdir( $directoryHandle )) !== false) {
// Make sure we're not dealing with a file or a link to the parent directory
if( is_dir( $config_fullpath . '/plugins/' . $file ) && ($file == '.' || $file == '..') !== true )
array_push( $list, $file );
}
}


// Get the plugin list from MySQL. Note that you will have to replace
// this code with your own, since I'm using classes that I didn't
// include in this article. Fortunately, it won't be too much of a problem.

// Connect to mysql
$mysqlConnection = new mysqlConnection( $config_username, $config_password, $config_server, $config_database );
// We select all the plugins from our database
// Each plugin has it's name stored, and whether
// it is active or not (active = 0 or 1)
$mysqlConnection->prepareQuery( 'SELECT * FROM plugins' );
$results = $mysqlConnection->executeQuery();
$results = $mysqlConnection->resultToObjects( $results );
$newResult = array();



// Create an array: 'plugin name' = 'active' (1 or 0)
foreach ( $results as $result ){
$newResult[$result->name] = $result->active;
}

// Register the active plugins
foreach( $list as $plugin ){
if($newResult[$plugin] == "1"){
pluginClass::register( $plugin );
}
}
}

// Hook the active plugins at a checkpoint.
// You will see exactly how it works later on.
static function hook( $checkpoint ){
// Cycle through all the plugins that are active
foreach(pluginClass::$plugins as $plugin){
if(!call_user_func( array( $plugin, $checkpoint ) ))
// Throw an exception if we can't hook the plugin
throw new Exception( "Cannot hook plugin ($plugin) at checkpoint ($checkpoint)" );
}
}

// Registration adds the plugin to the list of plugins, and also
// includes it's code into our runtime.
static function register( $plugin ){
global $config_fullpath;
require_once( $config_fullpath . "/plugins/$plugin/$plugin.class.php" );
array_push( pluginClass::$plugins, $plugin );
}
}


index.php

// Tell the class it is safe to run
define( "INPROCESS", true );

// My configuration file
require_once( './config.php' );
// Our plugin class
require_once( './include/plugin.class.php');

// Initialize our plugin engine
pluginClass::initialize();
// Create a new hook point called "onLoad"
// Now all the plugins that can be hooked here
// will be ran.
pluginClass::hook( "onLoad" );


A Sample Plugin
A sample plugin will reside in it's own directory inside plugins/. For example plugins/helloworld/. Then we need to go (or have a script that does it automatically. Homework for you...) to our database and add it, with its name there and active = 1. That's it, we're done with installing our plugin!

plugins/helloworld/helloworld.class.php


if( !defined( "INPROCESS" ) ){
header("HTTP/1.0 403 Forbidden");
die();
}
// We inherit the parent class plugin to make sure we will have no accidental instances of this class.
class helloworld extends plugin {
// That's right. We create a function with the name
// of our hook, and walla! We have a plugin hooked at
// onLoad.
static function onLoad(){
print 'Hello World!';
return true;
}
}


That's it. I am sorry for not doing this one step by step, but if you have any questions, you are welcome to comment. Of course, I didn't include the whole code here. You can create an installer that installs classes automatically, you can make sure there is no trash entries in MySQL by disabling the non existent plugins automatically, but one article won't be enough for that.

I hope it got your gray matter all buzzing and running, and now you'll create some awesome plugin systems for whatever project you're working on.

You're welcome to comment!!!

Tuesday, June 24, 2008

The Right Mind - Stylize

One of the most important aspects of coding visually is your coding style. Each person chooses his own style, but good coding style is always readable and consistent. In this post I will share with you my own coding style, separated to elements.

Naming


One of the most important elements in understandable code is variable names. Let's look at the wrong way to do it.

if($var1 <= $var2){
funcOne();
}

What can you gather about what the program will do in funcOne() from this code? Probably nothing. Now let's look at a better way to do it.

if($nextLoginTime <= $currentTime){
login();
}

Isn't this much better? Without any amazing knowledge in programming you can know right away that the given code will check if the next login time passed, and login if it did.

When writing programs, whether it is compiled C# or C++ code, or open source PHP code, I always try to keep the names of the variables, classes, namespaces and functions relevant to their functionality and contents. This way even after a year, I will read my own code easily.

Indentation


Indentation helps to understand the hierarchy and structure of your code. I use tabs to indent any code that logically belongs to another piece of code.

PHP Examples



function someFunction(){
...

if($success = false){
runFunction();
}

...
}


class someClass{
private $privateVariable = null;
function get_privateVariable(){
return $privateVariable;
}
function set_privateVariable( $newValue ){
$privateVariable = $newValue;
}

public $publicVariable = null;

function someFunction(){
...
}
}


Note that even though the functions get and set for the private variable are not located in between curly brackets, I still indent them, because logically they are just a small subset that belongs to the variable.


Commenting


Commenting may seem tiring and unnecessary at first, but after it becomes your second nature, you can really appreciate it.
I take it as a rule to insert both comments that help reading the code, and comments that describe, functions, classes and variables and are phpDoc compliant for PHP ( SharpDevelop also has similar documentation style for C# ).

While you may not want to comment so extensively, I do recommend you to take as a rule to comment everything that may take more than a moment to figure out while skimming the code.

For instance, compare the following two code fragments.

...
if(preg_match( "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/", $string ))
nextStep();
}
...


...
// If the string is a valid email address, move to the next step
if(preg_match( "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/", $string ))
nextStep();
}
...

You have to admit that you can't easily skim through the first fragment without wondering, and then stopping to figure out what the regular expression does, while reading the second fragment's comment saved you this time.

Tabular Variable Assignment


When there is a big list of variable assignments, I always prefer to have them in a table like structure. The following fragment is an example from a C# application.

public firebirdConnection( string newServer, string newDatabasePath, string newUser, string newPass){
this.server = newServer;
this.path = newDatabasePath;
this.username = newUser;
this.password = newPass;

...
}


The function starts with four variable initialization assignments. By formatting them into a tabbed structure, I make them more readable.

Consistency - Brackets, Function Operators, Spaces e.t.c


Aside from the other rules above, I also set myself a general style according to my own personal taste. I try to always keep all my code consistently formatted with this coding style.

Below is a quick summary of those rules.


// First curly bracket is always on the same line with it's function or statement.
// There is a padding one one space within the function declaration or calling brackets, and there is a space after the comma.
function doThis( $operator, $anotherOperator ){
// No space within if, for or other statement brackets
if($condition == true){
// Functions with lots of operators have a line break after each comma
login( $username,
$password,
'www.server.com',
1234 );
....
}
...
}

The Right Mind - "Love yourself as your fellow"

Welcome to my mini-series of mini-articles about development techniques and challenges. Adding to the little you can already read from my profile description, I will say that I work at a finance and real estate management company as a senior developer of all the infrastructures, data applications and web software.

Being in a position of a senior developer in our company, I have complete control over the coding standards and practices of the projects that we do, which turned out to be a curse, before I could turn it into a blessing.

The first thing I wanted to talk about in this series was a very general rule that I adopted after learning from my mistakes:

Write for Others. Always.

Let me explain. Sometimes, when you write a code for your own project, or for something only, or mostly you will be working on, you may be tempted to shortcut and write messy code, with the presumption that you will understand it later because you wrote it.

Well, in that case, you're wrong.

Try to open an uncommitted script you knew very well a year ago, but didn't touch for the last few months, and you're most likely to spend a long time figuring your own code out if it isn't written properly.

So what is writing "for others"? I will sum it up with a few points, and elaborate on each one in a separate article.

Write code for others. Always:

Maintain
Extend
Separate
Stylize