Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Anatomy of a Plugin (Object Oriented)
#1
I've written a few tutorials (that will need revision) on plugin development and am not too sure where to put them. Thought here would be best for now.

---------

A sensible structure for your plugin would be as follows:
  • yourplugin.php
  • yourplugin/
    • assets/ (non-php files your plugin may need)
      • css/
      • images/
      • js/
      • xml/ (default xml files you might need to copy to the /data directory)
    • inc/ (php files, excluding classes and language files)
    • lang/ (langauge files, e.g. en_US.php)
    • lib/ (php class files, e.g. plugin.class.php)

Let's go over the PHP files and their contents.

yourplugin.php

This file will be where we register the plugin, load the correct language files and initialize the plugin. A sensible way of doing this will be to encapsulate those procedures in a class and use only a few methods, like below:
PHP Code:
<?php

// get id of plugin based on this file name
$yourpluginId basename(__FILE__'.php');

// include the main plugin class
include($yourpluginId '/lib/plugin.class.php');

// instantiate an object so that we can initialize and register the plugin
$yourplugin = new YourPlugin($yourpluginId);
$yourplugin->autoload();    // autoloader, so classes in /lib/ are automatically included
$yourplugin->i18nMerge();   // load correct language file
$yourplugin->register();    // register the plugin
$yourplugin->init();        // initialize the plugin, set defaults

?>

Below, you can define global functions that you think the admin might need, but otherwise, the yourpluginname.php file does not need any further alteration done to it.
yourpluginname/lib/plugin.class.php

Now we need to ensure that the class actually exists. The following methods need to be made public:
PHP Code:
<?php

class YourPlugin {
  private 
$id;  // plugin id

  
public function __construct($id) { }
  public function 
autoload() { }
  public function 
i18nMerge() { }
  public function 
register() { }
  public function 
init() { }
  public function 
displayAdminPanel() { }
}

?>

__construct

In the class constructor, we pass the id so that it can be accessed via the other functions (we'll need the id for the register method, for example).
PHP Code:
public function __construct($id) {
    
$this->id $id;
  } 

The autoload method will call the spl_autoload_register function and register our autoloader method, a private method that we will define next.

autoload
PHP Code:
public function autoload() {
    
spl_autoload_register(array($this'autoloader'));
  } 
The autoloader method takes a class name as a parameter. It will see if a file name of the form classname.class.php exists in the lib/ directory and load it if it does. Note that the classname is lower case. This is just a preference of mine - as long as you remain consistent, you can decide on whatever naming convention that you want.

PHP Code:
private function autoloader($class) {
    if (
file_exists($file GSPLUGINPATH $this->id '/lib/' strtolower($class) . '.class.php')) {
      include(
$file);
    }
  } 

i18nMerge

In order to load the correct language file, i18nMerge will call the i18n_merge function. If a language file for the active langauge on the GetSimple installation exists, that will be loaded; otherwise, we will default to English (en_US). The plugin id is required (hence why we stored it as a property of the class).

PHP Code:
public function i18nMerge() {
    
i18n_merge($this->id) || i18n_merge($this->id'en_US');
  } 
register

Next, to actually get the plugin working, the plugin's details must be registered. Certain properties such as the plugin title will actually be done using the i18n_r function (so that the title/description/sidebar label change if the language changes). To do all of this, we make one call to the register_plugin function:
PHP Code:
public function register() {
    
register_plugin(
      
$this->id,                                // id
      
i18n_r($this->id '/PLGN_TITLE'),        // title
      
'0.1',                                    // version
      
'You',                                    // author
      
'http://yourwebsite/',                    // author site
      
i18n_r($this->id '/PLGN_DESC'),         // description
      
'pages',                                  // admin page (currently 'Pages')
      
array($this'displayAdminPanel')         // admin panel function
    
);
  } 

init

Now we can set up any initialization queries that should be done prior to the admin getting control. This can range from setting up the plugin hooks/filters to creating folders/files in the /data/other directory. This will all depend on the job of the plugin. The below example will put a sidebar link on the 'pages' page of the admin panel.

PHP Code:
public function init() {
    
// put sidebar link in admin page (pages)
    
add_action('pages-sidebar','createSideMenu', array($this->idi18n_r($this->id '/PLGN_SIDEBAR')));
   
    
// other initialization actions...
  


displayAdminPanel

Finally we tie everything together with the admin panel itself. This function will be called when the admin panel is accessed (i.e. when the admin visits /admin/load.php?id=yourpluginname). Conceptually, all you need is an if/else or switch statement to change the back-end page in accordance with the url query:

PHP Code:
public function displayAdminPanel() {
    if (
condition) {
      
// a page
    
}
    elseif (
othercondition) {
      
// another page
    
}
    else {
      
// main page
    
}
  } 

Whilst quicker to do, it can easily muddy the plugin class as the admin panel becomes more complex. So it is best to modulate the code and handle the admin panel (both its display and its functions) using separate classes.

PHP Code:
public function displayAdminPanel() {
    
$admin = new YourPluginAdminDisplay($this->id);   // load admin panel object
    
$admin->getPage();                                // get the correct admin panel page
  


yourplugin/inc/yourpluginadmin.class.php


This class will contain all of the functions needed for the back-end - e.g. saving settings, creating/editing content, etc...

PHP Code:
<?php

class YourPluginAdmin {
  protected 
$id;    // plugin id
  
protected $url;   // admin url
 
  // constructor
  
public function __construct($id) {
    
$this->id $id;
    
$this->url 'load.php?id='$this->id;
  }
 
  
// add your required admin methods here (and make them protected)
}

?>

yourplugin/inc/yourpluginadmindisplay.class.php

By extending the admin class, this plugin will let you define the admin pages themselves but still be able to use the admin class methods as though it were one class. The if-else conditional is best to have here, in the getPage method.

PHP Code:
<?php

class YourPluginAdminDisplay extends YourPluginAdmin {
  
// display correct admin panel page
  
public function getPage() {
    
// use if/else statements to show the correct admin panel page
    // below is just a default that shows the title and description
    
?>
    <h3><?php i18n($this->id '/PLGN_TITLE'); ?></h3>
    <p><?php i18n($this->id '/PLGN_DESC'); ?></p>
    <?php
  
}
}

?>

yourplugin/lang/en_US.php

With these things set up, we just need to ensure that the correct hashes exist in the langauge file. The fact that the current don't isn't a huge problem - it will not cause an error. But instead of showing the text that we want, it'll show the placeholder (e.g. {yourplugin/PLUGIN_TITLE}). The langauge file merely consists of an array called i18n whose keys are the placeholder titles and whose values are the text we want to replace them with.

PHP Code:
<?php

/** ENGLISH LANGAUGE FILE
      * each hash corresponds to a placeholder
      * access these placeholders using i18n (echo) or i18n_r (return) functions
      * e.g. <?php i18n('yourplugin/PLGN_TITLE'); ?>  */

$i18n = array();
$i18n['PLGN_TITLE']   = 'Your Plugin Title';
$i18n['PLGN_DESC']    = 'Your Plugin description.';
$i18n['PLGN_SIDEBAR'] = 'Your Plugin';

?>

Langauge placeholders can be called within your plugin using the i18n function (for printing/echoing) and i18n_r function (for returning a string - useful for editing the string first). Remember that there are many hashes within the core language file, so consult that hash table first to see if certain phrases have already been translated (this will save you some work). To use those hashes, simply don't prefix the hash with 'yourplugin/' (e.g. i18n('BTN_SAVECHANGES')).

Everything from here on out is dependent on the purpose and complexity of the individual plugin.

Credit:
- GetSimple Wiki for their Anatomy of a Plugin article
- shawn_a for previous comments on plugin framework topic


Download Example
Reply
#2
I think you also have to be careful of autoloaders and globals also, if you are using or declaring any globals inside your autoloaded file the scope will be limited to the autoloader unless you declare these globals before hand, there will also be differences in script path contexts.
NEW: SA Admin Toolbar Plugin | View All My Plugins
- Shawn A aka Tablatronix
Reply
#3
Hmm...good point. I think in general it's better to use the superglobal $GLOBALS array if such globals are necessary (so they needn't be declared). It also reduces the likelihood of accidentally editing a global variable clashing name with "$GLOBALS['varname']".
Reply




Users browsing this thread: 1 Guest(s)